plutonium 0.45.2 → 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 (50) 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 +33 -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/config/initializers/pagy.rb +1 -1
  26. data/docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md +481 -0
  27. data/docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md +236 -0
  28. data/gemfiles/rails_7.gemfile.lock +1 -1
  29. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  30. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  31. data/lib/generators/pu/core/update/update_generator.rb +8 -0
  32. data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +56 -0
  33. data/lib/generators/pu/invites/install_generator.rb +8 -1
  34. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +43 -0
  35. data/lib/generators/pu/profile/concerns/profile_arguments.rb +10 -4
  36. data/lib/generators/pu/profile/conn_generator.rb +9 -12
  37. data/lib/generators/pu/profile/install_generator.rb +5 -2
  38. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  39. data/lib/generators/pu/saas/portal_generator.rb +4 -9
  40. data/lib/generators/pu/saas/welcome/templates/app/views/welcome/onboarding.html.erb.tt +2 -2
  41. data/lib/plutonium/engine.rb +18 -5
  42. data/lib/plutonium/ui/layout/rodauth_layout.rb +6 -1
  43. data/lib/plutonium/version.rb +1 -1
  44. data/package.json +1 -1
  45. metadata +7 -8
  46. data/.claude/skills/plutonium/skill.md +0 -130
  47. data/.claude/skills/plutonium-definition-actions/SKILL.md +0 -424
  48. data/.claude/skills/plutonium-definition-query/SKILL.md +0 -364
  49. data/.claude/skills/plutonium-profile/SKILL.md +0 -276
  50. data/.claude/skills/plutonium-theming/SKILL.md +0 -424
