plutonium 0.45.3 → 0.46.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +146 -0
  3. data/.claude/skills/plutonium-assets/SKILL.md +248 -157
  4. data/.claude/skills/{plutonium-rodauth → plutonium-auth}/SKILL.md +195 -229
  5. data/.claude/skills/plutonium-controller/SKILL.md +9 -2
  6. data/.claude/skills/plutonium-create-resource/SKILL.md +22 -1
  7. data/.claude/skills/plutonium-definition/SKILL.md +521 -7
  8. data/.claude/skills/plutonium-entity-scoping/SKILL.md +317 -0
  9. data/.claude/skills/plutonium-forms/SKILL.md +8 -1
  10. data/.claude/skills/plutonium-installation/SKILL.md +25 -2
  11. data/.claude/skills/plutonium-interaction/SKILL.md +9 -2
  12. data/.claude/skills/plutonium-invites/SKILL.md +11 -7
  13. data/.claude/skills/plutonium-model/SKILL.md +50 -50
  14. data/.claude/skills/plutonium-nested-resources/SKILL.md +8 -1
  15. data/.claude/skills/plutonium-package/SKILL.md +8 -1
  16. data/.claude/skills/plutonium-policy/SKILL.md +69 -78
  17. data/.claude/skills/plutonium-portal/SKILL.md +26 -70
  18. data/.claude/skills/plutonium-views/SKILL.md +9 -2
  19. data/CHANGELOG.md +28 -0
  20. data/app/assets/plutonium.css +1 -1
  21. data/app/views/rodauth/_login_form.html.erb +0 -3
  22. data/app/views/rodauth/confirm_password.html.erb +0 -4
  23. data/app/views/rodauth/create_account.html.erb +0 -3
  24. data/app/views/rodauth/logout.html.erb +0 -3
  25. data/docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md +481 -0
  26. data/docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md +236 -0
  27. data/gemfiles/rails_7.gemfile.lock +1 -1
  28. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  29. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  30. data/lib/generators/pu/core/update/update_generator.rb +8 -0
  31. data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +56 -0
  32. data/lib/generators/pu/invites/install_generator.rb +8 -1
  33. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +43 -0
  34. data/lib/generators/pu/profile/concerns/profile_arguments.rb +10 -4
  35. data/lib/generators/pu/profile/conn_generator.rb +9 -12
  36. data/lib/generators/pu/profile/install_generator.rb +5 -2
  37. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  38. data/lib/generators/pu/saas/portal_generator.rb +4 -9
  39. data/lib/generators/pu/saas/welcome/templates/app/views/welcome/onboarding.html.erb.tt +2 -2
  40. data/lib/plutonium/engine.rb +18 -5
  41. data/lib/plutonium/ui/layout/rodauth_layout.rb +6 -1
  42. data/lib/plutonium/version.rb +1 -1
  43. data/package.json +1 -1
  44. metadata +7 -8
  45. data/.claude/skills/plutonium/skill.md +0 -130
  46. data/.claude/skills/plutonium-definition-actions/SKILL.md +0 -424
  47. data/.claude/skills/plutonium-definition-query/SKILL.md +0 -364
  48. data/.claude/skills/plutonium-profile/SKILL.md +0 -276
  49. data/.claude/skills/plutonium-theming/SKILL.md +0 -424
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.45.3
4
+ version: 0.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-04-07 00:00:00.000000000 Z
10
+ date: 2026-04-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -400,11 +400,11 @@ extra_rdoc_files: []
400
400
  files:
401
401
  - "# Plutonium: The pre-alpha demo.md"
402
402
  - ".claude/skills/plutonium-assets/SKILL.md"
403
+ - ".claude/skills/plutonium-auth/SKILL.md"
403
404
  - ".claude/skills/plutonium-controller/SKILL.md"
404
405
  - ".claude/skills/plutonium-create-resource/SKILL.md"
405
- - ".claude/skills/plutonium-definition-actions/SKILL.md"
406
- - ".claude/skills/plutonium-definition-query/SKILL.md"
407
406
  - ".claude/skills/plutonium-definition/SKILL.md"
