plutonium 0.44.0 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/skill.md +88 -11
  3. data/.claude/skills/plutonium-assets/SKILL.md +1 -1
  4. data/.claude/skills/plutonium-controller/SKILL.md +6 -2
  5. data/.claude/skills/plutonium-create-resource/SKILL.md +1 -1
  6. data/.claude/skills/plutonium-definition/SKILL.md +445 -53
  7. data/.claude/skills/plutonium-definition-actions/SKILL.md +2 -2
  8. data/.claude/skills/plutonium-definition-query/SKILL.md +2 -2
  9. data/.claude/skills/plutonium-forms/SKILL.md +6 -2
  10. data/.claude/skills/plutonium-installation/SKILL.md +3 -3
  11. data/.claude/skills/plutonium-interaction/SKILL.md +3 -3
  12. data/.claude/skills/plutonium-invites/SKILL.md +1 -1
  13. data/.claude/skills/plutonium-model/SKILL.md +228 -55
  14. data/.claude/skills/plutonium-nested-resources/SKILL.md +9 -2
  15. data/.claude/skills/plutonium-package/SKILL.md +3 -3
  16. data/.claude/skills/plutonium-policy/SKILL.md +6 -2
  17. data/.claude/skills/plutonium-portal/SKILL.md +97 -59
  18. data/.claude/skills/plutonium-profile/SKILL.md +2 -2
  19. data/.claude/skills/plutonium-rodauth/SKILL.md +1 -1
  20. data/.claude/skills/plutonium-theming/SKILL.md +1 -1
  21. data/.claude/skills/plutonium-views/SKILL.md +2 -2
  22. data/CHANGELOG.md +25 -0
  23. data/app/assets/plutonium.css +1 -1
  24. data/gemfiles/rails_7.gemfile.lock +3 -3
  25. data/gemfiles/rails_8.0.gemfile.lock +3 -3
  26. data/gemfiles/rails_8.1.gemfile.lock +3 -3
  27. data/lib/generators/pu/invites/install_generator.rb +2 -2
  28. data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/show.html.erb.tt +7 -7
  29. data/lib/generators/pu/saas/portal_generator.rb +17 -0
  30. data/lib/generators/pu/skills/sync/sync_generator.rb +21 -0
  31. data/lib/plutonium/engine.rb +1 -1
  32. data/lib/plutonium/railtie.rb +1 -1
  33. data/lib/plutonium/ui/form/components/resource_select.rb +1 -1
  34. data/lib/plutonium/ui/form/components/secure_association.rb +2 -2
  35. data/lib/plutonium/ui/form/components/secure_polymorphic_association.rb +6 -11
  36. data/lib/plutonium/version.rb +1 -1
  37. data/package.json +1 -1
  38. data/plutonium.gemspec +1 -1
  39. data/src/css/tokens.css +2 -0
  40. metadata +4 -8
  41. data/.claude/skills/plutonium-connect-resource/SKILL.md +0 -130
  42. data/.claude/skills/plutonium-definition-fields/SKILL.md +0 -535
  43. data/.claude/skills/plutonium-model-features/SKILL.md +0 -286
  44. data/.claude/skills/plutonium-resource/SKILL.md +0 -281
@@ -1,10 +1,16 @@
1
1
  ---
2
2
  name: plutonium-model
3
- description: Overview of Plutonium resource models - structure, setup, and best practices
3
+ description: Use when working with Plutonium resource models - setup, structure, has_cents, associations, SGID support, entity scoping, and routing
4
4
  ---
5
5
 
6
6
  # Plutonium Resource Models
7
7
 
8
+ **Always use generators to create models** - never create model files manually:
9
+ ```bash
10
+ rails g pu:res:scaffold Post title:string content:text --dest=main_app
11
+ ```
12
+ See `plutonium-create-resource` for full field type syntax and generator options.
13
+
8
14
  A model becomes a Plutonium resource by including `Plutonium::Resource::Record`. This provides enhanced ActiveRecord functionality for routing, labeling, field introspection, associations, and monetary handling.
9
15
 
10
16
  ## Setup