@@ -1,364 +0,0 @@
1
- ---
2
- name: plutonium-definition-query
3
- description: Use when adding search, filters, named scopes, or sorting to a Plutonium resource definition
4
- ---
5
-
6
- # Definition Query
7
-
8
- Configure how users can search, filter, and sort resource collections.
9
-
10
- ## Overview
11
-
12
- ```ruby
13
- class PostDefinition < ResourceDefinition
14
- # Search - global text search
15
- search do |scope, query|
16
- scope.where("title ILIKE ?", "%#{query}%")
17
- end
18
-
19
- # Filters - dropdown filter panel
20
- filter :title, with: :text, predicate: :contains
21
- filter :status, with: :select, choices: %w[draft published archived]
22
- filter :published, with: :boolean
23
- filter :created_at, with: :date_range
24
- filter :category, with: :association
25
-
26
- # Scopes - quick filter buttons
27
- scope :published
28
- scope :draft
29
-
30
- # Default scope
31
- default_scope :published
32
-
33
- # Sorting - sortable columns
34
- sort :title
35
- sort :created_at
36
-
37
- # Default sort
38
- default_sort :created_at, :desc
39
- end
40
- ```
41
-
42
- ## Search
43
-
44
- Define global search across fields:
45
-
46
- ```ruby
47
- # Single field
48
- search do |scope, query|
49
- scope.where("title ILIKE ?", "%#{query}%")
50
- end
51
-
52
- # Multiple fields
53
- search do |scope, query|
54
- scope.where(
55
- "title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
56
- q: "%#{query}%"
57
- )
58
- end
59
-
60
- # With associations
61
- search do |scope, query|
62
- scope.joins(:author).where(
63
- "posts.title ILIKE :q OR users.name ILIKE :q",
64
- q: "%#{query}%"
65
- ).distinct
66
- end
67
-
68
- # Split search terms
69
- search do |scope, query|
70
- terms = query.split(/\s+/)
71
- terms.reduce(scope) do |current_scope, term|
72
- current_scope.where("title ILIKE ?", "%#{term}%")
73
- end
74
- end
75
- ```
76
-
77
- ## Filters
78
-
79
- Plutonium provides **6 built-in filter types**. Use shorthand symbols or full class names.
80
-
81
- ### Text Filter
82
-
83
- String/text filtering with pattern matching.
84
-
85
- ```ruby
86
- # Shorthand (recommended)
87
- filter :title, with: :text, predicate: :contains
88
- filter :status, with: :text, predicate: :eq
89
-
90
- # Full class name
91
- filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
92
- ```
93
-
94
- **Predicates:**
95
-
96
- | Predicate | SQL | Description |
97
- |-----------|-----|-------------|
98
- | `:eq` | `= value` | Exact match (default) |
99
- | `:not_eq` | `!= value` | Not equal |
100
- | `:contains` | `LIKE %value%` | Contains substring |
101
- | `:not_contains` | `NOT LIKE %value%` | Does not contain |
102
- | `:starts_with` | `LIKE value%` | Starts with |
103
- | `:ends_with` | `LIKE %value` | Ends with |
104
- | `:matches` | `LIKE value` | Pattern match (`*` becomes `%`) |
105
- | `:not_matches` | `NOT LIKE value` | Does not match pattern |
106
-
107
- ### Boolean Filter
108
-
109
- True/false filtering for boolean columns.
110
-
111
- ```ruby
112
- # Basic
113
- filter :active, with: :boolean
114
-
115
- # Custom labels
116
- filter :published, with: :boolean, true_label: "Published", false_label: "Draft"
117
- ```
118
-
119
- Renders a select dropdown with "All", true label ("Yes"), and false label ("No").
120
-
121
- ### Date Filter
122
-
123
- Single date filtering with comparison predicates.
124
-
125
- ```ruby
126
- filter :created_at, with: :date, predicate: :gteq # On or after
127
- filter :due_date, with: :date, predicate: :lt # Before
128
- filter :published_at, with: :date, predicate: :eq # On exact date
129
- ```
130
-
131
- **Predicates:**
132
-
133
- | Predicate | Description |
134
- |-----------|-------------|
135
- | `:eq` | On this date (default) |
136
- | `:not_eq` | Not on this date |
137
- | `:lt` | Before date |
138
- | `:lteq` | On or before date |
139
- | `:gt` | After date |
140
- | `:gteq` | On or after date |
141
-
142
- ### Date Range Filter
143
-
144
- Filter between two dates (from/to).
145
-
146
- ```ruby
147
- # Basic
148
- filter :created_at, with: :date_range
149
-
150
- # Custom labels
151
- filter :published_at, with: :date_range,
152
- from_label: "Published from",
153
- to_label: "Published to"
154
- ```
155
-
156
- Renders two date pickers. Both are optional - users can filter with just "from" or just "to".
157
-
158
- ### Select Filter
159
-
160
- Filter from predefined choices.
161
-
162
- ```ruby
163
- # Static choices (array)
164
- filter :status, with: :select, choices: %w[draft published archived]
165
-
166
- # Dynamic choices (proc)
167
- filter :category, with: :select, choices: -> { Category.pluck(:name) }
168
-
169
- # Multiple selection
170
- filter :tags, with: :select, choices: %w[ruby rails js], multiple: true
171
- ```
172
-
173
- ### Association Filter
174
-
175
- Filter by associated record.
176
-
177
- ```ruby
178
- # Basic - infers Category class from :category key
179
- filter :category, with: :association
180
-
181
- # Explicit class
182
- filter :author, with: :association, class_name: User
183
-
184
- # Multiple selection
185
- filter :tags, with: :association, class_name: Tag, multiple: true
186
- ```
187
-
188
- Renders a resource select dropdown. Converts filter key to foreign key (`:category` -> `:category_id`).
189
-
190
- ## Custom Filters
191
-
192
- ### Custom Filter Class
193
-
194
- ```ruby
195
- class PriceRangeFilter < Plutonium::Query::Filter
196
- def apply(scope, min: nil, max: nil)
197
- scope = scope.where("price >= ?", min) if min.present?
198
- scope = scope.where("price <= ?", max) if max.present?
199
- scope
200
- end
201
-
202
- def customize_inputs
203
- input :min, as: :number
204
- input :max, as: :number
205
- field :min, placeholder: "Min price..."
206
- field :max, placeholder: "Max price..."
207
- end
208
- end
209
-
210
- # Use in definition
211
- filter :price, with: PriceRangeFilter
212
- ```
213
-
214
- ## Scopes
215
-
216
- Scopes appear as quick filter buttons. They reference model scopes.
217
-
218
- ### Basic Usage
219
-
220
- ```ruby
221
- class PostDefinition < ResourceDefinition
222
- scope :published # Uses Post.published
223
- scope :draft # Uses Post.draft
224
- scope :featured # Uses Post.featured
225
- end
226
- ```
227
-
228
- ### Inline Scope
229
-
230
- Use block syntax with the scope passed as an argument:
231
-
232
- ```ruby
233
- scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
234
- scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
235
- ```
236
-
237
- ### With Controller Context
238
-
239
- Inline scopes have access to controller context like `current_user`:
240
-
241
- ```ruby
242
- scope(:mine) { |scope| scope.where(author: current_user) }
243
- scope(:my_team) { |scope| scope.where(team: current_user.team) }
244
- ```
245
-
246
- ### Default Scope
247
-
248
- Set a scope as default to apply it when no scope is explicitly selected:
249
-
250
- ```ruby
251
- class PostDefinition < ResourceDefinition
252
- scope :published
253
- scope :draft
254
- scope :archived
255
-
256
- default_scope :published # Applied by default
257
- end
258
- ```
259
-
260
- When a default scope is set:
261
- - The default scope is applied on initial page load
262
- - The default scope button is highlighted (not "All")
263
- - Clicking "All" shows all records without any scope filter
264
- - URL without scope param uses the default; URL with `?q[scope]=` uses "All"
265
-
266
- ## Sorting
267
-
268
- ### Basic Sorting
269
-
270
- ```ruby
271
- sort :title
272
- sort :created_at
273
- sort :view_count
274
-
275
- # Multiple at once
276
- sorts :title, :created_at, :view_count
277
- ```
278
-
279
- ### Default Sort
280
-
281
- ```ruby
282
- # Field and direction
283
- default_sort :created_at, :desc
284
- default_sort :title, :asc
285
-
286
- # Complex sorting with block
287
- default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }
288
- ```
289
-
290
- **Note:** Default sort only applies when no sort params are provided.
291
-
292
- ## URL Parameters
293
-
294
- Query parameters are structured under `q`:
295
-
296
- ```
297
- /posts?q[search]=rails
298
- /posts?q[title][query]=widget
299
- /posts?q[status][value]=published
300
- /posts?q[created_at][from]=2024-01-01&q[created_at][to]=2024-12-31
301
- /posts?q[scope]=recent
302
- /posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
303
- ```
304
-
305
- ## Filter Summary Table
306
-
307
- | Type | Symbol | Input Params | Options |
308
- |------|--------|--------------|---------|
309
- | Text | `:text` | `query` | `predicate:` |
310
- | Boolean | `:boolean` | `value` | `true_label:`, `false_label:` |
311
- | Date | `:date` | `value` | `predicate:` |
312
- | Date Range | `:date_range` | `from`, `to` | `from_label:`, `to_label:` |
313
- | Select | `:select` | `value` | `choices:`, `multiple:` |
314
- | Association | `:association` | `value` | `class_name:`, `multiple:` |
315
-
316
- ## Complete Example
317
-
318
- ```ruby
319
- class ProductDefinition < ResourceDefinition
320
- # Full-text search
321
- search do |scope, query|
322
- scope.where(
323
- "name ILIKE :q OR description ILIKE :q",
324
- q: "%#{query}%"
325
- )
326
- end
327
-
328
- # Filters
329
- filter :name, with: :text, predicate: :contains
330
- filter :status, with: :select, choices: %w[draft active discontinued]
331
- filter :featured, with: :boolean
332
- filter :created_at, with: :date_range
333
- filter :price, with: :date, predicate: :gteq
334
- filter :category, with: :association
335
-
336
- # Quick scopes
337
- scope :active
338
- scope :featured
339
- scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
340
-
341
- # Default scope
342
- default_scope :active
343
-
344
- # Sortable columns
345
- sorts :name, :price, :created_at
346
-
347
- # Default sort
348
- default_sort :created_at, :desc
349
- end
350
- ```
351
-
352
- ## Performance Tips
353
-
354
- 1. **Add indexes** for filtered/sorted columns
355
- 2. **Use `.distinct`** when joining associations in search
356
- 3. **Consider `pg_search`** for complex full-text search
357
- 4. **Limit search fields** to indexed columns
358
- 5. **Use scopes** instead of filters for common queries
359
-
360
- ## Related Skills
361
-
362
- - `plutonium-definition` - Overview and structure
363
- - `plutonium-definition` - Fields, inputs, displays
364
- - `plutonium-definition-actions` - Actions and interactions
@@ -1,276 +0,0 @@
1
- ---
2
- name: plutonium-profile
3
- description: Use when adding a user profile or account settings page with Rodauth security features
4
- ---
5
-
6
- # Plutonium User Profile
7
-
8
- Plutonium provides a Profile resource generator for managing Rodauth account settings. The profile resource allows users to:
9
- - View and edit their profile information
10
- - Access Rodauth security features (change password, 2FA, etc.)
11
- - Manage their account settings in one place
12
-
13
- ## Quick Setup
14
-
15
- Use the setup generator to create and connect the profile in one command:
16
-
17
- ```bash
18
- rails g pu:profile:setup date_of_birth:date bio:text \
19
- --dest=competition \
20
- --portal=competition_portal
21
- ```
22
-
23
- ## Step-by-Step Installation
24
-
25
- ### Install the Profile Resource
26
-
27
- ```bash
28
- rails generate pu:profile:install --dest=main_app
29
- ```
30
-
31
- **Options:**
32
-
33
- | Option | Default | Description |
34
- |--------|---------|-------------|
35
- | `--dest=DESTINATION` | (prompts) | Target package or main_app |
36
- | `--user-model=NAME` | User | Rodauth user model name |
37
-
38
- **With custom fields:**
39
-
40
- ```bash
41
- rails g pu:profile:install \
42
- bio:text \
43
- avatar:attachment \
44
- 'timezone:string?' \
45
- --dest=customer
46
- ```
47
-
48
- **With custom name:**
49
-
50
- ```bash
51
- rails g pu:profile:install AccountSettings \
52
- bio:text \
53
- --dest=main_app
54
- ```
55
-
56
- ## What Gets Created
57
-
58
- The generator creates a standard Plutonium resource:
59
-
60
- ```
61
- app/models/[package/]profile.rb # Profile model
62
- db/migrate/xxx_create_profiles.rb # Migration
63
- app/controllers/[package/]profiles_controller.rb
64
- app/policies/[package/]profile_policy.rb
65
- app/definitions/[package/]profile_definition.rb
66
- ```
67
-
68
- And modifies:
69
- - **User model**: Adds `has_one :profile, dependent: :destroy`
70
- - **Definition**: Injects custom ShowPage with SecuritySection
71
-
72
- ## The SecuritySection Component
73
-
74
- The generator injects a custom `ShowPage` that renders `Plutonium::Profile::SecuritySection`:
75
-
76
- ```ruby
77
- class ProfileDefinition < Plutonium::Resource::Definition
78
- class ShowPage < ShowPage
79
- private
80
-
81
- def render_after_content
82
- render Plutonium::Profile::SecuritySection.new
83
- end
84
- end
85
- end
86
- ```
87
-
88
- The `SecuritySection` component dynamically checks which Rodauth features are enabled and displays links for:
89
-
90
- | Feature | Label | Description |
91
- |---------|-------|-------------|
92
- | `change_password` | Change Password | Update account password |
93
- | `change_login` | Change Email | Update email address |
94
- | `otp` | Two-Factor Authentication | Set up TOTP |
95
- | `recovery_codes` | Recovery Codes | View/regenerate backup codes |
96
- | `webauthn` | Security Keys | Manage passkeys |
97
- | `active_sessions` | Active Sessions | View/manage sessions |
98
- | `close_account` | Close Account | Delete account |
99
-
100
- Only enabled Rodauth features are displayed.
101
-
102
- ## After Generation
103
-
104
- ### 1. Run Migrations
105
-
106
- ```bash
107
- rails db:migrate
108
- ```
109
-
110
- ### 2. Connect to Portal
111
-
112
- Use the profile connect generator to register as a singular resource and configure the `profile_url` helper:
113
-
114
- ```bash
115
- rails g pu:profile:conn --dest=customer_portal
116
- ```
117
-
118
- This:
119
- - Registers the Profile as a singular resource (`/profile` instead of `/profiles/:id`)
120
- - Adds `profile_url` helper to enable the "Profile" link in the user menu
121
-
122
- ### 3. Create Profile Automatically
123
-
124
- Users need a profile created. Add a callback or use `find_or_create`:
125
-
126
- ```ruby
127
- # Option A: Callback on User
128
- class User < ApplicationRecord
129
- after_create :create_profile!
130
-
131
- private
132
-
133
- def create_profile!
134
- create_profile
135
- end
136
- end
137
-
138
- # Option B: In controller or before_action
139
- def current_profile
140
- @current_profile ||= current_user.profile || current_user.create_profile
141
- end
142
- ```
143
-
144
- ## Customization
145
-
146
- ### Adding Profile Fields
147
-
148
- Add fields during generation:
149
-
150
- ```bash
151
- rails g pu:profile:install \
152
- bio:text \
153
- avatar:attachment \
154
- website:string \
155
- 'company:string?' \
156
- --dest=main_app
157
- ```
158
-
159
- Or add to the migration manually before running.
160
-
161
- ### Customizing the Definition
162
-
163
- Edit the generated definition:
164
-
165
- ```ruby
166
- # app/definitions/profile_definition.rb
167
- class ProfileDefinition < Plutonium::Resource::Definition
168
- # Form configuration
169
- form do |f|
170
- f.field :bio
171
- f.field :avatar
172
- f.field :website
173
- end
174
-
175
- # Display configuration
176
- display do |d|
177
- d.field :bio
178
- d.field :avatar
179
- d.field :website
180
- end
181
-
182
- class ShowPage < ShowPage
183
- private
184
-
185
- def render_after_content
186
- render Plutonium::Profile::SecuritySection.new
187
- end
188
- end
189
- end
190
- ```
191
-
192
- ### Custom Security Section
193
-
194
- Override the SecuritySection or create your own:
195
-
196
- ```ruby
197
- class ProfileDefinition < Plutonium::Resource::Definition
198
- class ShowPage < ShowPage
199
- private
200
-
201
- def render_after_content
202
- render CustomSecuritySection.new
203
- end
204
- end
205
- end
206
- ```
207
-
208
- ### Adding Custom Actions
209
-
210
- Add profile-specific actions:
211
-
212
- ```ruby
213
- class ProfileDefinition < Plutonium::Resource::Definition
214
- action :export_data,
215
- interaction: Profile::ExportDataInteraction
216
-
217
- action :verify_email,
218
- interaction: Profile::VerifyEmailInteraction,
219
- category: :secondary
220
- end
221
- ```
222
-
223
- ## Profile Link in Header
224
-
225
- To add a profile link to the resource header, the `profile_url` helper is available via `Plutonium::Auth::Rodauth`:
226
-
227
- ```ruby
228
- # In your controller or view
229
- if respond_to?(:profile_url)
230
- link_to "Profile", profile_url
231
- end
232
- ```
233
-
234
- This helper is automatically available when Profile is connected to a portal.
235
-
236
- ## Troubleshooting
237
-
238
- ### "User model not found"
239
-
240
- Ensure the User model exists at `app/models/user.rb` with the marker comment:
241
-
242
- ```ruby
243
- class User < ApplicationRecord
244
- # add has_one associations above.
245
- end
246
- ```
247
-
248
- ### "Definition path not found"
249
-
250
- If using a package destination, ensure the package exists:
251
-
252
- ```bash
253
- # Check available packages
254
- ls packages/
255
- ```
256
-
257
- ### Profile Not Loading
258
-
259
- Ensure the Profile is connected to your portal:
260
-
261
- ```bash
262
- rails g pu:res:conn Profile --dest=my_portal --singular
263
- ```
264
-
265
- And the user has a profile:
266
-
267
- ```ruby
268
- current_user.profile || current_user.create_profile
269
- ```
270
-
271
- ## Related Skills
272
-
273
- - `plutonium-rodauth` - Authentication configuration
274
- - `plutonium-definition` - Customizing the profile definition
275
- - `plutonium-views` - Custom pages and components
276
- - `plutonium-portal` - Connecting resources to portals