407
+ - ".claude/skills/plutonium-entity-scoping/SKILL.md"
408
408
  - ".claude/skills/plutonium-forms/SKILL.md"
409
409
  - ".claude/skills/plutonium-installation/SKILL.md"
410
410
  - ".claude/skills/plutonium-interaction/SKILL.md"
@@ -414,11 +414,8 @@ files:
414
414
  - ".claude/skills/plutonium-package/SKILL.md"
415
415
  - ".claude/skills/plutonium-policy/SKILL.md"
416
416
  - ".claude/skills/plutonium-portal/SKILL.md"
417
- - ".claude/skills/plutonium-profile/SKILL.md"
418
- - ".claude/skills/plutonium-rodauth/SKILL.md"
419
- - ".claude/skills/plutonium-theming/SKILL.md"
420
417
  - ".claude/skills/plutonium-views/SKILL.md"
421
- - ".claude/skills/plutonium/skill.md"
418
+ - ".claude/skills/plutonium/SKILL.md"
422
419
  - ".cliff.toml"
423
420
  - ".node-version"
424
421
  - ".rspec"
@@ -579,6 +576,8 @@ files:
579
576
  - docs/reference/portal/index.md
580
577
  - docs/reference/views/forms.md
581
578
  - docs/reference/views/index.md
579
+ - docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md
580
+ - docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md
582
581
  - esbuild.config.js
583
582
  - exe/pug
584
583
  - gemfiles/rails_7.gemfile
