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,412 +0,0 @@
1
- # Controller Reference
2
-
3
- Complete reference for resource controllers.
4
-
5
- ## Overview
6
-
7
- Controllers handle HTTP requests and responses. Plutonium provides a controller module with CRUD actions built-in. You rarely need to customize controllers - definitions handle UI configuration and policies handle authorization.
8
-
9
- ## Base Class
10
-
11
- ```ruby
12
- # app/controllers/resource_controller.rb (generated during install)
13
- class ResourceController < ApplicationController
14
- include Plutonium::Resource::Controller
15
- end
16
-
17
- # app/controllers/posts_controller.rb (generated per resource)
18
- class PostsController < ::ResourceController
19
- # Empty - all CRUD actions inherited
20
- end
21
- ```
22
-
23
- For portals:
24
-
25
- ```ruby
26
- # packages/admin_portal/app/controllers/admin_portal/resource_controller.rb
27
- module AdminPortal
28
- class ResourceController < ::ResourceController
29
- include AdminPortal::Concerns::Controller
30
- end
31
- end
32
-
33
- # packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
34
- module AdminPortal
35
- class PostsController < ResourceController
36
- # Portal-specific customizations
37
- end
38
- end
39
- ```
40
-
41
- ## Built-in Actions
42
-
43
- | Action | HTTP Method | Path | Purpose |
44
- |--------|-------------|------|---------|
45
- | `index` | GET | `/posts` | List with pagination, search, filters, sorting |
46
- | `show` | GET | `/posts/:id` | Show record |
47
- | `new` | GET | `/posts/new` | New record form |
48
- | `create` | POST | `/posts` | Create record |
49
- | `edit` | GET | `/posts/:id/edit` | Edit record form |
50
- | `update` | PATCH/PUT | `/posts/:id` | Update record |
51
- | `destroy` | DELETE | `/posts/:id` | Delete record |
52
-
53
- Plus interactive action routes for custom operations defined in definitions.
54
-
55
- ## Key Methods
56
-
57
- ### Resource Access
58
-
59
- ```ruby
60
- resource_class # The model class (e.g., Post)
61
- resource_record! # Current record (raises RecordNotFound if not found)
62
- resource_record? # Current record (nil if not found)
63
- resource_params # Permitted params for create/update
64
- current_parent # Parent record for nested routes
65
- ```
66
-
67
- ### Authorization
68
-
69
- ```ruby
70
- authorize_current!(record, to: :action?) # Check permission
71
- current_policy # Policy for current resource
72
- permitted_attributes # Allowed attributes for action
73
- current_authorized_scope # Scoped records user can access
74
- authorized_resource_scope(Post) # Authorized scope for a different resource
75
- policy_for(record) # Get policy for any record
76
- allowed_to?(:edit?, record) # Check if action is allowed
77
- ```
78
-
79
- ### Definition Access
80
-
81
- ```ruby
82
- current_definition # Definition for current resource
83
- ```
84
-
85
- ### UI Building
86
-
87
- ```ruby
88
- build_form # Build form component
89
- build_detail # Build show/detail component
90
- build_collection # Build table component
91
- ```
92
-
93
- ### URL Generation
94
-
95
- ```ruby
96
- resource_url_for(@post) # URL for record
97
- resource_url_for(@post, action: :edit) # Edit URL
98
- resource_url_for(Post) # Index URL
99
- resource_url_for(Post, parent: @user) # Nested index URL
100
-
101
- # Interactions (sugar over `action: :interactive_*_action, interactive_action: ...`)
102
- resource_url_for(@post, interaction: :publish) # Record action
103
- resource_url_for(Post, interaction: :import) # Resource (class-level) action
104
- resource_url_for(Post, interaction: :archive, ids: [1, 2]) # Bulk action
105
- ```
106
-
107
- ## Customization Hooks
108
-
109
- All customization is done by overriding private methods.
110
-
111
- ### Redirect Hooks
112
-
113
- ```ruby
114
- class PostsController < ::ResourceController
115
- private
116
-
117
- # Where to go after create/update: "show" (default), "edit", "new", "index"
118
- def preferred_action_after_submit
119
- "edit"
120
- end
121
-
122
- # Custom URL after create/update (overrides preferred_action_after_submit)
123
- def redirect_url_after_submit
124
- resource_url_for(resource_class)
125
- end
126
-
127
- # Custom URL after destroy
128
- def redirect_url_after_destroy
129
- resource_url_for(resource_class)
130
- end
131
- end
132
- ```
133
-
134
- ### Parameter Hooks
135
-
136
- ```ruby
137
- class PostsController < ::ResourceController
138
- private
139
-
140
- # Modify params before create/update
141
- def resource_params
142
- params = super
143
- params[:tags] = params[:tags].split(",") if params[:tags].is_a?(String)
144
- params
145
- end
146
- end
147
- ```
148
-
149
- ### Query Hooks
150
-
151
- ```ruby
152
- class PostsController < ::ResourceController
153
- private
154
-
155
- # Customize the index query
156
- def filtered_resource_collection
157
- base = current_authorized_scope
158
- base = base.featured if params[:featured]
159
- current_query_object.apply(base, raw_resource_query_params)
160
- end
161
- end
162
- ```
163
-
164
- ### Presentation Hooks
165
-
166
- Control whether parent/entity fields appear in forms and displays:
167
-
168
- ```ruby
169
- class PostsController < ::ResourceController
170
- private
171
-
172
- # Show parent field in displays (default: false)
173
- def present_parent?
174
- true
175
- end
176
-
177
- # Include parent field in forms (default: same as present_parent?)
178
- def submit_parent?
179
- true
180
- end
181
-
182
- # Show scoped entity in displays (default: false)
183
- def present_scoped_entity?
184
- true
185
- end
186
-
187
- # Include scoped entity in forms (default: same as present_scoped_entity?)
188
- def submit_scoped_entity?
189
- true
190
- end
191
- end
192
- ```
193
-
194
- ## Lifecycle Callbacks
195
-
196
- Use standard Rails callbacks:
197
-
198
- ```ruby
199
- class PostsController < ::ResourceController
200
- before_action :check_quota, only: [:create]
201
-
202
- private
203
-
204
- def check_quota
205
- if current_user.posts.count >= 100
206
- redirect_to resource_url_for(resource_class), alert: "Post limit reached"
207
- end
208
- end
209
- end
210
- ```
211
-
212
- ## Custom Actions
213
-
214
- For most custom operations, use Interactive Actions in definitions. When you need a custom controller action:
215
-
216
- ```ruby
217
- class PostsController < ::ResourceController
218
- def publish
219
- authorize_current!(resource_record!, to: :publish?)
220
- resource_record!.update!(published: true)
221
- redirect_to resource_url_for(resource_record!), notice: "Published!"
222
- end
223
- end
224
- ```
225
-
226
- ### Routes for Custom Actions
227
-
228
- ```ruby
229
- # In portal routes or config/routes.rb
230
- register_resource Post do
231
- member do
232
- post :publish, as: :publish # Always use as: option!
233
- end
234
- end
235
- ```
236
-
237
- ::: warning Always Name Custom Routes
238
- Always use the `as:` option when defining custom routes. This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
239
- :::
240
-
241
- ## Authorization
242
-
243
- ### Automatic Authorization
244
-
245
- Authorization is checked automatically for standard CRUD actions via `authorize_current!`.
246
-
247
- ### Authorization Verification
248
-
249
- Controllers verify authorization was performed after every action:
250
-
251
- ```ruby
252
- # These run after every action
253
- verify_authorize_current # Ensures authorize_current! was called
254
- verify_current_authorized_scope # Ensures scope was loaded (except new/create)
255
- ```
256
-
257
- ### Skip Authorization Verification
258
-
259
- ```ruby
260
- class PostsController < ::ResourceController
261
- skip_verify_authorize_current only: [:preview]
262
- skip_verify_current_authorized_scope only: [:preview]
263
-
264
- def preview
265
- # Handle authorization manually or skip it
266
- end
267
- end
268
- ```
269
-
270
- ## Nested Resources
271
-
272
- Parent records are automatically resolved:
273
-
274
- ```ruby
275
- # Route: /users/:user_id/nested_posts/:id
276
- class PostsController < ::ResourceController
277
- # current_parent returns the User
278
- # resource_record! returns the Post scoped to that User
279
- end
280
- ```
281
-
282
- Parent fields are automatically excluded from forms/displays. Override with presentation hooks.
283
-
284
- ### Parent-Related Methods
285
-
286
- ```ruby
287
- current_parent # The parent record (e.g., User)
288
- parent_route_param # The route param key (e.g., :user_id)
289
- parent_input_param # The association name (e.g., :user)
290
- ```
291
-
292
- ## Entity Scoping (Multi-tenancy)
293
-
294
- When a portal is scoped to an entity:
295
-
296
- ```ruby
297
- # packages/customer_portal/lib/engine.rb
298
- module CustomerPortal
299
- class Engine < Rails::Engine
300
- include Plutonium::Portal::Engine
301
-
302
- config.after_initialize do
303
- scope_to_entity Organization
304
- end
305
- end
306
- end
307
- ```
308
-
309
- Controllers automatically:
310
- - Scope all queries to the entity
311
- - Exclude entity field from forms (detected by association class)
312
- - Inject entity value on create/update
313
- - Provide `current_scoped_entity` method
314
-
315
- ### Association Detection
316
-
317
- Plutonium auto-detects which `belongs_to` association points to the scoped entity class. This works even when `param_key` differs from the association name:
318
-
319
- ```ruby
320
- # Portal config
321
- scope_to_entity Competition::Team, param_key: :team
322
-
323
- # Model (association name differs from param_key)
324
- class Match < ApplicationRecord
325
- belongs_to :competition_team # Plutonium finds this by class
326
- end
327
- ```
328
-
329
- ### Multiple Associations to Same Class
330
-
331
- If a model has multiple associations to the scoped entity class, Plutonium raises an error:
332
-
333
- ```
334
- Match has multiple associations to Competition::Team: home_team, away_team.
335
- Plutonium cannot auto-detect which one to use for entity scoping.
336
- Override `scoped_entity_association` in your controller to specify the association.
337
- ```
338
-
339
- Resolve by overriding `scoped_entity_association`:
340
-
341
- ```ruby
342
- class MatchesController < ::ResourceController
343
- private
344
-
345
- def scoped_entity_association
346
- :home_team # Return the association name as a symbol
347
- end
348
- end
349
- ```
350
-
351
- ## Specifying Resource Class
352
-
353
- The resource class is inferred from the controller name. Override if needed:
354
-
355
- ```ruby
356
- class LegacyPostsController < ::ResourceController
357
- controller_for Post
358
- end
359
- ```
360
-
361
- ## Response Formats
362
-
363
- Controllers respond to multiple formats:
364
-
365
- - HTML (default)
366
- - JSON (via RABL templates)
367
- - Turbo Stream (for Hotwire)
368
-
369
- ## Error Handling
370
-
371
- ```ruby
372
- class PostsController < ::ResourceController
373
- rescue_from ActiveRecord::RecordNotFound do
374
- redirect_to resource_url_for(resource_class), alert: "Post not found"
375
- end
376
-
377
- rescue_from ActionPolicy::Unauthorized do
378
- redirect_to resource_url_for(resource_class), alert: "Not authorized"
379
- end
380
- end
381
- ```
382
-
383
- ## Portal-Specific Controllers
384
-
385
- Each portal can have its own controller override:
386
-
387
- ```ruby
388
- # packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
389
- module AdminPortal
390
- class PostsController < ResourceController
391
- private
392
-
393
- def preferred_action_after_submit
394
- "index" # Admin prefers list view
395
- end
396
- end
397
- end
398
- ```
399
-
400
- ## Best Practices
401
-
402
- 1. **Keep controllers thin** - Use definitions for UI, policies for auth, interactions for logic
403
- 2. **Don't override CRUD actions** - Customize via hooks (`resource_params`, `redirect_url_after_submit`)
404
- 3. **Use interactive actions** - For custom operations, define in definition with interaction
405
- 4. **Let authorization work** - Don't skip verification without good reason
406
- 5. **Trust the framework** - Most customization belongs in definitions or policies
407
-
408
- ## Related
409
-
410
- - [Definition Reference](/reference/definition/) - UI configuration
411
- - [Policy Reference](/reference/policy/) - Authorization
412
- - [Actions Reference](/reference/definition/actions) - Interactive actions