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,381 +1,179 @@
1
1
  # Creating Packages
2
2
 
3
- This guide covers creating and organizing Feature Packages and Portal Packages.
3
+ Organize your app into feature and portal packages.
4
4
 
5
- ## Package Types
5
+ ## Goal
6
6
 
7
- | Type | Purpose | Generator |
8
- |------|---------|-----------|
9
- | **Feature Package** | Business logic (models, definitions, policies) | `rails g pu:pkg:package NAME` |
10
- | **Portal Package** | Web interface (routes, auth, UI) | `rails g pu:pkg:portal NAME` |
7
+ Domain code (models, policies, definitions, interactions) lives in **feature packages**. Web interfaces (controllers, views, routes, auth) live in **portal packages**. Both are Rails engines with Plutonium conventions on top.
11
8
 
12
- ## Creating a Feature Package
9
+ ## Two types
13
10
 
14
- ### Using the Generator
11
+ | Type | Purpose | Generator | Examples |
12
+ |---|---|---|---|
13
+ | **Feature** | Business logic | `pu:pkg:package NAME` | `blogging`, `billing`, `inventory` |
14
+ | **Portal** | Web interface | `pu:pkg:portal NAME` | `admin_portal`, `customer_portal`, `public_portal` |
15
+
16
+ 🚨 Don't mix the two. Feature packages have NO routes, views, or controllers. Portal packages have NO models or interactions.
17
+
18
+ ## Feature package
19
+
20
+ ### 1. Generate
15
21
 
16
22
  ```bash
17
23
  rails g pu:pkg:package blogging
18
24
  ```
19
25
 
20
- ### Generated Structure
26
+ ### 2. Structure
21
27
 
22
28
  ```
23
29
  packages/blogging/
24
30
  ├── app/
25
- │ ├── controllers/blogging/
26
- │ └── resource_controller.rb
27
- │ ├── definitions/blogging/
28
- └── resource_definition.rb
29
- ├── interactions/blogging/
30
- │ │ └── resource_interaction.rb
31
- │ ├── models/blogging/
32
- │ │ └── resource_record.rb
33
- │ ├── policies/blogging/
34
- │ │ └── resource_policy.rb
35
- │ └── views/blogging/
36
- └── lib/
37
- └── engine.rb
31
+ │ ├── models/blogging/ # Blogging::Post
32
+ ├── definitions/blogging/ # Blogging::PostDefinition
33
+ │ ├── policies/blogging/ # Blogging::PostPolicy
34
+ │ └── interactions/blogging/ # Blogging::PublishPostInteraction
35
+ ├── db/migrate/
36
+ └── lib/engine.rb
38
37
  ```
39
38
 
40
- ### Engine Configuration
41
-
42
- ```ruby
43
- # packages/blogging/lib/engine.rb
44
- module Blogging
45
- class Engine < Rails::Engine
46
- include Plutonium::Package::Engine
47
- end
48
- end
49
- ```
50
-
51
- ### Namespacing
52
-
53
- All classes are auto-namespaced:
54
- - `app/models/blogging/post.rb` → `Blogging::Post`
55
- - `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
56
-
57
- ## Creating a Portal Package
58
-
59
- ### Using the Generator
39
+ ### 3. Create resources inside it
60
40
 
61
41
  ```bash
62
- rails g pu:pkg:portal admin
42
+ rails g pu:res:scaffold Blogging::Post title:string --dest=blogging
43
+ rails db:migrate
63
44
  ```
64
45
 
65
- ### Generator Options
66
-
67
- | Option | Description |
68
- |--------|-------------|
69
- | `--auth=NAME` | Rodauth account to authenticate with |
70
- | `--public` | Grant public access (no authentication) |
71
- | `--byo` | Bring your own authentication |
46
+ ### 4. Expose it via a portal
72
47
 
73
48
  ```bash
