plutonium 0.33.1 → 0.34.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +32 -1
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/query_object.rb +38 -8
  102. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  103. data/lib/plutonium/version.rb +1 -1
  104. data/lib/tasks/release.rake +19 -4
  105. data/package.json +1 -1
  106. metadata +76 -39
  107. data/brakeman.ignore +0 -28
  108. data/docs/api-examples.md +0 -49
  109. data/docs/guide/claude-code-guide.md +0 -74
  110. data/docs/guide/deep-dive/authorization.md +0 -189
  111. data/docs/guide/deep-dive/multitenancy.md +0 -256
  112. data/docs/guide/deep-dive/resources.md +0 -390
  113. data/docs/guide/getting-started/01-installation.md +0 -165
  114. data/docs/guide/index.md +0 -28
  115. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  116. data/docs/guide/introduction/02-core-concepts.md +0 -440
  117. data/docs/guide/tutorial/01-project-setup.md +0 -75
  118. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  119. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  120. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  121. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  122. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  123. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  124. data/docs/markdown-examples.md +0 -85
  125. data/docs/modules/action.md +0 -244
  126. data/docs/modules/authentication.md +0 -236
  127. data/docs/modules/configuration.md +0 -599
  128. data/docs/modules/controller.md +0 -443
  129. data/docs/modules/core.md +0 -316
  130. data/docs/modules/definition.md +0 -1308
  131. data/docs/modules/display.md +0 -759
  132. data/docs/modules/form.md +0 -495
  133. data/docs/modules/generator.md +0 -400
  134. data/docs/modules/index.md +0 -167
  135. data/docs/modules/interaction.md +0 -642
  136. data/docs/modules/package.md +0 -151
  137. data/docs/modules/policy.md +0 -176
  138. data/docs/modules/portal.md +0 -710
  139. data/docs/modules/query.md +0 -297
  140. data/docs/modules/resource_record.md +0 -618
  141. data/docs/modules/routing.md +0 -690
  142. data/docs/modules/table.md +0 -301
  143. data/docs/modules/ui.md +0 -631
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: definition
3
+ description: Overview of Plutonium resource definitions - structure, inheritance, and best practices
4
+ ---
5
+
6
+ # Definition Overview
7
+
8
+ Resource definitions configure **HOW** resources are rendered and interacted with. They are the central configuration point for UI behavior in Plutonium applications.
9
+
10
+ ## Key Principle
11
+
12
+ **All model attributes are auto-detected** - you only declare when overriding defaults.
13
+
14
+ ## File Location
15
+
16
+ - Main app: `app/definitions/model_name_definition.rb`
17
+ - Packages: `packages/pkg_name/app/definitions/pkg_name/model_name_definition.rb`
18
+
19
+ ## Definition Structure
20
+
21
+ ```ruby
22
+ class PostDefinition < Plutonium::Resource::Definition
23
+ # Fields, inputs, displays, columns (see definition-fields skill)
24
+ field :content, as: :rich_text
25
+ input :title, hint: "Be descriptive"
26
+ display :content, as: :markdown
27
+ column :title, align: :center
28
+
29
+ # Search, filters, scopes, sorting (see definition-query skill)
30
+ search { |scope, q| scope.where("title ILIKE ?", "%#{q}%") }
31
+ filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
32
+ scope :published
33
+ sort :created_at
34
+
35
+ # Actions (see definition-actions skill)
36
+ action :publish, interaction: PublishInteraction
37
+ end
38
+ ```
39
+
40
+ ## Definition Hierarchy
41
+
42
+ Definitions exist at multiple levels:
43
+
44
+ ### Main App (created by generators)
45
+
46
+ ```ruby
47
+ # app/definitions/resource_definition.rb (base - created during install)
48
+ class ResourceDefinition < Plutonium::Resource::Definition
49
+ action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
50
+ end
51
+
52
+ # app/definitions/post_definition.rb (resource-specific - created by scaffold)
53
+ class PostDefinition < ResourceDefinition
54
+ scope :published
55
+ input :content, as: :rich_text
56
+ end
57
+ ```
58
+
59
+ ### Portal-Specific Overrides
60
+
61
+ After connecting a resource to a portal, you can create a portal-specific definition to override defaults for that portal only:
62
+
63
+ ```ruby
64
+ # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
65
+ class AdminPortal::PostDefinition < ::PostDefinition
66
+ # Override or extend for admin portal only
67
+ input :internal_notes, as: :text # Only admins see this field
68
+ scope :pending_review # Admin-specific scope
69
+ end
70
+ ```
71
+
72
+ This lets you:
73
+ - Show different fields per portal
74
+ - Add portal-specific actions
75
+ - Customize search/filters per context
76
+ - Keep main app definition clean
77
+
78
+ ## Separation of Concerns
79
+
80
+ | Layer | Purpose | Example |
81
+ |-------|---------|---------|
82
+ | **Definition** | HOW fields render | `input :content, as: :rich_text` |
83
+ | **Policy** | WHAT is visible/editable | `permitted_attributes_for_read` |
84
+ | **Interaction** | Business logic | `resource.update!(state: :archived)` |
85
+
86
+ ## Auto-Detection
87
+
88
+ Plutonium automatically detects from your model:
89
+ - Database columns (string, text, integer, boolean, datetime, etc.)
90
+ - Associations (belongs_to, has_many, has_one)
91
+ - Active Storage/Active Shrine attachments (has_one_attached, has_many_attached)
92
+ - Enums
93
+ - Virtual attributes (with accessor methods)
94
+
95
+ **Only declare fields when you need to override the auto-detected behavior.**
96
+
97
+ ## When to Declare
98
+
99
+ ```ruby
100
+ class PostDefinition < ResourceDefinition
101
+ # 1. Override auto-detected type
102
+ field :content, as: :rich_text # text -> rich_text
103
+ input :published_at, as: :date # datetime -> date only
104
+
105
+ # 2. Add custom options
106
+ input :title, hint: "Be descriptive", placeholder: "Enter title"
107
+
108
+ # 3. Configure select choices
109
+ input :category, as: :select, choices: %w[Tech Business]
110
+
111
+ # 4. Add conditional logic
112
+ display :published_at, condition: -> { object.published? }
113
+
114
+ # 5. Custom rendering
115
+ display :status do |field|
116
+ StatusBadgeComponent.new(value: field.value)
117
+ end
118
+ end
119
+ ```
120
+
121
+ ## Minimal Definition Example
122
+
123
+ ```ruby
124
+ class PostDefinition < ResourceDefinition
125
+ # No field declarations needed - all auto-detected!
126
+
127
+ # Only customize what you need:
128
+ input :content, as: :rich_text
129
+ display :content, as: :markdown
130
+
131
+ scope :published
132
+ scope :draft
133
+ end
134
+ ```
135
+
136
+ ## Runtime Customization Hooks
137
+
138
+ Override these methods for dynamic behavior:
139
+
140
+ ```ruby
141
+ class PostDefinition < ResourceDefinition
142
+ def customize_fields
143
+ field :debug_info if Rails.env.development?
144
+ end
145
+
146
+ def customize_inputs
147
+ # Add/modify inputs at runtime
148
+ end
149
+
150
+ def customize_displays
151
+ # Add/modify displays at runtime
152
+ end
153
+
154
+ def customize_filters
155
+ # Add/modify filters at runtime
156
+ end
157
+
158
+ def customize_actions
159
+ # Add/modify actions at runtime
160
+ end
161
+ end
162
+ ```
163
+
164
+ ## Context Availability
165
+
166
+ | Context | current_user | object | current_parent |
167
+ |---------|-------------|--------|----------------|
168
+ | Definition file (class level) | No | No | No |
169
+ | `condition` procs | Yes | Yes | Yes |
170
+ | `input` blocks | Yes | Yes | Yes |
171
+ | Page title procs | Yes | Yes (current_record!) | Yes |
172
+
173
+ ## Page Customization
174
+
175
+ ```ruby
176
+ class PostDefinition < ResourceDefinition
177
+ # Titles (static or dynamic)
178
+ index_page_title "All Posts"
179
+ show_page_title -> { "#{current_record!.title} - Details" }
180
+
181
+ # Breadcrumbs
182
+ breadcrumbs true
183
+ show_page_breadcrumbs false
184
+
185
+ # Custom page classes (inherit from parent's nested class)
186
+ class IndexPage < IndexPage
187
+ def view_template(&block)
188
+ div(class: "custom-header") { h1 { "Custom" } }
189
+ super(&block)
190
+ end
191
+ end
192
+
193
+ class Form < Form
194
+ def form_template
195
+ div(class: "grid grid-cols-2") do
196
+ render field(:title).input_tag
197
+ render field(:content).easymde_tag
198
+ end
199
+ render_actions
200
+ end
201
+ end
202
+ end
203
+ ```
204
+
205
+ ## Best Practices
206
+
207
+ 1. **Let auto-detection work** - Don't declare unless overriding
208
+ 2. **Use portal-specific definitions** - Override per-portal when needed
209
+ 3. **Keep definitions focused** - Configuration only, no business logic
210
+ 4. **Use policies for authorization** - Not `condition` procs
211
+ 5. **Group related declarations** - Use comments to organize sections
212
+
213
+ ## Related Skills
214
+
215
+ - `definition-fields` - Fields, inputs, displays, columns
216
+ - `definition-actions` - Actions and interactions
217
+ - `definition-query` - Search, filters, scopes, sorting
218
+ - `views` - Custom page, form, display, and table classes
@@ -0,0 +1,386 @@
1
+ ---
2
+ name: definition-actions
3
+ description: Add custom actions and interactions to Plutonium resources
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
+ **Note:** For custom operations with business logic, use **Interactive Actions** with an Interaction class instead. That's the recommended approach for most custom actions.
41
+
42
+ ## Interactive Actions (with Interaction)
43
+
44
+ ```ruby
45
+ class PostDefinition < ResourceDefinition
46
+ action :publish,
47
+ interaction: PublishInteraction,
48
+ icon: Phlex::TablerIcons::Send
49
+
50
+ action :archive,
51
+ interaction: ArchiveInteraction,
52
+ color: :danger,
53
+ category: :danger,
54
+ position: 1000,
55
+ confirmation: "Are you sure?"
56
+ end
57
+ ```
58
+
59
+ ## Action Options
60
+
61
+ ```ruby
62
+ action :name,
63
+ # Display
64
+ label: "Custom Label", # Button text (default: name.titleize)
65
+ description: "What it does", # Tooltip/description
66
+ icon: Phlex::TablerIcons::Star, # Icon component
67
+ color: :danger, # :primary, :secondary, :danger
68
+
69
+ # Visibility
70
+ resource_action: true, # Show on index page
71
+ record_action: true, # Show on show page
72
+ collection_record_action: true, # Show in table rows
73
+ bulk_action: true, # For selected records
74
+
75
+ # Grouping
76
+ category: :primary, # :primary, :secondary, :danger
77
+ position: 50, # Order (lower = first)
78
+
79
+ # Behavior
80
+ confirmation: "Are you sure?", # Confirmation dialog
81
+ turbo_frame: "_top", # Turbo frame target
82
+ route_options: {action: :foo} # Route configuration
83
+ ```
84
+
85
+ ## Creating an Interaction
86
+
87
+ ### Basic Structure
88
+
89
+ ```ruby
90
+ # app/interactions/resource_interaction.rb (generated during install)
91
+ class ResourceInteraction < Plutonium::Resource::Interaction
92
+ end
93
+
94
+ # app/interactions/archive_interaction.rb
95
+ class ArchiveInteraction < ResourceInteraction
96
+ presents label: "Archive",
97
+ icon: Phlex::TablerIcons::Archive,
98
+ description: "Archive this record"
99
+
100
+ attribute :resource # The record being acted on
101
+
102
+ def execute
103
+ resource.archived!
104
+ succeed(resource).with_message("Record archived successfully.")
105
+ rescue ActiveRecord::RecordInvalid => e
106
+ failed(e.record.errors)
107
+ rescue => error
108
+ failed("Archive failed. Please try again.")
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### With Additional Inputs
114
+
115
+ ```ruby
116
+ # app/interactions/company/invite_user_interaction.rb
117
+ class Company::InviteUserInteraction < Plutonium::Resource::Interaction
118
+ presents label: "Invite User", icon: Phlex::TablerIcons::Mail
119
+
120
+ attribute :resource # The company
121
+ attribute :email
122
+ attribute :role
123
+
124
+ # Configure form inputs
125
+ input :email, as: :email, hint: "User's email address"
126
+ input :role, as: :select, choices: %w[admin member viewer]
127
+
128
+ # Validations
129
+ validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
130
+ validates :role, presence: true, inclusion: {in: %w[admin member viewer]}
131
+
132
+ def execute
133
+ UserInvite.create!(
134
+ company: resource,
135
+ email: email,
136
+ role: role,
137
+ invited_by: current_user
138
+ )
139
+ succeed(resource).with_message("Invitation sent to #{email}.")
140
+ rescue ActiveRecord::RecordInvalid => e
141
+ failed(e.record.errors)
142
+ end
143
+ end
144
+ ```
145
+
146
+ ### Bulk Action (Multiple Records)
147
+
148
+ ```ruby
149
+ class BulkArchiveInteraction < Plutonium::Resource::Interaction
150
+ presents label: "Archive Selected", icon: Phlex::TablerIcons::Archive
151
+
152
+ attribute :resources # Array of records (note: plural)
153
+
154
+ def execute
155
+ count = 0
156
+ resources.each do |record|
157
+ record.archived!
158
+ count += 1
159
+ end
160
+ succeed(resources).with_message("#{count} records archived.")
161
+ rescue => error
162
+ failed("Bulk archive failed: #{error.message}")
163
+ end
164
+ end
165
+ ```
166
+
167
+ ### Resource Action (No Record)
168
+
169
+ ```ruby
170
+ class ImportInteraction < Plutonium::Resource::Interaction
171
+ presents label: "Import CSV", icon: Phlex::TablerIcons::Upload
172
+
173
+ # No :resource or :resources attribute = resource action
174
+ attribute :file
175
+
176
+ input :file, as: :file
177
+
178
+ validates :file, presence: true
179
+
180
+ def execute
181
+ # Import logic...
182
+ succeed(nil).with_message("Import completed.")
183
+ end
184
+ end
185
+ ```
186
+
187
+ ## Interaction Responses
188
+
189
+ ```ruby
190
+ def execute
191
+ # Success with message
192
+ succeed(resource).with_message("Done!")
193
+
194
+ # Success with redirect
195
+ succeed(resource)
196
+ .with_redirect_response(resource_url_for(resource))
197
+ .with_message("Redirecting...")
198
+
199
+ # Failure with field errors
200
+ failed(resource.errors)
201
+
202
+ # Failure with custom message
203
+ failed("Something went wrong")
204
+
205
+ # Failure with specific field
206
+ failed("Invalid value", :email)
207
+ end
208
+ ```
209
+
210
+ ## Interaction Context
211
+
212
+ Inside an interaction:
213
+ - `current_user` - The authenticated user
214
+ - `view_context` - Access to helpers and view methods
215
+
216
+ ```ruby
217
+ def execute
218
+ resource.update!(
219
+ archived_by: current_user,
220
+ archived_at: Time.current
221
+ )
222
+ succeed(resource)
223
+ end
224
+ ```
225
+
226
+ ## Defining in Definition
227
+
228
+ ### Basic
229
+
230
+ ```ruby
231
+ class PostDefinition < ResourceDefinition
232
+ action :publish, interaction: PublishInteraction
233
+ action :archive, interaction: ArchiveInteraction
234
+ end
235
+ ```
236
+
237
+ ### With Overrides
238
+
239
+ ```ruby
240
+ class PostDefinition < ResourceDefinition
241
+ action :archive,
242
+ interaction: ArchiveInteraction,
243
+ collection_record_action: false, # Don't show in table
244
+ color: :danger,
245
+ position: 1000 # Show last
246
+ end
247
+ ```
248
+
249
+ ### Inherited Actions
250
+
251
+ Actions defined in `ResourceDefinition` (created during install) are inherited by all definitions:
252
+
253
+ ```ruby
254
+ # app/definitions/resource_definition.rb (created during install)
255
+ class ResourceDefinition < Plutonium::Resource::Definition
256
+ action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
257
+ end
258
+
259
+ # All definitions inherit the archive action automatically
260
+ class PostDefinition < ResourceDefinition
261
+ end
262
+ ```
263
+
264
+ ### Portal-Specific Actions
265
+
266
+ Override actions for a specific portal:
267
+
268
+ ```ruby
269
+ # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
270
+ class AdminPortal::PostDefinition < ::PostDefinition
271
+ # Add admin-only actions
272
+ action :feature, interaction: FeaturePostInteraction
273
+ action :bulk_publish, interaction: BulkPublishInteraction
274
+ end
275
+ ```
276
+
277
+ ## Default CRUD Actions
278
+
279
+ Plutonium provides these by default:
280
+
281
+ ```ruby
282
+ action :new, resource_action: true, position: 10
283
+ action :show, collection_record_action: true, position: 10
284
+ action :edit, record_action: true, position: 20
285
+ action :destroy, record_action: true, position: 100, category: :danger
286
+ ```
287
+
288
+ ## Authorization
289
+
290
+ Actions are authorized via policies:
291
+
292
+ ```ruby
293
+ # app/policies/post_policy.rb
294
+ class PostPolicy < ResourcePolicy
295
+ def publish?
296
+ user.admin? || record.author == user
297
+ end
298
+
299
+ def archive?
300
+ user.admin?
301
+ end
302
+ end
303
+ ```
304
+
305
+ The action only appears if the policy method returns `true`.
306
+
307
+ ## Immediate vs Form Actions
308
+
309
+ **Immediate** - Executes without showing a form (when interaction has no extra inputs):
310
+
311
+ ```ruby
312
+ class ArchiveInteraction < Plutonium::Resource::Interaction
313
+ attribute :resource # Only resource, no other inputs
314
+ # No input declarations
315
+
316
+ def execute
317
+ resource.archived!
318
+ succeed(resource)
319
+ end
320
+ end
321
+ ```
322
+
323
+ **Form** - Shows a form first (when interaction has additional inputs):
324
+
325
+ ```ruby
326
+ class InviteUserInteraction < Plutonium::Resource::Interaction
327
+ attribute :resource
328
+ attribute :email
329
+ attribute :role
330
+
331
+ input :email
332
+ input :role, as: :select, choices: %w[admin member]
333
+ # Has inputs = shows form first
334
+ end
335
+ ```
336
+
337
+ ## Common Patterns
338
+
339
+ ### Archive/Restore
340
+
341
+ ```ruby
342
+ class ArchiveInteraction < Plutonium::Resource::Interaction
343
+ presents label: "Archive", icon: Phlex::TablerIcons::Archive
344
+ attribute :resource
345
+
346
+ def execute
347
+ resource.archived!
348
+ succeed(resource).with_message("Archived.")
349
+ end
350
+ end
351
+
352
+ class RestoreInteraction < Plutonium::Resource::Interaction
353
+ presents label: "Restore", icon: Phlex::TablerIcons::Refresh
354
+ attribute :resource
355
+
356
+ def execute
357
+ resource.active!
358
+ succeed(resource).with_message("Restored.")
359
+ end
360
+ end
361
+ ```
362
+
363
+ ### Send Notification
364
+
365
+ ```ruby
366
+ class SendReminderInteraction < Plutonium::Resource::Interaction
367
+ presents label: "Send Reminder", icon: Phlex::TablerIcons::Bell
368
+ attribute :resource
369
+ attribute :message
370
+
371
+ input :message, as: :text, hint: "Custom message (optional)"
372
+
373
+ def execute
374
+ ReminderMailer.with(record: resource, message: message).deliver_later
375
+ succeed(resource).with_message("Reminder sent.")
376
+ end
377
+ end
378
+ ```
379
+
380
+ ## Related Skills
381
+
382
+ - `definition` - Overview and structure
383
+ - `definition-fields` - Fields, inputs, displays
384
+ - `definition-query` - Search, filters, scopes
385
+ - `interaction` - Writing interaction classes
386
+ - `policy` - Controlling action access