@@ -1,130 +0,0 @@
1
- ---
2
- name: plutonium
3
- description: Use when starting work on a Plutonium app, unsure which skill to read, or need an overview of the resource architecture
4
- ---
5
-
6
- # Plutonium Development Guide
7
-
8
- Read this first when working on a Plutonium application.
9
-
10
- ## Core Rules
11
-
12
- 1. **Always use generators** - Never manually create resources, packages, or portals
13
- 2. **Check relevant skills first** - Each concept has a dedicated skill with details
14
- 3. **Definitions over controllers** - UI customization belongs in definitions, not controllers
15
- 4. **Policies for authorization** - All permission logic goes in policies
16
-
17
- ## Key Generators
18
-
19
- ```bash
20
- rails g pu:res:scaffold Post title:string --dest=main_app # Create resource
21
- rails g pu:res:conn Post --dest=admin_portal # Connect to portal
22
- rails g pu:pkg:package blogging # Create feature package
23
- rails g pu:pkg:portal admin # Create portal
24
- ```
25
-
26
- Always specify `--dest` to avoid interactive prompts.
27
-
28
- ## Resource Architecture
29
-
30
- A **resource** is four layers working together for full CRUD with minimal code:
31
-
32
- | Layer | File | Purpose | Customize when... |
33
- |-------|------|---------|-------------------|
34
- | **Model** | `app/models/post.rb` | Data, validations, associations | Adding business logic |
35
- | **Definition** | `app/definitions/post_definition.rb` | UI - fields, actions, filters | Changing how things look/behave |
36
- | **Policy** | `app/policies/post_policy.rb` | Authorization - who can do what | Restricting access |
37
- | **Controller** | `app/controllers/posts_controller.rb` | Request handling | Rarely - use hooks if needed |
38
-
39
- ```
40
- ┌─────────────────────────────────────────────────────────────────┐
41
- │ Resource │
42
- ├─────────────────────────────────────────────────────────────────┤
43
- │ Model │ Definition │ Policy │ Controller│
44
- │ (WHAT it is) │ (HOW it looks) │ (WHO can act) │ (HOW it │
45
- │ │ │ │ responds) │
46
- ├─────────────────────────────────────────────────────────────────┤
47
- │ - attributes │ - field types │ - permissions │ - CRUD │
48
- │ - associations │ - inputs/forms │ - scoping │ - redirects│
49
- │ - validations │ - displays │ - attributes │ - params │
50
- │ - scopes │ - actions │ │ │
51
- │ - callbacks │ - filters │ │ │
52
- └─────────────────────────────────────────────────────────────────┘
53
- ```
54
-
55
- ## Auto-Detection
56
-
57
- Plutonium automatically detects from your model:
58
- - All database columns with appropriate field types
59
- - Associations (belongs_to, has_one, has_many)
60
- - Attachments (Active Storage)
61
- - Enums
62
-
63
- **You only need to declare when overriding defaults.**
64
-
65
- ## Creating Resources
66
-
67
- ### New Resources (from scratch)
68
-
69
- ```bash
70
- rails g pu:res:scaffold Post user:belongs_to title:string 'content:text?' published:boolean --dest=main_app
71
- ```
72
-
73
- See `plutonium-create-resource` skill for full generator options.
74
-
75
- ### From Existing Models
76
-
77
- 1. Include `Plutonium::Resource::Record` in your model (or inherit from a class that does)
78
- 2. Generate supporting files: `rails g pu:res:scaffold Post --no-migration`
79
- 3. Connect to a portal: `rails g pu:res:conn Post --dest=admin_portal`
80
-
81
- ## Connecting to Portals
82
-
83
- Resources must be connected to a portal to be accessible:
84
-
85
- ```bash
86
- rails g pu:res:conn Post --dest=admin_portal
87
- ```
88
-
89
- See `plutonium-portal` skill for portal details.
90
-
91
- ## Portal-Specific Customization
92
-
93
- Each portal can override the base definition, policy, and controller:
94
-
95
- ```ruby
96
- # Admin portal sees more
97
- class AdminPortal::PostDefinition < ::PostDefinition
98
- scope :draft
99
- scope :pending_review
100
- action :feature, interaction: FeaturePostInteraction
101
- end
102
- ```
103
-
104
- ## Workflow Summary
105
-
106
- 1. **Generate** - `rails g pu:res:scaffold Model attributes... --dest=main_app`
107
- 2. **Migrate** - `rails db:migrate`
108
- 3. **Connect** - `rails g pu:res:conn Model --dest=portal_name`
109
- 4. **Customize** - Edit definition/policy as needed
110
-
111
- ## Skill Reference
112
-
113
- | Topic | Skill |
114
- |-------|-------|
115
- | Creating resources | `plutonium-create-resource` |
116
- | Models & features | `plutonium-model` |
117
- | Field configuration | `plutonium-definition` |
118
- | Actions & interactions | `plutonium-definition-actions` |
119
- | Search, filters, scopes | `plutonium-definition-query` |
120
- | Authorization | `plutonium-policy` |
121
- | Custom views | `plutonium-views` |
122
- | Custom forms | `plutonium-forms` |
123
- | Nested resources | `plutonium-nested-resources` |
124
- | Packages & portals | `plutonium-package`, `plutonium-portal` |
125
- | Authentication | `plutonium-rodauth` |
126
- | Interactions | `plutonium-interaction` |
127
- | Theming & assets | `plutonium-theming`, `plutonium-assets` |
128
- | User profile | `plutonium-profile` |
129
- | User invites | `plutonium-invites` |
130
- | Installation | `plutonium-installation` |
@@ -1,424 +0,0 @@
1
- ---
2
- name: plutonium-definition-actions
3
- description: Use when adding custom actions, bulk operations, or wiring interactions into Plutonium resource definitions
4
- ---
5
-
6
- # Definition Actions
7
-
8
- Actions define custom operations that can be performed on resources. They can be simple (navigation) or interactive (with business logic via Interactions).
9
-
10
- ## Action Types
11
-
12
- | Type | Shows In | Use Case |
13
- |------|----------|----------|
14
- | `resource_action` | Index page | Import, Export, Create |
15
- | `record_action` | Show page | Edit, Delete, Archive |
16
- | `collection_record_action` | Table rows | Quick actions per row |
17
- | `bulk_action` | Selected records | Bulk operations |
18
-
19
- ## Simple Actions (Navigation)
20
-
21
- Simple actions link to existing routes. **The target route must already exist** - these don't create new functionality, just navigation links.
22
-
23
- ```ruby
24
- class PostDefinition < ResourceDefinition
25
- # Link to external URL
26
- action :documentation,
27
- label: "Documentation",
28
- route_options: {url: "https://docs.example.com"},
29
- icon: Phlex::TablerIcons::Book,
30
- resource_action: true
31
-
32
- # Link to custom controller action (you must add the action + route yourself)
33
- action :reports,
34
- route_options: {action: :reports},
35
- icon: Phlex::TablerIcons::ChartBar,
36
- resource_action: true
37
- end
38
- ```
39
-
40
- **Important:** When adding custom routes for actions, always use the `as:` option to name them:
41
-
42
- ```ruby
43
- # In your portal routes or config/routes.rb
44
- resources :posts do
45
- collection do
46
- get :reports, as: :reports # Named route required!
47
- end
48
- end
49
- ```
50
-
51
- This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
52
-
53
- **Note:** For custom operations with business logic, use **Interactive Actions** with an Interaction class instead. That's the recommended approach for most custom actions.
54
-
55
- ## Interactive Actions (with Interaction)
56
-
57
- ```ruby
58
- class PostDefinition < ResourceDefinition
59
- action :publish,
60
- interaction: PublishInteraction,
61
- icon: Phlex::TablerIcons::Send
62
-
63
- action :archive,
64
- interaction: ArchiveInteraction,
65
- color: :danger,
66
- category: :danger,
67
- position: 1000,
68
- confirmation: "Are you sure?"
69
- end
70
- ```
71
-
72
- ## Action Options
73
-
74
- ```ruby
75
- action :name,
76
- # Display
77
- label: "Custom Label", # Button text (default: name.titleize)
78
- description: "What it does", # Tooltip/description
79
- icon: Phlex::TablerIcons::Star, # Icon component
80
- color: :danger, # :primary, :secondary, :danger
81
-
82
- # Visibility
83
- resource_action: true, # Show on index page
84
- record_action: true, # Show on show page
85
- collection_record_action: true, # Show in table rows
86
- bulk_action: true, # For selected records
87
-
88
- # Grouping
89
- category: :primary, # :primary, :secondary, :danger
90
- position: 50, # Order (lower = first)
91
-
92
- # Behavior
93
- confirmation: "Are you sure?", # Confirmation dialog
94
- turbo_frame: "_top", # Turbo frame target
95
- route_options: {action: :foo} # Route configuration
96
- ```
97
-
98
- ## Creating an Interaction
99
-
100
- ### Basic Structure
101
-
102
- ```ruby
103
- # app/interactions/resource_interaction.rb (generated during install)
104
- class ResourceInteraction < Plutonium::Resource::Interaction
105
- end
106
-
107
- # app/interactions/archive_interaction.rb
108
- class ArchiveInteraction < ResourceInteraction
109
- presents label: "Archive",
110
- icon: Phlex::TablerIcons::Archive,
111
- description: "Archive this record"
112
-
113
- attribute :resource # The record being acted on
114
-
115
- def execute
116
- resource.archived!
117
- succeed(resource).with_message("Record archived successfully.")
118
- rescue ActiveRecord::RecordInvalid => e
119
- failed(e.record.errors)
120
- rescue => error
121
- failed("Archive failed. Please try again.")
122
- end
123
- end
124
- ```
125
-
126
- ### With Additional Inputs
127
-
128
- ```ruby
129
- # app/interactions/company/invite_user_interaction.rb
130
- class Company::InviteUserInteraction < Plutonium::Resource::Interaction
131
- presents label: "Invite User", icon: Phlex::TablerIcons::Mail
132
-
133
- attribute :resource # The company
134
- attribute :email
135
- attribute :role
136
-
137
- # Configure form inputs
138
- input :email, as: :email, hint: "User's email address"
139
- input :role, as: :select, choices: %w[admin member viewer]
140
-
141
- # Validations
142
- validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
143
- validates :role, presence: true, inclusion: {in: %w[admin member viewer]}
144
-
145
- def execute
146
- UserInvite.create!(
147
- company: resource,
148
- email: email,
149
- role: role,
150
- invited_by: current_user
151
- )
152
- succeed(resource).with_message("Invitation sent to #{email}.")
153
- rescue ActiveRecord::RecordInvalid => e
154
- failed(e.record.errors)
155
- end
156
- end
157
- ```
158
-
159
- ### Bulk Action (Multiple Records)
160
-
161
- Bulk actions operate on multiple selected records at once. When a definition has bulk actions, the resource table automatically shows:
162
- - **Selection checkboxes** in each row
163
- - **Bulk actions toolbar** that appears when records are selected
164
-
165
- ```ruby
166
- # 1. Create the interaction (note: plural `resources` attribute)
167
- class BulkArchiveInteraction < Plutonium::Resource::Interaction
168
- presents label: "Archive Selected", icon: Phlex::TablerIcons::Archive
169
-
170
- attribute :resources # Array of records (note: plural)
171
-
172
- def execute
173
- count = 0
174
- resources.each do |record|
175
- record.archived!
176
- count += 1
177
- end
178
- succeed(resources).with_message("#{count} records archived.")
179
- rescue => error
180
- failed("Bulk archive failed: #{error.message}")
181
- end
182
- end
183
-
184
- # 2. Register the action in the definition
185
- class PostDefinition < ResourceDefinition
186
- action :bulk_archive, interaction: BulkArchiveInteraction
187
- # bulk_action: true is automatically inferred from `resources` attribute
188
- end
189
-
190
- # 3. Add policy method
191
- class PostPolicy < ResourcePolicy
192
- def bulk_archive?
193
- create? # Or whatever permission level is appropriate
194
- end
195
- end
196
- ```
197
-
198
- **Authorization for bulk actions:**
199
- - Policy method (e.g., `bulk_archive?`) is checked **per record** - the backend fails the entire request if any selected record is not authorized
200
- - Records are fetched via `current_authorized_scope` - only records the user can access are included
201
- - The UI only shows action buttons that **all** selected records support (intersection of allowed actions)
202
-
203
- ### Resource Action (No Record)
204
-
205
- ```ruby
206
- class ImportInteraction < Plutonium::Resource::Interaction
207
- presents label: "Import CSV", icon: Phlex::TablerIcons::Upload
208
-
209
- # No :resource or :resources attribute = resource action
210
- attribute :file
211
-
212
- input :file, as: :file
213
-
214
- validates :file, presence: true
215
-
216
- def execute
217
- # Import logic...
218
- succeed(nil).with_message("Import completed.")
219
- end
220
- end
221
- ```
222
-
223
- ## Interaction Responses
224
-
225
- ```ruby
226
- def execute
227
- # Success with message (redirects to resource automatically)
228
- succeed(resource).with_message("Done!")
229
-
230
- # Success with custom redirect (only if different from default)
231
- succeed(resource)
232
- .with_redirect_response(custom_dashboard_path)
233
- .with_message("Redirecting...")
234
-
235
- # Failure with field errors
236
- failed(resource.errors)
237
-
238
- # Failure with custom message
239
- failed("Something went wrong")
240
-
241
- # Failure with specific field
242
- failed("Invalid value", :email)
243
- end
244
- ```
245
-
246
- **Note:** Redirect is automatic on success. Only use `with_redirect_response` for a different destination.
247
-
248
- ## Interaction Context
249
-
250
- Inside an interaction:
251
- - `current_user` - The authenticated user
252
- - `view_context` - Access to helpers and view methods
253
-
254
- ```ruby
255
- def execute
256
- resource.update!(
257
- archived_by: current_user,
258
- archived_at: Time.current
259
- )
260
- succeed(resource)
261
- end
262
- ```
263
-
264
- ## Defining in Definition
265
-
266
- ### Basic
267
-
268
- ```ruby
269
- class PostDefinition < ResourceDefinition
270
- action :publish, interaction: PublishInteraction
271
- action :archive, interaction: ArchiveInteraction
272
- end
273
- ```
274
-
275
- ### With Overrides
276
-
277
- ```ruby
278
- class PostDefinition < ResourceDefinition
279
- action :archive,
280
- interaction: ArchiveInteraction,
281
- collection_record_action: false, # Don't show in table
282
- color: :danger,
283
- position: 1000 # Show last
284
- end
285
- ```
286
-
287
- ### Inherited Actions
288
-
289
- Actions defined in `ResourceDefinition` (created during install) are inherited by all definitions:
290
-
291
- ```ruby
292
- # app/definitions/resource_definition.rb (created during install)
293
- class ResourceDefinition < Plutonium::Resource::Definition
294
- action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
295
- end
296
-
297
- # All definitions inherit the archive action automatically
298
- class PostDefinition < ResourceDefinition
299
- end
300
- ```
301
-
302
- ### Portal-Specific Actions
303
-
304
- Override actions for a specific portal:
305
-
306
- ```ruby
307
- # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
308
- class AdminPortal::PostDefinition < ::PostDefinition
309
- # Add admin-only actions
310
- action :feature, interaction: FeaturePostInteraction
311
- action :bulk_publish, interaction: BulkPublishInteraction
312
- end
313
- ```
314
-
315
- ## Default CRUD Actions
316
-
317
- Plutonium provides these by default:
318
-
319
- ```ruby
320
- action :new, resource_action: true, position: 10
321
- action :show, collection_record_action: true, position: 10
322
- action :edit, record_action: true, position: 20
323
- action :destroy, record_action: true, position: 100, category: :danger
324
- ```
325
-
326
- ## Authorization
327
-
328
- Actions are authorized via policies:
329
-
330
- ```ruby
331
- # app/policies/post_policy.rb
332
- class PostPolicy < ResourcePolicy
333
- def publish?
334
- user.admin? || record.author == user
335
- end
336
-
337
- def archive?
338
- user.admin?
339
- end
340
- end
341
- ```
342
-
343
- The action only appears if the policy method returns `true`.
344
-
345
- ## Immediate vs Form Actions
346
-
347
- **Immediate** - Executes without showing a form (when interaction has no extra inputs):
348
-
349
- ```ruby
350
- class ArchiveInteraction < Plutonium::Resource::Interaction
351
- attribute :resource # Only resource, no other inputs
352
- # No input declarations
353
-
354
- def execute
355
- resource.archived!
356
- succeed(resource)
357
- end
358
- end
359
- ```
360
-
361
- **Form** - Shows a form first (when interaction has additional inputs):
362
-
363
- ```ruby
364
- class InviteUserInteraction < Plutonium::Resource::Interaction
365
- attribute :resource
366
- attribute :email
367
- attribute :role
368
-
369
- input :email
370
- input :role, as: :select, choices: %w[admin member]
371
- # Has inputs = shows form first
372
- end
373
- ```
374
-
375
- ## Common Patterns
376
-
377
- ### Archive/Restore
378
-
379
- ```ruby
380
- class ArchiveInteraction < Plutonium::Resource::Interaction
381
- presents label: "Archive", icon: Phlex::TablerIcons::Archive
382
- attribute :resource
383
-
384
- def execute
385
- resource.archived!
386
- succeed(resource).with_message("Archived.")
387
- end
388
- end
389
-
390
- class RestoreInteraction < Plutonium::Resource::Interaction
391
- presents label: "Restore", icon: Phlex::TablerIcons::Refresh
392
- attribute :resource
393
-
394
- def execute
395
- resource.active!
396
- succeed(resource).with_message("Restored.")
397
- end
398
- end
399
- ```
400
-
401
- ### Send Notification
402
-
403
- ```ruby
404
- class SendReminderInteraction < Plutonium::Resource::Interaction
405
- presents label: "Send Reminder", icon: Phlex::TablerIcons::Bell
406
- attribute :resource
407
- attribute :message
408
-
409
- input :message, as: :text, hint: "Custom message (optional)"
410
-
411
- def execute
412
- ReminderMailer.with(record: resource, message: message).deliver_later
413
- succeed(resource).with_message("Reminder sent.")
414
- end
415
- end
416
- ```
417
-
418
- ## Related Skills
419
-
420
- - `plutonium-definition` - Overview and structure
421
- - `plutonium-definition` - Fields, inputs, displays
422
- - `plutonium-definition-query` - Search, filters, scopes
423
- - `plutonium-interaction` - Writing interaction classes
424
- - `plutonium-policy` - Controlling action access