74
- # Non-interactive examples
75
- rails g pu:pkg:portal admin --auth=admin
76
- rails g pu:pkg:portal api --public
77
- rails g pu:pkg:portal custom --byo
78
- ```
79
-
80
- Without flags, the generator prompts interactively.
81
-
82
- ### Generated Structure
83
-
84
- ```
85
- packages/admin_portal/
86
- ├── app/
87
- │ ├── controllers/admin_portal/
88
- │ │ ├── concerns/controller.rb
89
- │ │ ├── dashboard_controller.rb
90
- │ │ ├── plutonium_controller.rb
91
- │ │ └── resource_controller.rb
92
- │ ├── definitions/admin_portal/
93
- │ │ └── resource_definition.rb
94
- │ ├── policies/admin_portal/
95
- │ │ └── resource_policy.rb
96
- │ └── views/admin_portal/
97
- │ └── dashboard/index.html.erb
98
- ├── config/
99
- │ └── routes.rb
100
- └── lib/
101
- └── engine.rb
102
- ```
103
-
104
- ### Portal Engine
105
-
106
- ```ruby
107
- # packages/admin_portal/lib/engine.rb
108
- module AdminPortal
109
- class Engine < Rails::Engine
110
- include Plutonium::Portal::Engine
111
-
112
- config.after_initialize do
113
- # Multi-tenancy (optional)
114
- scope_to_entity Organization, strategy: :path
115
- end
116
- end
117
- end
49
+ rails g pu:res:conn Blogging::Post --dest=admin_portal
118
50
  ```
119
51
 
120
- ### Portal Authentication
52
+ ## Portal package
121
53
 
122
- Authentication is configured in the controller concern based on generator options:
54
+ ### 1. Generate
123
55
 
124
- ```ruby
125
- # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
126
- module AdminPortal
127
- module Concerns
128
- module Controller
129
- extend ActiveSupport::Concern
130
- include Plutonium::Portal::Controller
131
- include Plutonium::Auth::Rodauth(:admin)
132
- end
133
- end
134
- end
135
- ```
136
-
137
- For public access:
138
-
139
- ```ruby
140
- include Plutonium::Auth::Public
56
+ ```bash
57
+ rails g pu:pkg:portal admin --auth=user
141
58
  ```
142
59
 
143
- For custom authentication:
60
+ Options:
144
61
 
145
- ```ruby
146
- included do
147
- helper_method :current_user
148
- end
62
+ - `--auth=NAME` — Rodauth account to authenticate with.
63
+ - `--public` — public access, no auth.
64
+ - `--byo` — bring your own auth.
65
+ - `--scope=CLASS` — entity class for multi-tenancy.
149
66
 
150
- def current_user
151
- # Your authentication logic
152
- @current_user ||= User.find_by(api_key: request.headers["X-API-Key"])
153
- end
154
- ```
155
-
156
- ## Portal Routes
157
-
158
- The portal generator creates routes and auto-mounts to the main app:
67
+ ### 2. Mount it
159
68
 
160
69
  ```ruby
161
- # packages/admin_portal/config/routes.rb
162
- AdminPortal::Engine.routes.draw do
163
- root to: "dashboard#index"
164
-
165
- # Register resources here
166
- register_resource ::Post
167
- register_resource Blogging::Comment
168
- end
169
-
170
- # Also adds to main app routes:
171
- # config/routes.rb (auto-generated)
70
+ # config/routes.rb
172
71
  Rails.application.routes.draw do
173
- constraints Rodauth::Rails.authenticate(:admin) do
72
+ constraints Rodauth::Rails.authenticate(:user) do
174
73
  mount AdminPortal::Engine, at: "/admin"
175
74
  end
176
75
  end
177
76
  ```
178
77
 
179
- ### Custom Routes on Resources
180
-
181
- Add member or collection routes with a block:
182
-
183
- ```ruby
184
- register_resource ::Post do
185
- member do
186
- get :preview
187
- post :publish
188
- end
189
- collection do
190
- get :archived
191
- end
192
- end
193
- ```
194
-
195
- ## Package Loading
196
-
197
- Packages are loaded via `config/packages.rb` (generated during install):
198
-
199
- ```ruby
200
- # config/packages.rb
201
- Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
202
- load package
203
- end
204
- ```
205
-
206
- This is automatically required in `config/application.rb`.
207
-
208
- ## Adding Resources to Packages
78
+ ### 3. Connect resources
209
79
 
210
80
  ```bash
211
- # Add to main app
212
- rails g pu:res:scaffold Post title:string --dest=main_app
213
-
214
- # Add to a feature package
215
- rails g pu:res:scaffold Post title:string --dest=blogging
81
+ rails g pu:res:conn Post Blogging::Post --dest=admin_portal
216
82
  ```
217
83
 