@@ -122,88 +128,220 @@ end
122
128
  13. **Misc attribute macros** - `has_rich_text`, `has_secure_token`, `has_secure_password`
123
129
  14. **Methods** - Public methods above, private methods below
124
130
 
125
- ## Common Patterns
131
+ ## Monetary Handling (has_cents)
126
132
 
127
- ### Archiving (State-Based)
133
+ Store monetary values as integers (cents) while exposing decimal interfaces.
128
134
 
129
- ```ruby
130
- class Property < ResourceRecord
131
- enum :state, archived: 0, active: 1
135
+ ### Basic Usage
132
136
 
133
- scope :active, -> { where(state: :active) }
134
- scope :archived, -> { where(state: :archived) }
137
+ ```ruby
138
+ class Product < ResourceRecord
139
+ has_cents :price_cents # Creates price getter/setter
140
+ has_cents :cost_cents, name: :wholesale # Custom accessor name
141
+ has_cents :tax_cents, rate: 1000 # 3 decimal places
142
+ has_cents :quantity_cents, rate: 1 # Whole numbers only
143
+ end
135
144
 
136
- def archive!
137
- update!(state: :archived)
138
- end
145
+ product = Product.new
146
+ product.price = 19.99
147
+ product.price_cents # => 1999
148
+ product.price # => 19.99
139
149
 
140
- def restore!
141
- update!(state: :active)
142
- end
143
- end
150
+ # Truncates (doesn't round)
151
+ product.price = 10.999
152
+ product.price_cents # => 1099
144
153
  ```
145
154
 
146
- ### Multi-Tenant Scoping
155
+ ### Options
147
156
 
148
157
  ```ruby
149
- class Property < ResourceRecord
150
- belongs_to :company
158
+ has_cents :field_cents,
159
+ name: :custom_name, # Accessor name (default: field without _cents)
160
+ rate: 100, # Conversion rate (default: 100)
161
+ suffix: "amount" # Suffix for generated name (default: "amount")
162
+ ```
151
163
 
152
- # Compound uniqueness for multi-tenant
153
- validates :property_code, uniqueness: {scope: :company_id}
164
+ ### Validation
154
165
 
155
- # Custom scope for entity scoping
156
- scope :associated_with_company, ->(company) { where(company: company) }
166
+ ```ruby
167
+ class Product < ResourceRecord
168
+ has_cents :price_cents
169
+
170
+ # Validate the cents field
171
+ validates :price_cents, numericality: {greater_than: 0}
157
172
  end
173
+
174
+ product = Product.new(price: -10)
175
+ product.valid? # => false
176
+ product.errors[:price_cents] # => ["must be greater than 0"]
177
+ product.errors[:price] # => ["is invalid"] (propagated)
158
178
  ```
159
179
 
160
- ### Custom Validation
180
+ ### Introspection
161
181
 
162
182
  ```ruby
163
- class Contact < ResourceRecord
164
- validates :contact_type, presence: true
183
+ Product.has_cents_attributes
184
+ # => {price_cents: {name: :price, rate: 100}, ...}
165
185
 
166
- validate :ensure_contact_provided
186
+ Product.has_cents_attribute?(:price_cents) # => true
187
+ ```
167
188
 
168
- private
189
+ ## Association SGID Support
169
190
 
170
- def ensure_contact_provided
171
- return unless [email, phone, website].all?(&:blank?)
172
- errors.add(:base, "Please provide at least one contact method")
173
- end
191
+ All associations get Signed Global ID (SGID) methods for secure serialization.
192
+
193
+ ### Singular Associations (belongs_to, has_one)
194
+
195
+ ```ruby
196
+ class Post < ResourceRecord
197
+ belongs_to :user
198
+ has_one :featured_image
174
199
  end
200
+
201
+ post = Post.first
202
+
203
+ # Get SGID
204
+ post.user_sgid # => "BAh7CEkiCG..."
205
+ post.featured_image_sgid # => "BAh7CEkiCG..."
206
+
207
+ # Set by SGID (finds and assigns)
208
+ post.user_sgid = "BAh7CEkiCG..."
209
+ post.featured_image_sgid = "BAh7CEkiCG..."
175
210
  ```
176
211
 
177
- ### One-to-One Relationships
212
+ ### Collection Associations (has_many, has_and_belongs_to_many)
178
213
 
179
214
  ```ruby
