plutonium 0.44.1 → 0.45.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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/skill.md +88 -11
- data/.claude/skills/plutonium-assets/SKILL.md +1 -1
- data/.claude/skills/plutonium-controller/SKILL.md +6 -2
- data/.claude/skills/plutonium-create-resource/SKILL.md +1 -1
- data/.claude/skills/plutonium-definition/SKILL.md +445 -53
- data/.claude/skills/plutonium-definition-actions/SKILL.md +2 -2
- data/.claude/skills/plutonium-definition-query/SKILL.md +2 -2
- data/.claude/skills/plutonium-forms/SKILL.md +6 -2
- data/.claude/skills/plutonium-installation/SKILL.md +3 -3
- data/.claude/skills/plutonium-interaction/SKILL.md +3 -3
- data/.claude/skills/plutonium-invites/SKILL.md +1 -1
- data/.claude/skills/plutonium-model/SKILL.md +228 -55
- data/.claude/skills/plutonium-nested-resources/SKILL.md +9 -2
- data/.claude/skills/plutonium-package/SKILL.md +3 -3
- data/.claude/skills/plutonium-policy/SKILL.md +6 -2
- data/.claude/skills/plutonium-portal/SKILL.md +97 -59
- data/.claude/skills/plutonium-profile/SKILL.md +2 -2
- data/.claude/skills/plutonium-rodauth/SKILL.md +1 -1
- data/.claude/skills/plutonium-theming/SKILL.md +1 -1
- data/.claude/skills/plutonium-views/SKILL.md +2 -2
- data/CHANGELOG.md +15 -0
- data/app/assets/plutonium.css +1 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/invites/install_generator.rb +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/show.html.erb.tt +7 -7
- data/lib/generators/pu/saas/portal_generator.rb +17 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +21 -0
- data/lib/plutonium/engine.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/css/tokens.css +2 -0
- metadata +2 -6
- data/.claude/skills/plutonium-connect-resource/SKILL.md +0 -130
- data/.claude/skills/plutonium-definition-fields/SKILL.md +0 -535
- data/.claude/skills/plutonium-model-features/SKILL.md +0 -286
- data/.claude/skills/plutonium-resource/SKILL.md +0 -281
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-definition
|
|
3
|
-
description:
|
|
3
|
+
description: Use when configuring resource definitions - field types, inputs, displays, columns, conditional rendering, nested inputs, or definition structure
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Plutonium Resource Definitions
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Definitions are generated automatically** - never create them manually:
|
|
9
|
+
- `rails g pu:res:scaffold` creates the base definition
|
|
10
|
+
- `rails g pu:res:conn` creates portal-specific definitions
|
|
11
|
+
- `rails g pu:field:input NAME` creates custom field input components
|
|
12
|
+
- `rails g pu:field:renderer NAME` creates custom field display components
|
|
13
|
+
|
|
14
|
+
Resource definitions configure **HOW** resources are rendered and interacted with. They are the central configuration point for UI behavior.
|
|
9
15
|
|
|
10
16
|
## Key Principle
|
|
11
17
|
|
|
12
18
|
**All model attributes are auto-detected** - you only declare when overriding defaults.
|
|
13
19
|
|
|
20
|
+
Plutonium automatically detects from your model:
|
|
21
|
+
- Database columns (string, text, integer, boolean, datetime, etc.)
|
|
22
|
+
- Associations (belongs_to, has_many, has_one)
|
|
23
|
+
- Active Storage attachments (has_one_attached, has_many_attached)
|
|
24
|
+
- Enums
|
|
25
|
+
- Virtual attributes (with accessor methods)
|
|
26
|
+
|
|
14
27
|
## File Location
|
|
15
28
|
|
|
16
29
|
- Main app: `app/definitions/model_name_definition.rb`
|
|
@@ -20,7 +33,7 @@ Resource definitions configure **HOW** resources are rendered and interacted wit
|
|
|
20
33
|
|
|
21
34
|
```ruby
|
|
22
35
|
class PostDefinition < Plutonium::Resource::Definition
|
|
23
|
-
# Fields, inputs, displays, columns
|
|
36
|
+
# Fields, inputs, displays, columns
|
|
24
37
|
field :content, as: :markdown
|
|
25
38
|
input :title, hint: "Be descriptive"
|
|
26
39
|
display :content, as: :markdown
|
|
@@ -58,23 +71,14 @@ end
|
|
|
58
71
|
|
|
59
72
|
### Portal-Specific Overrides
|
|
60
73
|
|
|
61
|
-
After connecting a resource to a portal, you can create a portal-specific definition to override defaults for that portal only:
|
|
62
|
-
|
|
63
74
|
```ruby
|
|
64
75
|
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
65
76
|
class AdminPortal::PostDefinition < ::PostDefinition
|
|
66
|
-
# Override or extend for admin portal only
|
|
67
77
|
input :internal_notes, as: :text # Only admins see this field
|
|
68
78
|
scope :pending_review # Admin-specific scope
|
|
69
79
|
end
|
|
70
80
|
```
|
|
71
81
|
|
|
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
82
|
## Separation of Concerns
|
|
79
83
|
|
|
80
84
|
| Layer | Purpose | Example |
|
|
@@ -83,56 +87,422 @@ This lets you:
|
|
|
83
87
|
| **Policy** | WHAT is visible/editable | `permitted_attributes_for_read` |
|
|
84
88
|
| **Interaction** | Business logic | `resource.update!(state: :archived)` |
|
|
85
89
|
|
|
86
|
-
##
|
|
90
|
+
## Core Methods
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
**Only declare fields when you need to override the auto-detected behavior.**
|
|
92
|
+
| Method | Applies To | Use When |
|
|
93
|
+
|--------|-----------|----------|
|
|
94
|
+
| `field` | Forms + Show + Table | Universal type override |
|
|
95
|
+
| `input` | Forms only | Form-specific options |
|
|
96
|
+
| `display` | Show page only | Display-specific options |
|
|
97
|
+
| `column` | Table only | Table-specific options |
|
|
96
98
|
|
|
97
|
-
##
|
|
99
|
+
## Basic Usage
|
|
98
100
|
|
|
99
101
|
```ruby
|
|
100
102
|
class PostDefinition < ResourceDefinition
|
|
101
|
-
#
|
|
102
|
-
field :content, as: :markdown
|
|
103
|
-
input :published_at, as: :date # datetime -> date only
|
|
103
|
+
# field - changes type everywhere
|
|
104
|
+
field :content, as: :markdown
|
|
104
105
|
|
|
105
|
-
#
|
|
106
|
-
input :title,
|
|
106
|
+
# input - form-specific
|
|
107
|
+
input :title,
|
|
108
|
+
label: "Article Title",
|
|
109
|
+
hint: "Enter a descriptive title",
|
|
110
|
+
placeholder: "e.g. Getting Started"
|
|
111
|
+
|
|
112
|
+
# display - show page specific
|
|
113
|
+
display :content,
|
|
114
|
+
as: :markdown,
|
|
115
|
+
description: "Published content",
|
|
116
|
+
wrapper: {class: "col-span-full"}
|
|
117
|
+
|
|
118
|
+
# column - table specific
|
|
119
|
+
column :title, label: "Article", align: :center
|
|
120
|
+
column :view_count, align: :end
|
|
121
|
+
end
|
|
122
|
+
```
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
input :category, as: :select, choices: %w[Tech Business]
|
|
124
|
+
## Available Field Types
|
|
110
125
|
|
|
111
|
-
|
|
126
|
+
### Input Types (Forms)
|
|
127
|
+
|
|
128
|
+
| Category | Types |
|
|
129
|
+
|----------|-------|
|
|
130
|
+
| **Text** | `:string`, `:text`, `:email`, `:url`, `:tel`, `:password` |
|
|
131
|
+
| **Rich Text** | `:markdown` (EasyMDE editor) |
|
|
132
|
+
| **Numeric** | `:number`, `:integer`, `:decimal`, `:range` |
|
|
133
|
+
| **Boolean** | `:boolean` |
|
|
134
|
+
| **Date/Time** | `:date`, `:time`, `:datetime` |
|
|
135
|
+
| **Selection** | `:select`, `:slim_select`, `:radio_buttons`, `:check_boxes` |
|
|
136
|
+
| **Files** | `:file`, `:uppy`, `:attachment` |
|
|
137
|
+
| **Associations** | `:association`, `:secure_association`, `:belongs_to`, `:has_many`, `:has_one` |
|
|
138
|
+
| **Special** | `:hidden`, `:color`, `:phone` |
|
|
139
|
+
|
|
140
|
+
### Display Types (Show/Index)
|
|
141
|
+
|
|
142
|
+
`:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`
|
|
143
|
+
|
|
144
|
+
## Field Options
|
|
145
|
+
|
|
146
|
+
### Field-Level Options (wrapper)
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
input :title,
|
|
150
|
+
label: "Custom Label", # Custom label text
|
|
151
|
+
hint: "Help text for forms", # Form help text
|
|
152
|
+
placeholder: "Enter value", # Input placeholder
|
|
153
|
+
description: "For displays" # Display description
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Tag-Level Options (HTML element)
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
input :title,
|
|
160
|
+
class: "custom-class", # CSS class
|
|
161
|
+
data: {controller: "custom"}, # Data attributes
|
|
162
|
+
required: true, # HTML required
|
|
163
|
+
readonly: true, # HTML readonly
|
|
164
|
+
disabled: true # HTML disabled
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Wrapper Options
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
display :content, wrapper: {class: "col-span-full"}
|
|
171
|
+
input :notes, wrapper: {class: "bg-gray-50"}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Select/Choices
|
|
175
|
+
|
|
176
|
+
### Static Choices
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
input :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
180
|
+
input :status, as: :select, choices: Post.statuses.keys
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Dynamic Choices (requires block)
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
# Basic dynamic
|
|
187
|
+
input :author do |f|
|
|
188
|
+
choices = User.active.pluck(:name, :id)
|
|
189
|
+
f.select_tag choices: choices
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# With context access
|
|
193
|
+
input :team_members do |f|
|
|
194
|
+
choices = current_user.organization.users.pluck(:name, :id)
|
|
195
|
+
f.select_tag choices: choices
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Based on object state
|
|
199
|
+
input :related_posts do |f|
|
|
200
|
+
choices = if object.persisted?
|
|
201
|
+
Post.where.not(id: object.id).published.pluck(:title, :id)
|
|
202
|
+
else
|
|
203
|
+
[]
|
|
204
|
+
end
|
|
205
|
+
f.select_tag choices: choices
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Conditional Rendering
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class PostDefinition < ResourceDefinition
|
|
213
|
+
# Show based on object state
|
|
112
214
|
display :published_at, condition: -> { object.published? }
|
|
215
|
+
display :rejection_reason, condition: -> { object.rejected? }
|
|
113
216
|
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
217
|
+
# Show based on environment
|
|
218
|
+
field :debug_info, condition: -> { Rails.env.development? }
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Note:** Use `condition` for UI state logic. Use **policies** for authorization.
|
|
223
|
+
|
|
224
|
+
## Dynamic Forms (pre_submit)
|
|
225
|
+
|
|
226
|
+
Use `pre_submit: true` to create forms that dynamically show/hide fields based on other field values. When a `pre_submit` field changes, the form re-renders server-side and conditions are re-evaluated.
|
|
227
|
+
|
|
228
|
+
### Basic Pattern
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
class PostDefinition < ResourceDefinition
|
|
232
|
+
# Trigger field - causes form to re-render on change
|
|
233
|
+
input :send_notifications, pre_submit: true
|
|
234
|
+
|
|
235
|
+
# Dependent field - only shown when condition is true
|
|
236
|
+
input :notification_channel,
|
|
237
|
+
as: :select,
|
|
238
|
+
choices: %w[Email SMS],
|
|
239
|
+
condition: -> { object.send_notifications? }
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### How It Works
|
|
244
|
+
|
|
245
|
+
1. User changes a `pre_submit: true` field
|
|
246
|
+
2. Form submits via Turbo (no page reload)
|
|
247
|
+
3. Server re-renders the form with updated `object` state
|
|
248
|
+
4. Fields with `condition` procs are re-evaluated
|
|
249
|
+
5. Newly visible fields appear, hidden fields disappear
|
|
250
|
+
|
|
251
|
+
### Multiple Dependent Fields
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
class QuestionDefinition < ResourceDefinition
|
|
255
|
+
# Primary selector
|
|
256
|
+
input :question_type, as: :select,
|
|
257
|
+
choices: %w[text choice scale date boolean],
|
|
258
|
+
pre_submit: true
|
|
259
|
+
|
|
260
|
+
# Conditional fields based on question_type
|
|
261
|
+
input :max_length,
|
|
262
|
+
as: :integer,
|
|
263
|
+
condition: -> { object.question_type == "text" }
|
|
264
|
+
|
|
265
|
+
input :choices,
|
|
266
|
+
as: :text,
|
|
267
|
+
hint: "One choice per line",
|
|
268
|
+
condition: -> { object.question_type == "choice" }
|
|
269
|
+
|
|
270
|
+
input :min_value,
|
|
271
|
+
as: :integer,
|
|
272
|
+
condition: -> { object.question_type == "scale" }
|
|
273
|
+
|
|
274
|
+
input :max_value,
|
|
275
|
+
as: :integer,
|
|
276
|
+
condition: -> { object.question_type == "scale" }
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Cascading Dependencies
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
class PropertyDefinition < ResourceDefinition
|
|
284
|
+
input :property_type, as: :select,
|
|
285
|
+
choices: %w[residential commercial],
|
|
286
|
+
pre_submit: true
|
|
287
|
+
|
|
288
|
+
input :residential_type, as: :select,
|
|
289
|
+
choices: %w[apartment house condo],
|
|
290
|
+
condition: -> { object.property_type == "residential" },
|
|
291
|
+
pre_submit: true
|
|
292
|
+
|
|
293
|
+
input :commercial_type, as: :select,
|
|
294
|
+
choices: %w[office retail warehouse],
|
|
295
|
+
condition: -> { object.property_type == "commercial" },
|
|
296
|
+
pre_submit: true
|
|
297
|
+
|
|
298
|
+
input :apartment_floor,
|
|
299
|
+
as: :integer,
|
|
300
|
+
condition: -> { object.residential_type == "apartment" }
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Dynamic Choices with pre_submit
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
class SurveyResponseDefinition < ResourceDefinition
|
|
308
|
+
input :category, as: :select,
|
|
309
|
+
choices: Category.pluck(:name, :id),
|
|
310
|
+
pre_submit: true
|
|
311
|
+
|
|
312
|
+
input :subcategory do |f|
|
|
313
|
+
choices = if object.category.present?
|
|
314
|
+
Category.find(object.category).subcategories.pluck(:name, :id)
|
|
315
|
+
else
|
|
316
|
+
[]
|
|
317
|
+
end
|
|
318
|
+
f.select_tag choices: choices
|
|
117
319
|
end
|
|
118
320
|
end
|
|
119
321
|
```
|
|
120
322
|
|
|
121
|
-
|
|
323
|
+
### Tips
|
|
324
|
+
|
|
325
|
+
- Only add `pre_submit: true` to fields that control visibility of other fields
|
|
326
|
+
- Keep dependencies simple - deeply nested conditions are hard to debug
|
|
327
|
+
- The form submits on change, so avoid `pre_submit` on frequently-changed fields
|
|
328
|
+
|
|
329
|
+
## Custom Rendering
|
|
330
|
+
|
|
331
|
+
### Block Syntax
|
|
332
|
+
|
|
333
|
+
**For Display (can return any component):**
|
|
334
|
+
```ruby
|
|
335
|
+
display :status do |field|
|
|
336
|
+
StatusBadgeComponent.new(value: field.value, class: field.dom.css_class)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
display :metrics do |field|
|
|
340
|
+
if field.value.present?
|
|
341
|
+
MetricsChartComponent.new(data: field.value)
|
|
342
|
+
else
|
|
343
|
+
EmptyStateComponent.new(message: "No metrics")
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**For Input (must use form builder methods):**
|
|
349
|
+
```ruby
|
|
350
|
+
input :birth_date do |f|
|
|
351
|
+
case object.age_category
|
|
352
|
+
when 'adult'
|
|
353
|
+
f.date_tag(min: 18.years.ago.to_date)
|
|
354
|
+
when 'minor'
|
|
355
|
+
f.date_tag(max: 18.years.ago.to_date)
|
|
356
|
+
else
|
|
357
|
+
f.date_tag
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### phlexi_tag (Advanced Display)
|
|
363
|
+
|
|
364
|
+
```ruby
|
|
365
|
+
# With component class
|
|
366
|
+
display :status, as: :phlexi_tag, with: StatusBadgeComponent
|
|
367
|
+
|
|
368
|
+
# With inline proc
|
|
369
|
+
display :priority, as: :phlexi_tag, with: ->(value, attrs) {
|
|
370
|
+
case value
|
|
371
|
+
when 'high'
|
|
372
|
+
span(class: "badge badge-danger") { "High" }
|
|
373
|
+
when 'medium'
|
|
374
|
+
span(class: "badge badge-warning") { "Medium" }
|
|
375
|
+
else
|
|
376
|
+
span(class: "badge badge-info") { "Low" }
|
|
377
|
+
end
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Custom Component Class
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
input :color_picker, as: ColorPickerComponent
|
|
385
|
+
display :chart, as: ChartComponent
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Column Options
|
|
389
|
+
|
|
390
|
+
### Alignment
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
column :title, align: :start # Left (default)
|
|
394
|
+
column :status, align: :center # Center
|
|
395
|
+
column :amount, align: :end # Right
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Value Formatting
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
# Truncate long text
|
|
402
|
+
column :description, formatter: ->(value) { value&.truncate(30) }
|
|
403
|
+
|
|
404
|
+
# Format numbers
|
|
405
|
+
column :price, formatter: ->(value) { "$%.2f" % value if value }
|
|
406
|
+
|
|
407
|
+
# Transform values
|
|
408
|
+
column :status, formatter: ->(value) { value&.humanize&.upcase }
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**formatter vs block:** Use `formatter` when you only need the value. Use a block when you need the full record:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
# formatter - receives just the value
|
|
415
|
+
column :name, formatter: ->(value) { value&.titleize }
|
|
416
|
+
|
|
417
|
+
# block - receives the full record
|
|
418
|
+
column :full_name do |record|
|
|
419
|
+
"#{record.first_name} #{record.last_name}"
|
|
420
|
+
end
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Nested Inputs
|
|
424
|
+
|
|
425
|
+
Render inline forms for associated records. Requires `accepts_nested_attributes_for` on the model.
|
|
426
|
+
|
|
427
|
+
### Model Setup
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
class Post < ResourceRecord
|
|
431
|
+
has_many :comments
|
|
432
|
+
has_one :metadata
|
|
433
|
+
|
|
434
|
+
accepts_nested_attributes_for :comments, allow_destroy: true, limit: 10
|
|
435
|
+
accepts_nested_attributes_for :metadata, update_only: true
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Basic Declaration
|
|
122
440
|
|
|
123
441
|
```ruby
|
|
124
442
|
class PostDefinition < ResourceDefinition
|
|
125
|
-
#
|
|
443
|
+
# Block syntax
|
|
444
|
+
nested_input :comments do |n|
|
|
445
|
+
n.input :body, as: :text
|
|
446
|
+
n.input :author_name
|
|
447
|
+
end
|
|
126
448
|
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
449
|
+
# Using another definition
|
|
450
|
+
nested_input :metadata, using: PostMetadataDefinition, fields: %i[seo_title seo_description]
|
|
451
|
+
end
|
|
452
|
+
```
|
|
130
453
|
|
|
131
|
-
|
|
132
|
-
|
|
454
|
+
### Options
|
|
455
|
+
|
|
456
|
+
| Option | Description |
|
|
457
|
+
|--------|-------------|
|
|
458
|
+
| `limit` | Max records (auto-detected from model, default: 10) |
|
|
459
|
+
| `allow_destroy` | Show delete checkbox (auto-detected from model) |
|
|
460
|
+
| `update_only` | Hide "Add" button, only edit existing |
|
|
461
|
+
| `description` | Help text above the section |
|
|
462
|
+
| `condition` | Proc to show/hide section |
|
|
463
|
+
| `using` | Reference another Definition class |
|
|
464
|
+
| `fields` | Which fields to render from the definition |
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
nested_input :amenities,
|
|
468
|
+
allow_destroy: true,
|
|
469
|
+
limit: 20,
|
|
470
|
+
description: "Add property amenities" do |n|
|
|
471
|
+
n.input :name
|
|
472
|
+
n.input :icon, as: :select, choices: ICONS
|
|
473
|
+
end
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Singular Associations
|
|
477
|
+
|
|
478
|
+
For `has_one` and `belongs_to`, limit is automatically 1:
|
|
479
|
+
|
|
480
|
+
```ruby
|
|
481
|
+
nested_input :profile do |n| # has_one
|
|
482
|
+
n.input :bio
|
|
483
|
+
n.input :website
|
|
133
484
|
end
|
|
134
485
|
```
|
|
135
486
|
|
|
487
|
+
### Gotchas
|
|
488
|
+
|
|
489
|
+
- Model must have `accepts_nested_attributes_for`
|
|
490
|
+
- For custom class names, use `class_name:` in both model and `using:` in definition
|
|
491
|
+
- `update_only: true` hides the Add button
|
|
492
|
+
- Limit is enforced in UI (Add button hidden when reached)
|
|
493
|
+
|
|
494
|
+
## File Uploads
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
input :avatar, as: :file
|
|
498
|
+
input :avatar, as: :uppy
|
|
499
|
+
|
|
500
|
+
input :documents, as: :file, multiple: true
|
|
501
|
+
input :documents, as: :uppy,
|
|
502
|
+
allowed_file_types: ['.pdf', '.doc'],
|
|
503
|
+
max_file_size: 5.megabytes
|
|
504
|
+
```
|
|
505
|
+
|
|
136
506
|
## Runtime Customization Hooks
|
|
137
507
|
|
|
138
508
|
Override these methods for dynamic behavior:
|
|
@@ -161,15 +531,6 @@ class PostDefinition < ResourceDefinition
|
|
|
161
531
|
end
|
|
162
532
|
```
|
|
163
533
|
|
|
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
534
|
## Form Configuration
|
|
174
535
|
|
|
175
536
|
```ruby
|
|
@@ -182,8 +543,6 @@ class PostDefinition < ResourceDefinition
|
|
|
182
543
|
end
|
|
183
544
|
```
|
|
184
545
|
|
|
185
|
-
Singular resources (e.g., `resource :profile` routes or `has_one` nested) auto-hide this button since creating "another" doesn't make sense.
|
|
186
|
-
|
|
187
546
|
## Page Customization
|
|
188
547
|
|
|
189
548
|
```ruby
|
|
@@ -216,6 +575,39 @@ class PostDefinition < ResourceDefinition
|
|
|
216
575
|
end
|
|
217
576
|
```
|
|
218
577
|
|
|
578
|
+
## Context in Blocks
|
|
579
|
+
|
|
580
|
+
Inside `condition` procs and `input` blocks:
|
|
581
|
+
- `object` - The record being edited/displayed
|
|
582
|
+
- `current_user` - The authenticated user
|
|
583
|
+
- `current_parent` - Parent record for nested resources
|
|
584
|
+
- `request`, `params` - Request information
|
|
585
|
+
- All helper methods
|
|
586
|
+
|
|
587
|
+
## When to Declare
|
|
588
|
+
|
|
589
|
+
```ruby
|
|
590
|
+
class PostDefinition < ResourceDefinition
|
|
591
|
+
# 1. Override auto-detected type
|
|
592
|
+
field :content, as: :markdown # text -> rich_text
|
|
593
|
+
input :published_at, as: :date # datetime -> date only
|
|
594
|
+
|
|
595
|
+
# 2. Add custom options
|
|
596
|
+
input :title, hint: "Be descriptive", placeholder: "Enter title"
|
|
597
|
+
|
|
598
|
+
# 3. Configure select choices
|
|
599
|
+
input :category, as: :select, choices: %w[Tech Business]
|
|
600
|
+
|
|
601
|
+
# 4. Add conditional logic
|
|
602
|
+
display :published_at, condition: -> { object.published? }
|
|
603
|
+
|
|
604
|
+
# 5. Custom rendering
|
|
605
|
+
display :status do |field|
|
|
606
|
+
StatusBadgeComponent.new(value: field.value)
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
```
|
|
610
|
+
|
|
219
611
|
## Best Practices
|
|
220
612
|
|
|
221
613
|
1. **Let auto-detection work** - Don't declare unless overriding
|
|
@@ -226,7 +618,7 @@ end
|
|
|
226
618
|
|
|
227
619
|
## Related Skills
|
|
228
620
|
|
|
229
|
-
- `plutonium-definition-fields` - Fields, inputs, displays, columns
|
|
230
621
|
- `plutonium-definition-actions` - Actions and interactions
|
|
231
622
|
- `plutonium-definition-query` - Search, filters, scopes, sorting
|
|
232
623
|
- `plutonium-views` - Custom page, form, display, and table classes
|
|
624
|
+
- `plutonium-forms` - Custom form templates and field builders
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-definition-actions
|
|
3
|
-
description:
|
|
3
|
+
description: Use when adding custom actions, bulk operations, or wiring interactions into Plutonium resource definitions
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Definition Actions
|
|
@@ -418,7 +418,7 @@ end
|
|
|
418
418
|
## Related Skills
|
|
419
419
|
|
|
420
420
|
- `plutonium-definition` - Overview and structure
|
|
421
|
-
- `plutonium-definition
|
|
421
|
+
- `plutonium-definition` - Fields, inputs, displays
|
|
422
422
|
- `plutonium-definition-query` - Search, filters, scopes
|
|
423
423
|
- `plutonium-interaction` - Writing interaction classes
|
|
424
424
|
- `plutonium-policy` - Controlling action access
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-definition-query
|
|
3
|
-
description:
|
|
3
|
+
description: Use when adding search, filters, named scopes, or sorting to a Plutonium resource definition
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Definition Query
|
|
@@ -360,5 +360,5 @@ end
|
|
|
360
360
|
## Related Skills
|
|
361
361
|
|
|
362
362
|
- `plutonium-definition` - Overview and structure
|
|
363
|
-
- `plutonium-definition
|
|
363
|
+
- `plutonium-definition` - Fields, inputs, displays
|
|
364
364
|
- `plutonium-definition-actions` - Actions and interactions
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-forms
|
|
3
|
-
description:
|
|
3
|
+
description: Use when building custom form templates, overriding field builders, or theming form components in Plutonium
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Forms
|
|
7
7
|
|
|
8
|
+
**Use generators for custom field types:**
|
|
9
|
+
- `rails g pu:field:input NAME` creates a custom form input component
|
|
10
|
+
- `rails g pu:field:renderer NAME` creates a custom display renderer
|
|
11
|
+
|
|
8
12
|
Plutonium forms are built on [Phlexi::Form](https://github.com/radioactive-labs/phlexi-form), providing a Ruby-first approach to form building with Phlex components.
|
|
9
13
|
|
|
10
14
|
## Form Class Hierarchy
|
|
@@ -447,7 +451,7 @@ end
|
|
|
447
451
|
|
|
448
452
|
## Related Skills
|
|
449
453
|
|
|
450
|
-
- `plutonium-definition
|
|
454
|
+
- `plutonium-definition` - Input configuration (as:, hint:, condition:)
|
|
451
455
|
- `plutonium-views` - Custom page classes
|
|
452
456
|
- `plutonium-assets` - TailwindCSS and component theming
|
|
453
457
|
- `plutonium-interaction` - Interactive action forms
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-installation
|
|
3
|
-
description:
|
|
3
|
+
description: Use when installing Plutonium in a new or existing Rails app - generators, configuration, and initial setup
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Installation
|
|
@@ -292,11 +292,11 @@ For models that already exist in your app:
|
|
|
292
292
|
|
|
293
293
|
## Related Skills
|
|
294
294
|
|
|
295
|
-
- `plutonium
|
|
295
|
+
- `plutonium` - Resource architecture overview
|
|
296
296
|
- `plutonium-rodauth` - Authentication setup and configuration
|
|
297
297
|
- `plutonium-package` - Feature and portal packages
|
|
298
298
|
- `plutonium-portal` - Portal configuration
|
|
299
299
|
- `plutonium-views` - Custom pages, layouts, and Phlex components
|
|
300
300
|
- `plutonium-assets` - TailwindCSS and custom styling
|
|
301
301
|
- `plutonium-create-resource` - Resource scaffold options
|
|
302
|
-
- `plutonium-
|
|
302
|
+
- `plutonium-portal` - Portal connection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-interaction
|
|
3
|
-
description:
|
|
3
|
+
description: Use when writing interaction classes for custom business logic, multi-step operations, or actions beyond basic CRUD
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Interactions
|
|
@@ -68,7 +68,7 @@ input :content, as: :text
|
|
|
68
68
|
input :date, as: :date
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
See `plutonium-definition
|
|
71
|
+
See `plutonium-definition` skill for all input types and options.
|
|
72
72
|
|
|
73
73
|
## Presentation
|
|
74
74
|
|
|
@@ -380,4 +380,4 @@ end
|
|
|
380
380
|
- `plutonium-definition-actions` - Declaring actions in definitions
|
|
381
381
|
- `plutonium-forms` - Custom interaction form templates
|
|
382
382
|
- `plutonium-policy` - Controlling access to actions
|
|
383
|
-
- `plutonium
|
|
383
|
+
- `plutonium` - How interactions fit in the architecture
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-invites
|
|
3
|
-
description:
|
|
3
|
+
description: Use when setting up user invitations with entity membership in a multi-tenant Plutonium app
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium User Invites
|