218
- Resources are namespaced:
84
+ See [Reference › App › Portals](/reference/app/portals) for the full portal surface.
219
85
 
220
- ```ruby
221
- # packages/blogging/app/models/blogging/post.rb
222
- module Blogging
223
- class Post < Blogging::ResourceRecord
224
- # Model code
225
- end
226
- end
227
- ```
228
-
229
- ## Connecting Resources to Portals
86
+ ## Auto-namespacing
230
87
 
231
- Resources must be connected to portals to be accessible:
88
+ Every file under `app/<kind>/blogging/` resolves to `Blogging::*`:
232
89
 
233
- ```bash
234
- # Connect main app resource
235
- rails g pu:res:conn Post --dest=admin_portal
236
-
237
- # Connect namespaced resource
238
- rails g pu:res:conn Blogging::Post --dest=admin_portal
239
- ```
240
-
241
- ## Entity Scoping (Multi-tenancy)
242
-
243
- Automatically scope all data to a parent entity:
90
+ - `app/models/blogging/post.rb` → `Blogging::Post`
91
+ - `app/policies/blogging/post_policy.rb` `Blogging::PostPolicy`
244
92
 
245
- ### Path Strategy
93
+ Each feature package gets base classes — `Blogging::ApplicationRecord`, `Blogging::ResourceRecord`, `Blogging::ResourcePolicy`, `Blogging::ResourceDefinition`, `Blogging::ResourceInteraction` — that inherit from the main app's.
246
94
 
247
- Entity ID in URL path:
95
+ ## Cross-package references
248
96
 
249
- ```ruby
250
- # packages/admin_portal/lib/engine.rb
251
- config.after_initialize do
252
- scope_to_entity Organization, strategy: :path
253
- end
97
+ ```bash
98
+ rails g pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text --dest=comments
254
99
  ```
255
100
 
256
- Routes become: `/organizations/:organization_id/posts`
101
+ The `blogging/post` syntax expands to `Blogging::Post`.
257
102
 
258
- ### Custom Strategy
103
+ ## When to use which
259
104
 
260
- Implement your own lookup method:
105
+ ### Feature package
261
106
 
262
- ```ruby
263
- config.after_initialize do
264
- scope_to_entity Organization, strategy: :current_organization
265
- end
107
+ When the code:
266
108
 
267
- # In controller concern
268
- def current_organization
269
- @current_organization ||= Organization.find_by!(subdomain: request.subdomain)
270
- end
271
- ```
109
+ - Could be reused across multiple portals (admin and customer both edit `Blogging::Post`).
110
+ - Has no inherent UI / auth.
111
+ - You want isolated from other domains (`billing` shouldn't depend on `blogging`).
272
112
 
273
- ## Package Best Practices
113
+ ### Portal package
274
114
 
275
- ### 1. Single Responsibility
276
- Each feature package should handle one domain:
277
- - `blogging` - Posts, comments, categories
278
- - `inventory` - Products, stock, warehouses
279
- - `billing` - Invoices, payments, subscriptions
115
+ When the code:
280
116
 
281
- ### 2. Clear Naming
282
- - Feature packages: domain nouns (`blogging`, `billing`)
283
- - Portal packages: role + portal (`admin_portal`, `api_portal`)
117
+ - Has a specific auth flow (admin vs customer vs public).
118
+ - Renders different views of the same underlying resources.
119
+ - Needs different policies / definitions per audience.
284
120
 
285
- ### 3. Minimal Cross-Dependencies
286
- Limit dependencies between feature packages. If two packages are tightly coupled, consider merging them.
121
+ ### When NOT to make a package
287
122
 
288
- ### 4. Portal Customization
289
- Put UI customizations in portal packages, not feature packages:
123
+ For an app that doesn't need cross-portal sharing, just put resources in `--dest=main_app`. Packages add organization, not power.
290
124
 
291
- ```ruby
292
- # Good: Portal-specific definition
293
- # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
294
-
295
- # Bad: Feature package with portal-specific code
296
- # packages/blogging/app/definitions/blogging/admin_post_definition.rb
297
- ```
298
-
299
- ## Multiple Portals Pattern
300
-
301
- Common pattern for different user types:
125
+ ## Typical architecture
302
126
 
303
127
  ```
304
128
  packages/