180
- # Parent side
181
- class Tenant < ResourceRecord
182
- has_one :residential_profile, class_name: "ResidentialTenantProfile"
183
- has_one :commercial_profile, class_name: "CommercialTenantProfile"
215
+ class User < ResourceRecord
216
+ has_many :posts
217
+ has_and_belongs_to_many :roles
184
218
  end
185
219
 
186
- # Child side (unique index on foreign key)
187
- class ResidentialTenantProfile < ResourceRecord
188
- belongs_to :tenant
189
- # Migration: t.index :tenant_id, unique: true
220
+ user = User.first
221
+
222
+ # Get SGIDs
223
+ user.post_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
224
+ user.role_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
225
+
226
+ # Bulk assignment
227
+ user.post_sgids = ["BAh7CEkiCG...", ...]
228
+
229
+ # Individual manipulation
230
+ user.add_post_sgid("BAh7CEkiCG...") # Add to collection
231
+ user.remove_post_sgid("BAh7CEkiCG...") # Remove from collection
232
+ ```
233
+
234
+ ## Entity Scoping (associated_with)
235
+
236
+ Query records associated with another record. Essential for multi-tenant apps.
237
+
238
+ ### Basic Usage
239
+
240
+ ```ruby
241
+ class Comment < ResourceRecord
242
+ belongs_to :post
190
243
  end
244
+
245
+ # Find comments for a post
246
+ Comment.associated_with(post)
247
+ # => Comment.where(post: post)
248
+ ```
249
+
250
+ ### Association Detection
251
+
252
+ Works with:
253
+ - `belongs_to` - Uses WHERE clause (most efficient)
254
+ - `has_one` - Uses JOIN + WHERE
255
+ - `has_many` - Uses JOIN + WHERE
256
+
257
+ ```ruby
258
+ # Direct association (preferred)
259
+ Comment.associated_with(post) # WHERE post_id = ?
260
+
261
+ # Reverse association (less efficient, logs warning)
262
+ Post.associated_with(comment) # JOIN comments WHERE comments.id = ?
191
263
  ```
192
264
 
193
- ### Polymorphic Associations
265
+ ### Custom Scopes
266
+
267
+ For optimal performance, define custom scopes:
194
268
 
195
269
  ```ruby
196
270
  class Comment < ResourceRecord
197
- belongs_to :commentable, polymorphic: true
271
+ # Custom scope naming: associated_with_{model_name}
272
+ scope :associated_with_user, ->(user) do
273
+ joins(:post).where(posts: {user_id: user.id})
274
+ end
198
275
  end
199
276
 
200
- class Post < ResourceRecord
201
- has_many :comments, as: :commentable
277
+ # Automatically uses custom scope
278
+ Comment.associated_with(user)
279
+ ```
280
+
281
+ ### Error Handling
282
+
283
+ ```ruby
284
+ # When no association exists
285
+ UnrelatedModel.associated_with(user)
286
+ # Raises: Could not resolve the association between 'UnrelatedModel' and 'User'
287
+ #
288
+ # Define:
289
+ # 1. the associations between the models
290
+ # 2. a named scope on UnrelatedModel e.g.
291
+ #
292
+ # scope :associated_with_user, ->(user) { do_something_here }
293
+ ```
294
+
295
+ ## URL Routing
296
+
297
+ ### Default Behavior
298
+
299
+ ```ruby
300
+ user = User.find(1)
301
+ user.to_param # => "1"
302
+ ```
303
+
304
+ ### Custom Path Parameters
305
+
306
+ Use a stable, unique field instead of ID:
307
+
308
+ ```ruby
309
+ class User < ResourceRecord
310
+ private
311
+
312
+ def path_parameter(param_name)
313
+ :username # Must be unique
314
+ end
202
315
  end
203
316
 
