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,462 +0,0 @@
1
- # Definition Actions
2
-
3
- Complete reference for custom actions in definitions.
4
-
5
- ## Overview
6
-
7
- Actions add buttons beyond standard CRUD operations. Two types:
8
-
9
- 1. **Simple Actions** - Navigate to URLs
10
- 2. **Interactive Actions** - Execute Interactions with optional user input
11
-
12
- ## Action Types
13
-
14
- | Type | Shows In | Use Case |
15
- |------|----------|----------|
16
- | `resource_action` | Index page | Import, Export, Create |
17
- | `record_action` | Show page | Edit, Delete, Archive |
18
- | `collection_record_action` | Table rows | Quick actions per row |
19
- | `bulk_action` | Selected records | Bulk operations |
20
-
21
- ## Simple Actions
22
-
23
- Simple actions link to existing routes. The target route must already exist.
24
-
25
- ```ruby
26
- class PostDefinition < Plutonium::Resource::Definition
27
- # Link to external URL
28
- action :documentation,
29
- label: "Documentation",
30
- route_options: {url: "https://docs.example.com"},
31
- icon: Phlex::TablerIcons::Book,
32
- resource_action: true
33
-
34
- # Link to custom controller action
35
- action :reports,
36
- route_options: {action: :reports},
37
- icon: Phlex::TablerIcons::ChartBar,
38
- resource_action: true
39
- end
40
- ```
41
-
42
- ::: warning Always Name Custom Routes
43
- When adding custom routes for actions, always use the `as:` option:
44
-
45
- ```ruby
46
- resources :posts do
47
- collection do
48
- get :reports, as: :reports # Named route required!
49
- end
50
- end
51
- ```
52
-
53
- This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
54
- :::
55
-
56
- **Note:** For custom operations with business logic, use **Interactive Actions** with an Interaction class.
57
-
58
- ## Interactive Actions
59
-
60
- ```ruby
61
- class PostDefinition < Plutonium::Resource::Definition
62
- action :publish,
63
- interaction: PublishInteraction,
64
- icon: Phlex::TablerIcons::Send
65
-
66
- action :archive,
67
- interaction: ArchiveInteraction,
68
- color: :danger,
69
- category: :danger,
70
- position: 1000,
71
- confirmation: "Are you sure?"
72
- end
73
- ```
74
-
75
- ## Action Options
76
-
77
- ```ruby
78
- action :name,
79
- # Display
80
- label: "Custom Label", # Button text (default: name.titleize)
81
- description: "What it does", # Tooltip/description
82
- icon: Phlex::TablerIcons::Star, # Icon component
83
-
84
- # Styling
85
- color: :danger, # :primary, :secondary, :danger
86
-
87
- # Visibility (boolean flags)
88
- resource_action: true, # Show on index page
89
- record_action: true, # Show on show page
90
- collection_record_action: true, # Show in table rows
91
- bulk_action: true, # For selected records
92
-
93
- # Grouping
94
- category: :primary, # :primary, :secondary, :danger
95
- position: 50, # Order (lower = first)
96
-
97
- # Behavior
98
- confirmation: "Are you sure?", # Confirmation dialog
99
- turbo_frame: "_top", # Turbo frame target
100
- return_to: "/custom/path", # Override return URL
101
- route_options: {action: :foo}, # Route configuration
102
-
103
- # Dialog chrome (for interactive actions with a form)
104
- modal: :slideover # :centered (default) or :slideover
105
- ```
106
-
107
- ### `Action#with(...)`
108
-
109
- Action records are frozen value objects. To derive a variant — typically inside `customize_actions` — call `existing.with(...)` for a new copy with overrides applied:
110
-
111
- ```ruby
112
- def customize_actions
113
- defined_actions[:edit] = defined_actions[:edit].with(turbo_frame: "_top")
114
- end
115
- ```
116
-
117
- ## Route Options
118
-
119
- Configure how the action's URL is generated:
120
-
121
- ```ruby
122
- # Simple route to controller action
123
- action :preview,
124
- route_options: {action: :preview},
125
- record_action: true
126
-
127
- # With HTTP method
128
- action :archive,
129
- route_options: {method: :post, action: :archive},
130
- record_action: true
131
-
132
- # External URL
133
- action :docs,
134
- route_options: {url: "https://docs.example.com"},
135
- resource_action: true
136
-
137
- # Custom URL resolver
138
- action :create_deployment,
139
- route_options: Plutonium::Action::RouteOptions.new(
140
- url_resolver: ->(subject) {
141
- resource_url_for(Deployment, action: :new, parent: subject)
142
- }
143
- ),
144
- record_action: true
145
- ```
146
-
147
- ## Built-in CRUD Actions
148
-
149
- These are defined by default:
150
-
151
- ```ruby
152
- action :new,
153
- route_options: {action: :new},
154
- resource_action: true,
155
- category: :primary,
156
- icon: Phlex::TablerIcons::Plus,
157
- position: 10
158
-
159
- action :show,
160
- route_options: {action: :show},
161
- collection_record_action: true,
162
- icon: Phlex::TablerIcons::Eye,
163
- position: 10
164
-
165
- action :edit,
166
- route_options: {action: :edit},
167
- record_action: true,
168
- collection_record_action: true,
169
- icon: Phlex::TablerIcons::Edit,
170
- position: 20
171
-
172
- action :destroy,
173
- route_options: {method: :delete},
174
- record_action: true,
175
- collection_record_action: true,
176
- category: :danger,
177
- icon: Phlex::TablerIcons::Trash,
178
- position: 100,
179
- confirmation: "Are you sure?",
180
- turbo_frame: "_top"
181
- ```
182
-
183
- ### Customizing Built-in Actions
184
-
185
- Override in your definition:
186
-
187
- ```ruby
188
- class PostDefinition < Plutonium::Resource::Definition
189
- # Customize delete confirmation
190
- action :destroy,
191
- confirmation: "This will permanently delete the post and all comments.",
192
- route_options: {method: :delete},
193
- record_action: true,
194
- collection_record_action: true,
195
- category: :danger,
196
- icon: Phlex::TablerIcons::Trash,
197
- position: 100,
198
- turbo_frame: "_top"
199
- end
200
- ```
201
-
202
- ## Authorization
203
-
204
- Actions are authorized via policies:
205
-
206
- ```ruby
207
- # app/policies/post_policy.rb
208
- class PostPolicy < Plutonium::Resource::Policy
209
- def publish?
210
- user.admin? || record.author == user
211
- end
212
-
213
- def archive?
214
- user.admin?
215
- end
216
- end
217
- ```
218
-
219
- The action only appears if the policy method returns `true`.
220
-
221
- ## Immediate vs Form Actions
222
-
223
- **Immediate** - Executes without showing a form (when interaction has no extra inputs):
224
-
225
- ```ruby
226
- class ArchiveInteraction < Plutonium::Resource::Interaction
227
- attribute :resource # Only resource, no other inputs
228
- # No input declarations
229
-
230
- def execute
231
- resource.archived!
232
- succeed(resource)
233
- end
234
- end
235
- ```
236
-
237
- **Form** - Shows a form first (when interaction has additional inputs):
238
-
239
- ```ruby
240
- class InviteUserInteraction < Plutonium::Resource::Interaction
241
- attribute :resource
242
- attribute :email
243
- attribute :role
244
-
245
- input :email
246
- input :role, as: :select, choices: %w[admin member]
247
- # Has inputs = shows form first
248
- end
249
- ```
250
-
251
- ## Interaction Reference
252
-
253
- ### Basic Structure
254
-
255
- ```ruby
256
- class PublishInteraction < Plutonium::Resource::Interaction
257
- presents label: "Publish",
258
- icon: Phlex::TablerIcons::Send,
259
- description: "Make this post public"
260
-
261
- attribute :resource # The record being acted upon
262
-
263
- private
264
-
265
- def execute
266
- resource.update!(published: true, published_at: Time.current)
267
- succeed(resource).with_message("Published!")
268
- rescue ActiveRecord::RecordInvalid => e
269
- failed(e.record.errors)
270
- end
271
- end
272
- ```
273
-
274
- ### With User Input
275
-
276
- ```ruby
277
- class InviteUserInteraction < Plutonium::Resource::Interaction
278
- presents label: "Invite User", icon: Phlex::TablerIcons::Mail
279
-
280
- attribute :resource # The company
281
- attribute :email
282
- attribute :role
283
-
284
- input :email, as: :email
285
- input :role, as: :select, choices: %w[admin member viewer]
286
-
287
- validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
288
- validates :role, presence: true
289
-
290
- private
291
-
292
- def execute
293
- UserInvite.create!(
294
- company: resource,
295
- email: email,
296
- role: role
297
- )
298
- succeed(resource).with_message("Invitation sent to #{email}.")
299
- rescue ActiveRecord::RecordInvalid => e
300
- failed(e.record.errors)
301
- end
302
- end
303
- ```
304
-
305
- ### Bulk Action
306
-
307
- Bulk actions operate on multiple selected records. When registered, the resource table automatically shows:
308
- - **Selection checkboxes** in each row
309
- - **Bulk actions toolbar** that appears when records are selected
310
-
311
- ```ruby
312
- class BulkArchiveInteraction < Plutonium::Resource::Interaction
313
- presents label: "Archive Selected", icon: Phlex::TablerIcons::Archive
314
-
315
- attribute :resources # Array of records (note: plural)
316
-
317
- private
318
-
319
- def execute
320
- count = 0
321
- resources.each do |record|
322
- record.archived!
323
- count += 1
324
- end
325
- succeed(resources).with_message("#{count} records archived.")
326
- end
327
- end
328
- ```
329
-
330
- Register in definition:
331
-
332
- ```ruby
333
- class PostDefinition < ResourceDefinition
334
- action :bulk_archive, interaction: BulkArchiveInteraction
335
- # bulk_action: true is automatically inferred from `resources` attribute
336
- end
337
- ```
338
-
339
- Add the policy method (checked per-record):
340
-
341
- ```ruby
342
- class PostPolicy < ResourcePolicy
343
- def bulk_archive?
344
- # Can use record attributes - checked for each selected record
345
- user.admin? || record.author == user
346
- end
347
- end
348
- ```
349
-
350
- ::: tip Bulk Action Authorization
351
- Bulk actions use **per-record authorization**:
352
- - Policy method (e.g., `bulk_archive?`) is checked for **each selected record** - you can use `record` attributes
353
- - Backend rejects the entire request if any record fails authorization
354
- - UI only shows actions that **all** selected records support
355
- :::
356
-
357
- ### Resource Action (No Record)
358
-
359
- ```ruby
360
- class ImportInteraction < Plutonium::Resource::Interaction
361
- presents label: "Import CSV", icon: Phlex::TablerIcons::Upload
362
-
363
- # No :resource or :resources = resource action
364
- attribute :file
365
-
366
- input :file, as: :file
367
-
368
- validates :file, presence: true
369
-
370
- private
371
-
372
- def execute
373
- # Import logic...
374
- succeed(nil).with_message("Import completed.")
375
- end
376
- end
377
- ```
378
-
379
- ## Interaction Responses
380
-
381
- ```ruby
382
- def execute
383
- # Success with message (redirects to resource automatically)
384
- succeed(resource).with_message("Done!")
385
-
386
- # Success with custom redirect (only if different from default)
387
- succeed(resource)
388
- .with_redirect_response(custom_dashboard_path)
389
- .with_message("Redirecting...")
390
-
391
- # Failure with field errors
392
- failed(resource.errors)
393
-
394
- # Failure with custom message
395
- failed("Something went wrong")
396
- end
397
- ```
398
-
399
- ::: tip Automatic Redirect
400
- Redirect is automatic on success. You can use `with_redirect_response` for a different destination.
401
- :::
402
-
403
- ## Inherited Actions
404
-
405
- Actions defined in `ResourceDefinition` are inherited by all definitions:
406
-
407
- ```ruby
408
- # app/definitions/resource_definition.rb
409
- class ResourceDefinition < Plutonium::Resource::Definition
410
- action :archive,
411
- interaction: ArchiveInteraction,
412
- color: :danger,
413
- position: 1000
414
- end
415
-
416
- # All definitions inherit the archive action automatically
417
- class PostDefinition < ResourceDefinition
418
- end
419
- ```
420
-
421
- ## Portal-Specific Actions
422
-
423
- Override actions for a specific portal:
424
-
425
- ```ruby
426
- # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
427
- class AdminPortal::PostDefinition < ::PostDefinition
428
- # Add admin-only actions
429
- action :feature, interaction: FeaturePostInteraction
430
- action :bulk_publish, interaction: BulkPublishInteraction
431
- end
432
- ```
433
-
434
- ## Common Patterns
435
-
436
- ### Archive/Restore
437
-
438
- ```ruby
439
- action :archive,
440
- interaction: ArchiveInteraction,
441
- record_action: true,
442
- color: :danger
443
-
444
- action :restore,
445
- interaction: RestoreInteraction,
446
- record_action: true
447
- ```
448
-
449
- ### Export
450
-
451
- ```ruby
452
- action :export,
453
- interaction: ExportInteraction,
454
- resource_action: true,
455
- icon: Phlex::TablerIcons::Download
456
- ```
457
-
458
- ## Related
459
-
460
- - [Definition Reference](./index)
461
- - [Fields Reference](./fields)
462
- - [Query Reference](./query)