305
- ├── blogging/ # Feature: blog functionality
306
- ├── billing/ # Feature: payment/invoicing
307
- ├── admin_portal/ # Portal: admin interface
308
- ├── dashboard_portal/ # Portal: user dashboard
309
- └── public_portal/ # Portal: public read-only
129
+ ├── blogging/ # Feature: blog functionality
130
+ ├── billing/ # Feature: payments/invoicing
131
+ ├── admin_portal/ # Portal: admin interface
132
+ └── customer_portal/ # Portal: customer dashboard
310
133
  ```
311
134
 
312
- Each portal can:
313
- - Have different authentication
314
- - Show different fields
315
- - Allow different actions
316
- - Use different layouts
135
+ The portals expose the features. A single feature can be exposed by multiple portals — usually with different policies and definitions per portal.
317
136
 
318
- ## Portal-Specific Overrides
137
+ ## Package loading
319
138
 
320
- ### Override Definition
139
+ Generated by `pu:core:install`:
321
140
 
322
141
  ```ruby
323
- # packages/admin_portal/app/definitions/admin_portal/post_definition.rb
324
- class AdminPortal::PostDefinition < ::PostDefinition
325
- # Add portal-specific scopes
326
- scope :my_posts, -> { where(user: current_user) }
142
+ # config/packages.rb
143
+ Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
144
+ load package
327
145
  end
328
146
  ```
329
147
 
330
- ### Override Policy
331
-
332
- ```ruby
333
- # packages/admin_portal/app/policies/admin_portal/post_policy.rb
334
- class AdminPortal::PostPolicy < ::PostPolicy
335
- include AdminPortal::ResourcePolicy
336
-
337
- def destroy?
338
- true # Admins can delete
339
- end
340
-
341
- def permitted_attributes_for_create
342
- %i[title content featured internal_notes] # More fields
343
- end
344
- end
345
- ```
148
+ Loaded from `config/application.rb`. Migrations from all packages are picked up by `rails db:migrate` automatically.
346
149
 
347
- ### Override Controller
150
+ ## Per-portal overrides
348
151
 
349
152
  ```ruby
350
- # packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
351
- class AdminPortal::PostsController < ::PostsController
352
- include AdminPortal::Concerns::Controller
153
+ # Definition — different fields per portal
154
+ class AdminPortal::PostDefinition < ::PostDefinition
155
+ input :internal_notes, as: :text # admins see this; customers don't
156
+ scope :pending_review
157
+ end
353
158
 
354
- private
159
+ # Policy — different rules per portal
160
+ class AdminPortal::PostPolicy < ::PostPolicy
161
+ include AdminPortal::ResourcePolicy
355
162
 
356
- def preferred_action_after_submit
357
- "index"
358
- end
163
+ def destroy? = true
164
+ def permitted_attributes_for_create = %i[title content featured internal_notes]
359
165
  end
360
166
  ```
361
167
 
362
- ## Controller Hierarchy
363
-
364
- Portal controllers inherit from the feature package's controller if one exists (and include the portal's `Concerns::Controller`). If no feature package controller exists, they inherit from the portal's `ResourceController`.
168
+ ## Common issues
365
169
 
366
- ```ruby
367
- # With feature package controller:
368
- class AdminPortal::PostsController < ::PostsController
369
- include AdminPortal::Concerns::Controller
370
- end
371
-
372
- # Without feature package controller:
373
- class AdminPortal::PostsController < AdminPortal::ResourceController
374
- end
375
- ```
170
+ - **Class not loading** — namespace must match the directory: `app/models/blogging/post.rb` MUST be `Blogging::Post`.
171
+ - **Migration not running** — package migrations are auto-included. If they aren't running, check `config/packages.rb` is loaded from `application.rb`.
172
+ - **Cross-package association fails** — use `blogging/post:belongs_to` in `pu:res:scaffold`, OR manually set `class_name: "Blogging::Post"` on the `belongs_to`.
376
173
 
377
174
  ## Related
378
175
 
379
- - [Adding Resources](./adding-resources)
380
- - [Authentication](./authentication)
381
- - [Multi-tenancy](./multi-tenancy)
176
+ - [Reference › App › Packages](/reference/app/packages) — full package surface
177
+ - [Reference › App › Portals](/reference/app/portals) — portal-specific configuration
178
+ - [Adding resources](./adding-resources) — `pu:res:scaffold` and `pu:res:conn`
179
+ - [Authentication](./authentication) — portal auth setup