204
- class Photo < ResourceRecord
205
- has_many :comments, as: :commentable
317
+ user = User.create(username: "john_doe")
318
+ user.to_param # => "john_doe"
319
+ # URLs: /users/john_doe
320
+ ```
321
+
322
+ ### Dynamic Path Parameters (SEO-friendly)
323
+
324
+ Include ID prefix for uniqueness with human-readable suffix:
325
+
326
+ ```ruby
327
+ class Article < ResourceRecord
328
+ private
329
+
330
+ def dynamic_path_parameter(param_name)
331
+ :title
332
+ end
206
333
  end
334
+
335
+ article = Article.create(id: 1, title: "My Great Article")
336
+ article.to_param # => "1-my-great-article"
337
+ # URLs: /articles/1-my-great-article
338
+ ```
339
+
340
+ ### Path Parameter Lookup
341
+
342
+ ```ruby
343
+ User.from_path_param("john_doe")
344
+ Article.from_path_param("1-my-great-article") # Extracts ID
207
345
  ```
208
346
 
209
347
  ## Labeling
@@ -244,6 +382,49 @@ User.has_one_attached_field_names # Active Storage single
244
382
  User.has_many_attached_field_names # Active Storage multiple
245
383
  ```
246
384
 
385
+ ## Common Patterns
386
+
387
+ ### Archiving (State-Based)
388
+
389
+ ```ruby
390
+ class Property < ResourceRecord
391
+ enum :state, archived: 0, active: 1
392
+
393
+ scope :active, -> { where(state: :active) }
394
+ scope :archived, -> { where(state: :archived) }
395
+ end
396
+ ```
397
+
398
+ ### Multi-Tenant Scoping
399
+
400
+ ```ruby
401
+ class Property < ResourceRecord
402
+ belongs_to :company
403
+
404
+ # Compound uniqueness for multi-tenant
405
+ validates :property_code, uniqueness: {scope: :company_id}
406
+
407
+ # Custom scope for entity scoping
408
+ scope :associated_with_company, ->(company) { where(company: company) }
409
+ end
410
+ ```
411
+
412
+ ## Performance Tips
413
+
414
+ ```ruby
415
+ # Efficient: Direct belongs_to
416
+ Comment.associated_with(post) # Simple WHERE
417
+
418
+ # Less efficient: Reverse has_many (logs warning)
419
+ Post.associated_with(comment) # JOIN required
420
+
421
+ # Optimal: Custom scope when direct isn't possible
422
+ scope :associated_with_user, ->(user) { where(user_id: user.id) }
423
+
424
+ # SGID: Batch assignment over individual adds
425
+ user.post_sgids = sgid_array # Single operation
426
+ ```
427
+
247
428
  ## Best Practices
248
429
 
249
430
  1. **Use enums for state** - `enum :state, archived: 0, active: 1` instead of soft-delete
@@ -253,15 +434,7 @@ User.has_many_attached_field_names # Active Storage multiple
253
434
  5. **Validate at boundaries** - Validate user input, trust internal code
254
435
  6. **Use scopes** - Define commonly used queries as scopes
255
436
 
256
- ## Integration
257
-
258
- Models integrate with:
259
- - **Policies** - `resource_field_names` for auto-detection
260
- - **Definitions** - Field introspection for forms/displays
261
- - **Controllers** - `from_path_param` for lookups
262
- - **Query Objects** - Association detection for sorting
263
-
264
437
  ## Related Skills
265
438
 
266
- - `plutonium-model-features` - has_cents, associations, scopes, routes
267
439
  - `plutonium-create-resource` - Scaffold generator for new resources
440
+ - `plutonium-definition` - Definition overview, fields, inputs, displays
@@ -1,10 +1,17 @@
1
1
  ---
2
2
  name: plutonium-nested-resources
3
- description: Plutonium nested resources - parent/child routes, scoping, and URL generation
3
+ description: Use when configuring parent/child resource relationships, nested routes, or scoped URL generation in Plutonium
4
4
  ---
5
5
 
6
6
  # Nested Resources
7
7
 
