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,383 +0,0 @@
1
- # Definition Fields
2
-
3
- Complete reference for field configuration in definitions.
4
-
5
- ## Core Methods
6
-
7
- | Method | Applies To | Use When |
8
- |--------|-----------|----------|
9
- | `field` | Forms + Show + Table | Universal type override |
10
- | `input` | Forms only | Form-specific options |
11
- | `display` | Show page only | Display-specific options |
12
- | `column` | Table only | Table-specific options |
13
-
14
- ## Basic Usage
15
-
16
- ```ruby
17
- class PostDefinition < Plutonium::Resource::Definition
18
- # field - changes type everywhere
19
- field :content, as: :markdown
20
-
21
- # input - form-specific
22
- input :title,
23
- label: "Article Title",
24
- hint: "Enter a descriptive title",
25
- placeholder: "e.g. Getting Started"
26
-
27
- # display - show page specific
28
- display :content,
29
- as: :markdown,
30
- description: "Published content",
31
- wrapper: {class: "col-span-full"}
32
-
33
- # column - table specific
34
- column :title, label: "Article", align: :center
35
- column :view_count, align: :end
36
- end
37
- ```
38
-
39
- ## Available Field Types
40
-
41
- ### Input Types (Forms)
42
-
43
- | Category | Types |
44
- |----------|-------|
45
- | **Text** | `:string`, `:text`, `:email`, `:url`, `:tel`, `:password` |
46
- | **Rich Text** | `:markdown` (uses EasyMDE editor) |
47
- | **Numeric** | `:number`, `:integer`, `:decimal`, `:range` |
48
- | **Boolean** | `:boolean` |
49
- | **Date/Time** | `:date`, `:time`, `:datetime` |
50
- | **Selection** | `:select`, `:slim_select`, `:radio_buttons`, `:check_boxes` |
51
- | **Files** | `:file`, `:uppy`, `:attachment` |
52
- | **Associations** | `:association`, `:secure_association`, `:belongs_to`, `:has_many`, `:has_one` |
53
- | **Special** | `:hidden`, `:color`, `:phone` |
54
-
55
- ### Display Types (Show/Index)
56
-
57
- `:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`
58
-
59
- ## Field Options
60
-
61
- ### Common Options
62
-
63
- ```ruby
64
- input :title,
65
- label: "Custom Label", # Custom label text
66
- hint: "Help text for forms", # Form help text
67
- placeholder: "Enter value", # Input placeholder
68
- description: "For displays" # Display description
69
- ```
70
-
71
- ### HTML Attributes
72
-
73
- ```ruby
74
- input :title,
75
- class: "custom-class", # CSS class
76
- data: {controller: "custom"}, # Data attributes
77
- required: true, # HTML required
78
- readonly: true, # HTML readonly
79
- disabled: true # HTML disabled
80
- ```
81
-
82
- ### Wrapper Options
83
-
84
- ```ruby
85
- display :content, wrapper: {class: "col-span-full"}
86
- input :notes, wrapper: {class: "bg-gray-50"}
87
- ```
88
-
89
- ## Select/Choices
90
-
91
- ### Static Choices
92
-
93
- ```ruby
94
- input :category, as: :select, choices: %w[Tech Business Lifestyle]
95
- input :status, as: :select, choices: Post.statuses.keys
96
- ```
97
-
98
- ### Dynamic Choices (requires block)
99
-
100
- ```ruby
101
- # Basic dynamic
102
- input :author do |f|
103
- choices = User.active.pluck(:name, :id)
104
- f.select_tag choices: choices
105
- end
106
-
107
- # With context access
108
- input :team_members do |f|
109
- choices = current_user.organization.users.pluck(:name, :id)
110
- f.select_tag choices: choices
111
- end
112
-
113
- # Based on object state
114
- input :related_posts do |f|
115
- choices = if object.persisted?
116
- Post.where.not(id: object.id).published.pluck(:title, :id)
117
- else
118
- []
119
- end
120
- f.select_tag choices: choices
121
- end
122
- ```
123
-
124
- ## Conditional Rendering
125
-
126
- ```ruby
127
- class PostDefinition < Plutonium::Resource::Definition
128
- # Show based on object state
129
- display :published_at, condition: -> { object.published? }
130
- display :rejection_reason, condition: -> { object.rejected? }
131
-
132
- # Show based on environment
133
- field :debug_info, condition: -> { Rails.env.development? }
134
- end
135
- ```
136
-
137
- **Note:** Use `condition` for UI state logic. Use **policies** for authorization.
138
-
139
- ## Dynamic Forms (pre_submit)
140
-
141
- Use `pre_submit: true` to create forms that dynamically show/hide fields based on other field values.
142
-
143
- ### Basic Pattern
144
-
145
- ```ruby
146
- class PostDefinition < Plutonium::Resource::Definition
147
- # Trigger field - causes form to re-render on change
148
- input :send_notifications, pre_submit: true
149
-
150
- # Dependent field - only shown when condition is true
151
- input :notification_channel,
152
- as: :select,
153
- choices: %w[Email SMS],
154
- condition: -> { object.send_notifications? }
155
- end
156
- ```
157
-
158
- ### How It Works
159
-
160
- 1. User changes a `pre_submit: true` field
161
- 2. Form submits via Turbo (no page reload)
162
- 3. Server re-renders the form with updated `object` state
163
- 4. Fields with `condition` procs are re-evaluated
164
- 5. Newly visible fields appear, hidden fields disappear
165
-
166
- ## Custom Rendering
167
-
168
- ### Block Syntax
169
-
170
- **For Display (can return any component):**
171
-
172
- ```ruby
173
- display :status do |field|
174
- StatusBadgeComponent.new(value: field.value, class: field.dom.css_class)
175
- end
176
-
177
- display :metrics do |field|
178
- if field.value.present?
179
- MetricsChartComponent.new(data: field.value)
180
- else
181
- EmptyStateComponent.new(message: "No metrics")
182
- end
183
- end
184
- ```
185
-
186
- **For Input (must use form builder methods):**
187
-
188
- ```ruby
189
- input :birth_date do |f|
190
- case object.age_category
191
- when 'adult'
192
- f.date_tag(min: 18.years.ago.to_date)
193
- when 'minor'
194
- f.date_tag(max: 18.years.ago.to_date)
195
- else
196
- f.date_tag
197
- end
198
- end
199
- ```
200
-
201
- ### phlexi_tag (Advanced Display)
202
-
203
- ```ruby
204
- # With component class
205
- display :status, as: :phlexi_tag, with: StatusBadgeComponent
206
-
207
- # With inline proc
208
- display :priority, as: :phlexi_tag, with: ->(value, attrs) {
209
- case value
210
- when 'high'
211
- span(class: "badge badge-danger") { "High" }
212
- when 'medium'
213
- span(class: "badge badge-warning") { "Medium" }
214
- else
215
- span(class: "badge badge-info") { "Low" }
216
- end
217
- }
218
- ```
219
-
220
- ### Custom Component Class
221
-
222
- ```ruby
223
- input :color_picker, as: ColorPickerComponent
224
- display :chart, as: ChartComponent
225
- ```
226
-
227
- ## Column Alignment
228
-
229
- ```ruby
230
- column :title, align: :start # Left (default)
231
- column :status, align: :center # Center
232
- column :amount, align: :end # Right
233
- ```
234
-
235
- ## Value Formatting
236
-
237
- Use `formatter` for simple value transformations without a full block:
238
-
239
- ```ruby
240
- # Truncate long text
241
- column :description, formatter: ->(value) { value&.truncate(30) }
242
-
243
- # Format numbers
244
- column :price, formatter: ->(value) { "$%.2f" % value if value }
245
-
246
- # Transform values
247
- column :status, formatter: ->(value) { value&.humanize&.upcase }
248
- ```
249
-
250
- The `formatter` option:
251
- - Receives the field value as its argument
252
- - Returns the transformed value for display
253
- - Works with `column` and `display` declarations
254
- - Is simpler than block syntax when you only need to transform the value
255
-
256
- **formatter vs block:** Use `formatter` when you only need the value. Use a block when you need access to the full record:
257
-
258
- ```ruby
259
- # formatter - receives just the value
260
- column :name, formatter: ->(value) { value&.titleize }
261
-
262
- # block - receives the full record
263
- column :full_name do |record|
264
- "#{record.first_name} #{record.last_name}"
265
- end
266
- ```
267
-
268
- ## Nested Inputs
269
-
270
- Render inline forms for associated records. Requires `accepts_nested_attributes_for` on the model.
271
-
272
- ### Model Setup
273
-
274
- ```ruby
275
- class Post < ResourceRecord
276
- has_many :comments
277
- has_one :metadata
278
-
279
- accepts_nested_attributes_for :comments, allow_destroy: true, limit: 10
280
- accepts_nested_attributes_for :metadata, update_only: true
281
- end
282
- ```
283
-
284
- ### Basic Declaration
285
-
286
- ```ruby
287
- class PostDefinition < Plutonium::Resource::Definition
288
- # Block syntax
289
- nested_input :comments do |n|
290
- n.input :body, as: :text
291
- n.input :author_name
292
- end
293
-
294
- # Using another definition
295
- nested_input :metadata, using: PostMetadataDefinition, fields: %i[seo_title seo_description]
296
- end
297
- ```
298
-
299
- ### Options
300
-
301
- | Option | Description |
302
- |--------|-------------|
303
- | `limit` | Max records (auto-detected from model, default: 10) |
304
- | `allow_destroy` | Show delete checkbox (auto-detected from model) |
305
- | `update_only` | Hide "Add" button, only edit existing |
306
- | `description` | Help text above the section |
307
- | `condition` | Proc to show/hide section |
308
- | `using` | Reference another Definition class |
309
- | `fields` | Which fields to render from the definition |
310
-
311
- ```ruby
312
- nested_input :amenities,
313
- allow_destroy: true,
314
- limit: 20,
315
- description: "Add property amenities" do |n|
316
- n.input :name
317
- n.input :icon, as: :select, choices: ICONS
318
- end
319
- ```
320
-
321
- ## File Uploads
322
-
323
- ```ruby
324
- input :avatar, as: :file
325
- input :avatar, as: :uppy
326
-
327
- input :documents, as: :file, multiple: true
328
- input :documents, as: :uppy,
329
- allowed_file_types: ['.pdf', '.doc'],
330
- max_file_size: 5.megabytes
331
- ```
332
-
333
- ## Common Patterns
334
-
335
- ### Rich Text Content
336
-
337
- ```ruby
338
- field :content, as: :markdown # Form: EasyMDE editor
339
- display :content, as: :markdown # Show: rendered markdown
340
- ```
341
-
342
- ### Money Fields
343
-
344
- ```ruby
345
- input :price, as: :decimal, class: "font-mono"
346
- display :price, class: "font-bold text-green-600"
347
- ```
348
-
349
- ### Status Badges
350
-
351
- ```ruby
352
- display :status do |field|
353
- color = case field.value
354
- when 'active' then 'green'
355
- when 'pending' then 'yellow'
356
- else 'gray'
357
- end
358
- span(class: "badge badge-#{color}") { field.value.humanize }
359
- end
360
- ```
361
-
362
- ### Hidden Fields
363
-
364
- ```ruby
365
- field :author_id, as: :hidden
366
- input :tenant_id, as: :hidden
367
- ```
368
-
369
- ## Context in Blocks
370
-
371
- Inside `condition` procs and `input` blocks:
372
- - `object` - The record being edited/displayed
373
- - `current_user` - The authenticated user
374
- - `current_parent` - Parent record for nested resources
375
- - `request`, `params` - Request information
376
- - All helper methods
377
-
378
- ## Related
379
-
380
- - [Definition Reference](./index)
381
- - [Actions Reference](./actions)
382
- - [Query Reference](./query)
383
- - [Forms Reference](/reference/views/forms)
@@ -1,326 +0,0 @@
1
- # Definition Reference
2
-
3
- Complete reference for resource definitions.
4
-
5
- ## Overview
6
-
7
- Definitions control how resources render - which fields appear in forms, how tables display data, what actions are available.
8
-
9
- ```ruby
10
- class PostDefinition < Plutonium::Resource::Definition
11
- # Field configuration
12
- field :title
13
- field :body, as: :markdown
14
-
15
- # Form-specific
16
- input :title, placeholder: "Enter title"
17
-
18
- # Display-specific
19
- display :body, as: :markdown
20
-
21
- # Table columns
22
- column :title
23
-
24
- # Custom actions
25
- action :publish, interaction: PublishPost
26
-
27
- # Search
28
- search do |scope, query|
29
- scope.where("title ILIKE ?", "%#{query}%")
30
- end
31
-
32
- # Sorting
33
- sort :title
34
- sort :created_at
35
- default_sort :created_at, :desc
36
- end
37
- ```
38
-
39
- ## Definition Files
40
-
41
- ### Location
42
-
43
- ```
44
- app/definitions/post_definition.rb
45
- packages/blogging/app/definitions/blogging/post_definition.rb
46
- ```
47
-
48
- ### Naming Convention
49
-
50
- | Model | Definition |
51
- |-------|------------|
52
- | `Post` | `PostDefinition` |
53
- | `Blogging::Post` | `Blogging::PostDefinition` |
54
-
55
- ### Portal-Specific
56
-
57
- Override for specific portals:
58
-
59
- ```ruby
60
- # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
61
- module AdminPortal
62
- class PostDefinition < ::PostDefinition
63
- # Admin-specific customizations
64
- field :internal_notes
65
- end
66
- end
67
- ```
68
-
69
- ## Auto-Detection
70
-
71
- By default, Plutonium auto-detects fields from the model:
72
-
73
- ```ruby
74
- class PostDefinition < Plutonium::Resource::Definition
75
- # Empty definition = all fields auto-detected
76
- end
77
- ```
78
-
79
- Override selectively:
80
-
81
- ```ruby
82
- class PostDefinition < Plutonium::Resource::Definition
83
- # Override just body field
84
- field :body, as: :markdown
85
-
86
- # Other fields still auto-detected
87
- end
88
- ```
89
-
90
- ## Core Methods
91
-
92
- ### Field Declaration
93
-
94
- | Method | Purpose |
95
- |--------|---------|
96
- | `field` | Universal type/options for forms, displays, tables |
97
- | `input` | Form-specific configuration |
98
- | `display` | Show page configuration |
99
- | `column` | Table configuration |
100
-
101
- ### Query Configuration
102
-
103
- | Method | Purpose |
104
- |--------|---------|
105
- | `search` | Full-text search block |
106
- | `filter` | Sidebar filter inputs |
107
- | `scope` | Quick filter buttons |
108
- | `sort` / `sorts` | Sortable columns |
109
- | `default_sort` | Default sort order |
110
-
111
- ### Actions
112
-
113
- | Method | Purpose |
114
- |--------|---------|
115
- | `action` | Define custom actions |
116
- | `nested_input` | Nested forms for associations |
117
-
118
- ## Page Configuration
119
-
120
- Configure page titles and descriptions:
121
-
122
- ```ruby
123
- class PostDefinition < Plutonium::Resource::Definition
124
- index_page_title "All Posts"
125
- index_page_description "Manage your blog posts"
126
-
127
- new_page_title "Create Post"
128
- new_page_description "Add a new blog post"
129
-
130
- show_page_title { |record| record.title }
131
- show_page_description "View post details"
132
-
133
- edit_page_title { |record| "Edit: #{record.title}" }
134
- edit_page_description "Update post content"
135
- end
136
- ```
137
-
138
- ## Breadcrumbs
139
-
140
- Control breadcrumb display:
141
-
142
- ```ruby
143
- class PostDefinition < Plutonium::Resource::Definition
144
- # Disable breadcrumbs globally
145
- breadcrumbs false
146
-
147
- # Or per-page
148
- index_page_breadcrumbs true
149
- new_page_breadcrumbs true
150
- show_page_breadcrumbs true
151
- edit_page_breadcrumbs true
152
- interactive_action_page_breadcrumbs true
153
- end
154
- ```
155
-
156
- ## Form Configuration
157
-
158
- Control form behavior:
159
-
160
- ```ruby
161
- class PostDefinition < Plutonium::Resource::Definition
162
- # Controls "Save and add another" / "Update and continue editing" buttons
163
- # nil (default) = auto-detect (hidden for singular resources, shown for plural)
164
- # true = always show
165
- # false = always hide
166
- submit_and_continue false
167
-
168
- # How `:new` / `:edit` render. Default is :slideover.
169
- # :slideover — slide-in panel from the right (default)
170
- # :centered — centered modal dialog
171
- # false — full standalone pages (no modal)
172
- modal :centered
173
- end
174
- ```
175
-
176
- Singular resources (e.g., `resource :profile` routes or `has_one` nested) auto-hide the secondary submit button since creating "another" doesn't make sense.
177
-
178
- The `modal` setting only retargets the framework-provided `:new` / `:edit` actions. Custom interactive actions render in their own dialog whose chrome is set on the action via the per-action `modal:` option (`:centered` default, or `:slideover`) — see [Actions](./actions#action-options).
179
-
180
- ## Show Page Metadata Panel
181
-
182
- The `metadata` DSL declares a list of fields that render in a right-side aside on the show page as label/value rows, leaving the main card focused on the record's substance.
183
-
184
- ```ruby
185
- class PostDefinition < Plutonium::Resource::Definition
186
- metadata :author, :state, :created_at, :updated_at
187
- end
188
- ```
189
-
190
- - **Opt-in.** Without a `metadata` call, the show page renders full-width with no aside.
191
- - **Policy-aware.** Fields are intersected with the policy's permitted attributes. The panel auto-hides when nothing is permitted.
192
- - **Deduplicated.** Fields listed in `metadata` are removed from the main card so values aren't shown twice.
193
- - **Responsive.** Side-by-side at `lg+`, stacked single-column below.
194
-
195
- Field formatting (label, `as:`, blocks) is shared with the main card — declare once via `field` / `display` and the metadata panel inherits it.
196
-
197
- ## Index Views (Table & Grid)
198
-
199
- Resources can opt into a card-based **Grid** view alongside the default **Table** view. The user can switch between them via the toolbar; the choice is persisted per-resource via cookie.
200
-
201
- ```ruby
202
- class UserDefinition < Plutonium::Resource::Definition
203
- views :table, :grid # enable both
204
- default_view :grid # initial view if no cookie
205
-
206
- grid_fields(
207
- image: :avatar, # ActiveStorage attachment, Shrine, or URL string
208
- header: :name, # falls back to record.to_label
209
- subheader: :email,
210
- body: :bio,
211
- meta: [:role, :status], # rendered as small pills
212
- footer: :last_seen_at # falls back to :created_at
213
- )
214
-
215
- grid_layout :media # :compact (default) or :media
216
- grid_columns 3 # pin to 3 cols on lg+; default is 1/2/3/4 responsive
217
- end
218
- ```
219
-
220
- | Method | Purpose |
221
- |--------|---------|
222
- | `views :table, :grid` | Which views are available. Default `[:table]`. |
223
- | `default_view :grid` | Initial view when no cookie. Falls back to first declared view. |
224
- | `grid_fields(...)` | Maps card slots to fields. **Implicitly enables `:grid`** if not already declared. |
225
- | `grid_layout :media` | `:compact` (image left of content) or `:media` (full-width image on top). |
226
- | `grid_columns 3` | Override responsive column count on `lg+`. Default is 1 / 2 / 3 / 4 at sm/md/lg/xl. |
227
-
228
- Grid slots — `:image`, `:header`, `:subheader`, `:body`, `:meta`, `:footer` — are all optional. `:meta` accepts an array; the rest are single fields. Slots that point at fields not permitted by the user's policy collapse silently.
229
-
230
- ## Custom Page Classes
231
-
232
- Override default page components:
233
-
234
- ```ruby
235
- class PostDefinition < Plutonium::Resource::Definition
236
- # Custom page classes
237
- class IndexPage < Plutonium::UI::Page::Index
238
- def render_header
239
- # Custom header
240
- end
241
- end
242
-
243
- class Form < Plutonium::UI::Form::Resource
244
- # Custom form behavior
245
- end
246
-
247
- class Table < Plutonium::UI::Table::Resource
248
- # Custom table behavior
249
- end
250
-
251
- class Display < Plutonium::UI::Display::Resource
252
- # Custom display behavior
253
- end
254
- end
255
- ```
256
-
257
- ## Inheritance
258
-
259
- Definitions inherit from each other:
260
-
261
- ```ruby
262
- # Base definition
263
- class PostDefinition < Plutonium::Resource::Definition
264
- field :title
265
- field :body
266
- end
267
-
268
- # Extended definition
269
- class AdminPortal::PostDefinition < ::PostDefinition
270
- field :internal_notes
271
- field :moderation_status
272
- end
273
- ```
274
-
275
- ## Customization Hooks
276
-
277
- Override customization methods for dynamic configuration:
278
-
279
- ```ruby
280
- class PostDefinition < Plutonium::Resource::Definition
281
- def customize_fields
282
- field :dynamic_field if some_condition?
283
- end
284
-
285
- def customize_inputs
286
- input :special_input, as: :text
287
- end
288
-
289
- def customize_displays
290
- display :computed_value
291
- end
292
-
293
- def customize_columns
294
- column :extra_column
295
- end
296
-
297
- def customize_filters
298
- filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
299
- end
300
-
301
- def customize_scopes
302
- scope :active
303
- end
304
-
305
- def customize_sorts
306
- sort :custom_field
307
- end
308
-
309
- def customize_actions
310
- action :dynamic_action, interaction: SomeInteraction
311
- end
312
- end
313
- ```
314
-
315
- ## Sections
316
-
317
- - [Fields](./fields) - Form and display field configuration
318
- - [Actions](./actions) - Custom actions and buttons
319
- - [Query](./query) - Search, filters, scopes, and sorting
320
-
321
- ## Related
322
-
323
- - [Fields](./fields) - Field configuration
324
- - [Actions](./actions) - Custom actions
325
- - [Query](./query) - Search, filters, scopes
326
- - [Views Reference](/reference/views/)