8
+ **Always use generators** to create both parent and child resources, then connect them to portals:
9
+ ```bash
10
+ rails g pu:res:scaffold Company name:string --dest=main_app
11
+ rails g pu:res:scaffold Property company:belongs_to name:string --dest=main_app
12
+ rails g pu:res:conn Company Property --dest=admin_portal
13
+ ```
14
+
8
15
  Plutonium automatically creates nested routes for `has_many` and `has_one` associations, scopes queries to the parent, and handles URL generation.
9
16
 
10
17
  ## How It Works
@@ -333,4 +340,4 @@ Generates nested routes:
333
340
  - `plutonium-portal` - Route registration
334
341
  - `plutonium-policy` - Authorization and scoping
335
342
  - `plutonium-controller` - Presentation hooks
336
- - `plutonium-model-features` - associated_with scope
343
+ - `plutonium-model` - associated_with scope
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: plutonium-package
3
- description: Plutonium packages - modular Rails engines for organizing features and portals
3
+ description: Use when creating feature packages or portal packages to organize a Plutonium app into modular engines
4
4
  ---
5
5
 
6
6
  # Plutonium Packages
@@ -186,6 +186,6 @@ rails db:migrate # Runs migrations from all packages
186
186
  ## Related Skills
187
187
 
188
188
  - `plutonium-portal` - Portal-specific features (auth, entity scoping, routes)
189
- - `plutonium-resource` - Resource architecture overview
190
- - `plutonium-connect-resource` - Connecting resources to portals
189
+ - `plutonium` - Resource architecture overview
190
+ - `plutonium-portal` - Connecting resources to portals
191
191
  - `plutonium-create-resource` - Creating resources
@@ -1,10 +1,14 @@
1
1
  ---
2
2
  name: plutonium-policy
3
- description: Plutonium resource policies - authorization, attribute permissions, and scoping
3
+ description: Use when configuring authorization - action permissions, attribute visibility, relation scoping, or per-portal policies
4
4
  ---
5
5
 
6
6
  # Plutonium Policies
7
7
 
8
+ **Policies are generated automatically** - never create them manually:
9
+ - `rails g pu:res:scaffold` creates the base policy
10
+ - `rails g pu:res:conn` creates portal-specific policies with attribute permissions
11
+
8
12
  Policies control WHO can do WHAT with resources. Built on [ActionPolicy](https://actionpolicy.evilmartians.io/).
9
13
 
10
14
  Plutonium extends ActionPolicy with:
@@ -456,6 +460,6 @@ end
456
460
 
457
461
  ## Related Skills
458
462
 
459
- - `plutonium-resource` - How policies fit in the resource architecture
463
+ - `plutonium` - How policies fit in the resource architecture
460
464
  - `plutonium-definition-actions` - Actions that need policy methods
461
465
  - `plutonium-controller` - How controllers use policies
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: plutonium-portal
3
- description: Plutonium portals - web interfaces with authentication, entity scoping, and routes
3
+ description: Use when creating portals, connecting resources to portals, configuring authentication, entity scoping, or portal routes
4
4
  ---
5
5
 
6
6
  # Plutonium Portals
@@ -32,10 +32,7 @@ rails g pu:pkg:portal custom --byo
32
32
  rails g pu:pkg:portal admin --auth=admin --scope=Organization
33
33
  ```
34
34
 
35
- Without flags, the generator prompts interactively:
36
- - **Rodauth account** - Use existing Rodauth authentication
37
- - **Public access** - No authentication required
38
- - **Bring your own** - Implement custom `current_user`
35
+ Without flags, the generator prompts interactively.
39
36
 
40
37
  ## Portal Engine
41
38
 
@@ -53,6 +50,86 @@ module DashboardPortal
53
50
  end
54
51
  ```
55
52
 
53
+ ## Connecting Resources to Portals
54
+
55
+ Resources must be connected to a portal to be accessible via its web interface.
56
+
57
+ ### Command Syntax
58
+
59
+ ```bash
60
+ rails g pu:res:conn RESOURCE [RESOURCE...] --dest=PORTAL_NAME [--singular]
61
+ ```
62
+
63
+ **Always specify resources directly** - this avoids interactive prompts.
64
+
65
+ ### Usage Patterns
66
+
67
+ ```bash
68
+ # Main app resources
69
+ rails g pu:res:conn Post Comment Tag --dest=dashboard_portal
70
+
71
+ # Namespaced resources (from a feature package)
72
+ rails g pu:res:conn Blogging::Post Blogging::Comment --dest=admin_portal
73
+
74
+ # Singular resources (profile, dashboard, settings)
75
+ rails g pu:res:conn Profile --dest=customer_portal --singular
76
+ ```
77
+
78
+ ### What Gets Generated
79
+
80
+ For a resource `Post` connected to `admin_portal`:
81
+
82
+ ```
83
+ packages/admin_portal/app/
84
+ ├── controllers/admin_portal/posts_controller.rb # Portal controller
85
+ ├── policies/admin_portal/post_policy.rb # Portal policy
86
+ └── definitions/admin_portal/post_definition.rb # Portal definition
87
+ ```
88
+
89
+ Plus route registration in `packages/admin_portal/config/routes.rb`.
90
+
91
+ ### Generated Controller
92
+
93
+ ```ruby
94
+ class AdminPortal::PostsController < ::PostsController
95
+ include AdminPortal::Concerns::Controller
96
+ end
97
+ ```
98
+
99
+ ### Generated Policy
100
+
101
+ ```ruby
102
+ class AdminPortal::PostPolicy < ::PostPolicy
103
+ include AdminPortal::ResourcePolicy
104
+
105
+ def permitted_attributes_for_create
106
+ [:title, :content, :user_id]
107
+ end
108
+
109
+ def permitted_attributes_for_read
110
+ [:title, :content, :user_id, :created_at, :updated_at]
111
+ end
112
+
113
+ def permitted_associations
114
+ %i[]
115
+ end
116
+ end
117
+ ```
118
+
119
+ ### Route Registration
120
+
121
+ ```ruby
122
+ # In packages/admin_portal/config/routes.rb
123
+ register_resource ::Post
124
+ register_resource ::Profile, singular: true # With --singular
125
+ ```
126
+
127
+ ### Important Notes
128
+
129
+ 1. **Always specify resources directly** - avoids prompts, no `--src` needed
130
+ 2. **Always use the generator** - never manually connect resources
131
+ 3. **Run after migrations** - the generator reads model columns for policy attributes
132
+
56
133
  ## Authentication
57
134
 
58
135
  ### Rodauth Integration
@@ -224,13 +301,6 @@ register_resource ::Post do
224
301
  end
225
302
  ```
226
303
 
227
- This generates:
228
- - `GET /posts/:id/preview`
229
- - `GET /posts/:id/analytics`
230
- - `POST /posts/:id/publish`
231
- - `GET /posts/archived`
232
- - `POST /posts/bulk_publish`
233
-
234
304
  ### Mounting in Main App
235
305
 
236
306
  ```ruby
@@ -263,8 +333,6 @@ end
263
333
 
264
334
  ### Portal ResourceController
265
335
 
266
- The portal's `ResourceController` serves as the base class for resource controllers when no feature package controller exists. It includes the portal's `Concerns::Controller` so individual resource controllers don't need to.
267
-
268
336
  ```ruby
269
337
  # packages/dashboard_portal/app/controllers/dashboard_portal/resource_controller.rb
270
338
  module DashboardPortal
@@ -279,7 +347,6 @@ end
279
347
  For portal pages not tied to a resource (dashboard, settings, etc.), inherit from `PlutoniumController`:
280
348
 
281
349
  ```ruby
282
- # packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
283
350
  module DashboardPortal
284
351
  class DashboardController < PlutoniumController
285
352
  def index
@@ -294,10 +361,7 @@ end
294
361
  ### Override Definition
295
362
 
296
363
  ```ruby
297
- # packages/dashboard_portal/app/definitions/dashboard_portal/post_definition.rb
298
364
  class DashboardPortal::PostDefinition < ::PostDefinition
299
- # Hide certain actions from this portal
300
- # Add portal-specific scopes
301
365
  scope :my_posts, -> { where(user: current_user) }
302
366
  end
303
367
  ```
@@ -305,7 +369,6 @@ end
305
369
  ### Override Policy
306
370
 
307
371
  ```ruby
308
- # packages/dashboard_portal/app/policies/dashboard_portal/post_policy.rb
309
372
  class DashboardPortal::PostPolicy < ::PostPolicy
310
373
  include DashboardPortal::ResourcePolicy
311
374
 
@@ -322,7 +385,6 @@ end
322
385
  ### Override Controller
323
386
 
324
387
  ```ruby
325
- # packages/dashboard_portal/app/controllers/dashboard_portal/posts_controller.rb
326
388
  module DashboardPortal
327
389
  class PostsController < ResourceController
328
390
  private
@@ -334,39 +396,6 @@ module DashboardPortal
334
396
  end
335
397
  ```
336
398
 
337
- ## Layout and Views
338
-
339
- ### Portal Layout
340
-
341
- ```erb
342
- <!-- packages/dashboard_portal/app/views/layouts/dashboard_portal.html.erb -->
343
- <!DOCTYPE html>
344
- <html>
345
- <head>
346
- <title>Dashboard</title>
347
- <%= csrf_meta_tags %>
348
- <%= stylesheet_link_tag "application" %>
349
- </head>
350
- <body>
351
- <nav><!-- Portal navigation --></nav>
352
- <main><%= yield %></main>
353
- </body>
354
- </html>
355
- ```
356
-
357
- ### Dashboard Controller
358
-
359
- ```ruby
360
- # packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
361
- module DashboardPortal
362
- class DashboardController < PlutoniumController
363
- def index
364
- # Dashboard home page
365
- end
366
- end
367
- end
368
- ```
369
-
370
399
  ## Multiple Portals Example
371
400
 
372
401
  ```ruby
@@ -400,17 +429,26 @@ module PublicPortal
400
429
  end
401
430
  ```
402
431
 
403
- Each portal can:
404
- - Have different authentication
405
- - Show different fields
406
- - Allow different actions
407
- - Use different layouts
432
+ ## Typical Workflow
433
+
434
+ ```bash
435
+ # 1. Create portal
436
+ rails g pu:pkg:portal admin --auth=admin --scope=Organization
437
+
438
+ # 2. Create resources
439
+ rails g pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
440
+ rails db:migrate
441
+
442
+ # 3. Connect resources to portal
443
+ rails g pu:res:conn Post --dest=admin_portal
444
+
445
+ # 4. Customize portal-specific definitions/policies as needed
446
+ ```
408
447
 
409
448
  ## Related Skills
410
449
 
411
450
  - `plutonium-package` - Package overview (features vs portals)
412
451
  - `plutonium-rodauth` - Authentication setup and configuration
413
- - `plutonium-connect-resource` - Connecting resources to portals
414
452
  - `plutonium-policy` - Portal-specific policies
415
453
  - `plutonium-definition` - Portal-specific definitions
416
454
  - `plutonium-controller` - Portal-specific controllers
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: plutonium-profile
3
- description: Plutonium user profile - account settings page with Rodauth security links
3
+ description: Use when adding a user profile or account settings page with Rodauth security features
4
4
  ---
5
5
 
6
6
  # Plutonium User Profile
@@ -273,4 +273,4 @@ current_user.profile || current_user.create_profile
273
273
  - `plutonium-rodauth` - Authentication configuration
274
274
  - `plutonium-definition` - Customizing the profile definition
275
275
  - `plutonium-views` - Custom pages and components
276
- - `plutonium-connect-resource` - Connecting resources to portals
276
+ - `plutonium-portal` - Connecting resources to portals
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: plutonium-rodauth
3
- description: Plutonium Rodauth integration - authentication setup, account types, and configuration
3
+ description: Use when setting up authentication - Rodauth configuration, account types, login flows, or multi-account support
4
4
  ---
5
5
 
6
6
  # Plutonium Rodauth Authentication
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: plutonium-theming
3
- description: Plutonium design token system - CSS custom properties, component classes, and consistent styling patterns
3
+ description: Use when customizing colors, typography, spacing, or component appearance via Plutonium design tokens
4
4
  ---
5
5
 
6
6
  # Plutonium Design Token System