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
@@ -0,0 +1,517 @@
1
+ # Generators
2
+
3
+ Plutonium's `pu:*` CLI generators. Discoverable via `rails g pu:<tab>`. Always pass `--dest=` to skip prompts.
4
+
5
+ ## Catalog
6
+
7
+ | Generator | Purpose |
8
+ |---|---|
9
+ | [`pu:core:install`](#pu-core-install) | Initial Plutonium setup (base classes, config, layouts) |
10
+ | [`pu:core:assets`](#pu-core-assets) | Set up custom Tailwind + Stimulus toolchain |
11
+ | [`pu:res:scaffold`](#pu-res-scaffold) | New resource (model, migration, controller, policy, definition) |
12
+ | [`pu:res:conn`](#pu-res-conn) | Connect a resource to a portal |
13
+ | [`pu:pkg:package`](#pu-pkg-package) | New feature package |
14
+ | [`pu:pkg:portal`](#pu-pkg-portal) | New portal package |
15
+ | [`pu:rodauth:install`](#pu-rodauth-install) | Install Rodauth base |
16
+ | [`pu:rodauth:account`](#pu-rodauth-account) | Basic Rodauth account |
17
+ | [`pu:rodauth:admin`](#pu-rodauth-admin) | Hardened admin account (2FA, lockout, audit) |
18
+ | [`pu:saas:setup`](#pu-saas-setup) | **Meta** — user + entity + membership + portal + profile + welcome + invites |
19
+ | [`pu:saas:user`](#individual-saas-generators) | Individual: SaaS user account |
20
+ | [`pu:saas:entity`](#individual-saas-generators) | Individual: entity model |
21
+ | [`pu:saas:membership`](#individual-saas-generators) | Individual: membership join model |
22
+ | [`pu:saas:portal`](#individual-saas-generators) | Individual: entity-scoped portal |
23
+ | [`pu:saas:welcome`](#individual-saas-generators) | Individual: onboarding / select-entity flow |
24
+ | [`pu:saas:api_client`](#pu-saas-api-client) | API client for M2M auth |
25
+ | [`pu:profile:install`](#pu-profile-install) | Profile resource + security section |
26
+ | [`pu:profile:setup`](#pu-profile-setup) | Meta — `pu:profile:install` + `pu:profile:conn` |
27
+ | [`pu:profile:conn`](#pu-profile-conn) | Connect profile to a portal as a singular resource |
28
+ | [`pu:invites:install`](#pu-invites-install) | User invitations package |
29
+ | [`pu:invites:invitable`](#pu-invites-invitable) | Mark a model as invitable |
30
+ | [`pu:eject:layout`](#pu-eject-layout) | Eject base layout for customization |
31
+ | [`pu:eject:shell`](#pu-eject-shell) | Eject topbar/sidebar partials |
32
+ | [`pu:test:install`](#pu-test-install) | Install `Plutonium::Testing` scaffolding |
33
+ | [`pu:test:scaffold`](#pu-test-scaffold) | Scaffold integration tests per (resource × portal) |
34
+ | [`pu:core:update`](#pu-core-update) | Update plutonium gem + npm package |
35
+ | [`pu:skills:sync`](#pu-skills-sync) | Sync Plutonium Claude skills into the project |
36
+
37
+ ::: tip Unattended mode
38
+ All generators block on prompts by default. Pass `--dest=`, `--auth=`, `--force`, `--skip-bundle`, `--quiet` etc. to run in scripts/CI. See [App › Unattended execution](./index#unattended-execution).
39
+ :::
40
+
41
+ ---
42
+
43
+ ## Resource generators
44
+
45
+ ### `pu:res:scaffold`
46
+
47
+ Generate a complete resource: model, migration, controller, policy, definition.
48
+
49
+ ```bash
50
+ rails g pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
51
+ ```
52
+
53
+ | Option | Description |
54
+ |---|---|
55
+ | `--dest=NAME` | Destination package (`main_app` or `<package>`) — required for unattended runs |
56
+ | `--no-model` | Skip model file (for existing models) |
57
+ | `--no-migration` | Skip migration (use with `--no-model` for existing schema) |
58
+
59
+ Field type syntax — full reference in [Resource › Model](/reference/resource/model). Quick recap:
60
+
61
+ ```bash
62
+ 'name:string' # required string
63
+ 'name:string?' # nullable
64
+ 'company:belongs_to' # association
65
+ 'parent:belongs_to?' # nullable association
66
+ 'email:string:uniq' # with unique index
67
+ 'amount:decimal{10,2}' # decimal with precision
68
+ 'status:string{default:draft}' # with default value
69
+ 'metadata:jsonb{default:{}}' # JSON default
70
+ ```
71
+
72
+ Quote any field containing `?` or `{}` to prevent shell expansion.
73
+
74
+ ### `pu:res:conn`
75
+
76
+ Connect a resource to a portal. Generates portal-specific controller, policy, definition + route registration.
77
+
78
+ ```bash
79
+ rails g pu:res:conn Post Comment Tag --dest=admin_portal
80
+ rails g pu:res:conn Blogging::Post --dest=admin_portal # namespaced
81
+ rails g pu:res:conn Profile --dest=customer_portal --singular # singleton
82
+ ```
83
+
84
+ | Option | Description |
85
+ |---|---|
86
+ | `--dest=PORTAL` | Target portal (required) |
87
+ | `--singular` | Register as singular resource (`/profile`, no `:id`, no index) |
88
+
89
+ ::: tip Run after migrations
90
+ The generator reads model columns to seed the policy's `permitted_attributes_for_*`.
91
+ :::
92
+
93
+ See [Portals › Connecting resources](./portals#connecting-resources-pu-res-conn) for full details.
94
+
95
+ ---
96
+
97
+ ## Package generators
98
+
99
+ ### `pu:pkg:package`
100
+
101
+ Feature package — models, policies, definitions, interactions.
102
+
103
+ ```bash
104
+ rails g pu:pkg:package blogging
105
+ ```
106
+
107
+ See [Packages › Feature packages](./packages#feature-packages).
108
+
109
+ ### `pu:pkg:portal`
110
+
111
+ Portal package — controllers, views, routes, auth.
112
+
113
+ ```bash
114
+ rails g pu:pkg:portal admin --auth=user
115
+ rails g pu:pkg:portal admin --auth=admin --scope=Organization
116
+ ```
117
+
118
+ | Option | Description |
119
+ |---|---|
120
+ | `--auth=NAME` | Rodauth account to authenticate with |
121
+ | `--public` | Public access (no auth) |
122
+ | `--byo` | Bring your own auth |
123
+ | `--scope=CLASS` | Entity class for multi-tenancy |
124
+
125
+ See [Portals › Creating a portal](./portals#creating-a-portal).
126
+
127
+ ---
128
+
129
+ ## Authentication generators
130
+
131
+ ### `pu:rodauth:install`
132
+
133
+ Install the Rodauth base — Roda app, base plugin, controller, layout, PostgreSQL extension migration.
134
+
135
+ ```bash
136
+ rails g pu:rodauth:install
137
+ ```
138
+
139
+ ### `pu:rodauth:account`
140
+
141
+ Basic Rodauth account with configurable features.
142
+
143
+ ```bash
144
+ rails g pu:rodauth:account user # interactive
145
+ rails g pu:rodauth:account user --defaults # standard features
146
+ rails g pu:rodauth:account user --kitchen_sink # ALL features
147
+ rails g pu:rodauth:account api_user --api_only --jwt --jwt_refresh
148
+ ```
149
+
150
+ For full option tables (features, defaults, individual feature flags) see [Auth › Accounts](/reference/auth/accounts).
151
+
152
+ ### `pu:rodauth:admin`
153
+
154
+ Hardened admin account — pre-configured with multi-phase login, required TOTP, recovery codes, lockout, active sessions, audit logging, role-based access, invite interaction, and **no public signup**.
155
+
156
+ ```bash
157
+ rails g pu:rodauth:admin admin
158
+ rails g pu:rodauth:admin admin --roles=super_admin,admin,viewer
159
+ rails g pu:rodauth:admin admin --extra-attributes=name:string,department:string
160
+ ```
161
+
162
+ | Option | Default | Description |
163
+ |---|---|---|
164
+ | `--roles` | `super_admin,admin` | Comma-separated roles (positional enum, index 0 most privileged) |
165
+ | `--extra_attributes` | | Additional model attributes |
166
+
167
+ Creates a rake task for account creation:
168
+
169
+ ```bash
170
+ rails rodauth_admin:create[admin@example.com,password123]
171
+ ```
172
+
173
+ ---
174
+
175
+ ## SaaS generators
176
+
177
+ ### `pu:saas:setup`
178
+
179
+ **Meta-generator.** Creates the user + entity + membership trio AND runs:
180
+
181
+ - `pu:saas:portal` → entity-scoped `{Entity}Portal`
182
+ - `pu:profile:setup` → profile model + association
183
+ - `pu:saas:welcome` → onboarding / select-entity flow
184
+ - `pu:invites:install` → invitations package
185
+
186
+ ```bash
187
+ rails g pu:saas:setup --user Customer --entity Organization
188
+ rails g pu:saas:setup --user Customer --entity Organization --roles=member,admin
189
+ rails g pu:saas:setup --user Customer --entity Organization --no-allow-signup
190
+ rails g pu:saas:setup --user Customer --entity Organization \
191
+ --user-attributes=name:string --entity-attributes=slug:string
192
+ ```
193
+
194
+ | Option | Default | Description |
195
+ |---|---|---|
196
+ | `--user=NAME` | (required) | User account model name |
197
+ | `--entity=NAME` | (required) | Entity model name |
198
+ | `--allow-signup` | `true` | Allow public registration |
199
+ | `--roles` | `admin,member` | Additional roles — **`owner` always prepended as index 0** |
200
+ | `--skip-entity` | | Skip entity model generation |
201
+ | `--skip-membership` | | Skip membership model generation |
202
+ | `--user-attributes` | | Additional user model attributes |
203
+ | `--entity-attributes` | | Additional entity model attributes |
204
+ | `--membership-attributes` | | Additional membership model attributes |
205
+ | `--api_client=NAME` | | Also generate an API client model |
206
+ | `--api_client_roles` | `read_only,write,admin` | API client roles |
207
+
208
+ ::: warning Don't re-run pieces manually
209
+ `pu:saas:setup` chains four other generators. Don't re-run portal / profile / welcome / invites separately after this. Pass `--force` to re-run the whole thing.
210
+ :::
211
+
212
+ ### Individual SaaS generators
213
+
214
+ For when you don't want the full `pu:saas:setup` meta-generator:
215
+
216
+ ```bash
217
+ rails g pu:saas:user Customer
218
+ rails g pu:saas:entity Organization --extra-attributes=slug:string
219
+ rails g pu:saas:membership --user Customer --entity Organization --roles=member,admin
220
+ rails g pu:saas:portal customer --entity Organization
221
+ rails g pu:saas:welcome --user Customer --entity Organization
222
+ ```
223
+
224
+ ### `pu:saas:api_client`
225
+
226
+ API client account for machine-to-machine authentication. HTTP Basic Auth with auto-generated password.
227
+
228
+ ```bash
229
+ rails g pu:saas:api_client ApiClient
230
+ rails g pu:saas:api_client ApiClient --entity=Organization
231
+ rails g pu:saas:api_client ApiClient --entity=Organization --roles=read_only,write,admin
232
+ ```
233
+
234
+ | Option | Default | Description |
235
+ |---|---|---|
236
+ | `--entity=NAME` | | Entity to scope API clients to |
237
+ | `--roles` | `read_only,write,admin` | Available roles |
238
+ | `--extra_attributes` | | Additional model attributes |
239
+ | `--dest` | `main_app` | Destination package |
240
+
241
+ CLI creation:
242
+
243
+ ```bash
244
+ rake api_clients:create LOGIN=my-service
245
+ rake api_clients:create LOGIN=my-service ORGANIZATION=acme ROLE=write
246
+ ```
247
+
248
+ ::: warning Credentials shown once
249
+ The auto-generated password is displayed once at creation and cannot be retrieved later (`SecureRandom.base64(32)`).
250
+ :::
251
+
252
+ ---
253
+
254
+ ## Profile generators
255
+
256
+ See [Auth › Profile](/reference/auth/profile) for the full profile feature.
257
+
258
+ ### `pu:profile:install`
259
+
260
+ Generate the profile resource (model, migration, controller, policy, definition) and modify the user model to add `has_one :profile`.
261
+
262
+ ```bash
263
+ rails g pu:profile:install bio:text avatar:attachment 'timezone:string?' --dest=customer
264
+ rails g pu:profile:install AccountSettings bio:text --dest=main_app # custom resource name
265
+ ```
266
+
267
+ | Option | Default | Description |
268
+ |---|---|---|
269
+ | `--dest=DEST` | (prompts) | Target package or `main_app` |
270
+ | `--user-model=NAME` | `User` | Rodauth user model |
271
+
272
+ ### `pu:profile:setup`
273
+
274
+ Meta — runs `pu:profile:install` + `pu:profile:conn` in one shot.
275
+
276
+ ```bash
277
+ rails g pu:profile:setup date_of_birth:date bio:text \
278
+ --dest=competition \
279
+ --portal=competition_portal
280
+ ```
281
+
282
+ ### `pu:profile:conn`
283
+
284
+ Connect the profile resource to a portal as a **singular** resource (registers `/profile` and the `profile_url` helper).
285
+
286
+ ```bash
287
+ rails g pu:profile:conn --dest=customer_portal
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Invite generators
293
+
294
+ See [Tenancy › Invites](/reference/tenancy/invites) for the full invitation system.
295
+
296
+ ### `pu:invites:install`
297
+
298
+ ```bash
299
+ rails g pu:invites:install
300
+ rails g pu:invites:install --entity-model=Organization --user-model=Customer --invite-model=OrganizationInvite
301
+ ```
302
+
303
+ | Option | Default | Description |
304
+ |---|---|---|
305
+ | `--entity-model=NAME` | `Entity` | Entity model name |
306
+ | `--user-model=NAME` | `User` | User model name |
307
+ | `--invite-model=NAME` | `<EntityModel><UserModel>Invite` | Invite class name |
308
+ | `--membership-model=NAME` | `EntityUser` | Membership join model |
309
+ | `--roles` | `member,admin` | Comma-separated |
310
+ | `--rodauth=NAME` | `user` | Rodauth configuration for signup |
311
+ | `--enforce-domain` | `false` | Require email domain to match entity |
312
+
313
+ Multiple invite flows are supported — run `pu:invites:install` once per flow.
314
+
315
+ ### `pu:invites:invitable`
316
+
317
+ Mark an app model as invitable (gets notified when an invite is accepted via `on_invite_accepted`).
318
+
319
+ ```bash
320
+ rails g pu:invites:invitable Tenant
321
+ rails g pu:invites:invitable TeamMember --role=member
322
+ rails g pu:invites:invitable Tenant --dest=my_package
323
+ ```
324
+
325
+ | Option | Default | Description |
326
+ |---|---|---|
327
+ | `--role=ROLE` | `member` | Role to assign to invited users |
328
+ | `--user-model=NAME` | `User` | User model |
329
+ | `--membership-model=NAME` | `EntityUser` | Membership join model |
330
+ | `--dest=PACKAGE` | `main_app` | Destination package |
331
+ | `--[no-]email-templates` | `true` | Generate custom email templates |
332
+
333
+ ---
334
+
335
+ ## Core generators
336
+
337
+ ### `pu:core:install`
338
+
339
+ Initial Plutonium setup. Creates base classes, config initializer, layouts. Run once per app.
340
+
341
+ ```bash
342
+ rails g pu:core:install
343
+ ```
344
+
345
+ ### `pu:core:assets`
346
+
347
+ Set up the custom Tailwind + Stimulus toolchain. Installs npm packages, creates `tailwind.config.js`, imports Plutonium CSS, registers Stimulus controllers.
348
+
349
+ ```bash
350
+ rails g pu:core:assets
351
+ ```
352
+
353
+ See [UI › Assets](/reference/ui/assets) for what gets configured.
354
+
355
+ ### `pu:core:update`
356
+
357
+ Update the plutonium gem and npm package together.
358
+
359
+ ```bash
360
+ rails g pu:core:update
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Eject generators
366
+
367
+ ### `pu:eject:layout`
368
+
369
+ Copy the base layout template into your portal for direct editing.
370
+
371
+ ```bash
372
+ rails g pu:eject:layout
373
+ ```
374
+
375
+ ### `pu:eject:shell`
376
+
377
+ Copy the topbar/sidebar partials into your portal for direct editing.
378
+
379
+ ```bash
380
+ rails g pu:eject:shell --dest=admin_portal
381
+ ```
382
+
383
+ See [UI › Layouts](/reference/ui/layouts).
384
+
385
+ ---
386
+
387
+ ## Test generators
388
+
389
+ See [Testing](/reference/testing/) for the full testing toolkit.
390
+
391
+ ### `pu:test:install`
392
+
393
+ Install the testing scaffolding (require `plutonium/testing` in `test_helper.rb`; create `test/support/plutonium_testing.rb` for overrides). Run once.
394
+
395
+ ```bash
396
+ rails g pu:test:install
397
+ ```
398
+
399
+ ### `pu:test:scaffold`
400
+
401
+ Scaffold integration tests — one file per (resource × portal) pairing.
402
+
403
+ ```bash
404
+ rails g pu:test:scaffold Blogging::Post --portals=admin,org
405
+ rails g pu:test:scaffold Blogging::Post --portals=admin --concerns=crud,policy,definition
406
+ rails g pu:test:scaffold Blogging::Post --portals=org --parent=organization --dest=blogging
407
+ ```
408
+
409
+ | Option | Default | Description |
410
+ |---|---|---|
411
+ | `--portals=admin,org` | (required) | Emit one file per portal |
412
+ | `--concerns=...` | `crud,policy,definition` | Concerns to include (full list: `crud,policy,definition,nested,model,interaction,portal_access`) |
413
+ | `--parent=organization` | | Wires the `NestedResource` parent |
414
+ | `--dest=...` | `main_app` | Output destination |
415
+
416
+ ---
417
+
418
+ ## Skill generators
419
+
420
+ ### `pu:skills:sync`
421
+
422
+ Sync Plutonium's Claude Code skills into the project (`.claude/skills/`). Run when upgrading the gem.
423
+
424
+ ```bash
425
+ rails g pu:skills:sync
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Common workflows
431
+
432
+ ### Full app setup
433
+
434
+ ```bash
435
+ # 1. Plutonium template (greenfield) — does all initial setup
436
+ rails new myapp -a propshaft -j esbuild -c tailwind \
437
+ -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
438
+
439
+ # Or for existing app:
440
+ rails generate pu:core:install
441
+ rails generate pu:rodauth:install
442
+
443
+ # 2. Account types
444
+ rails generate pu:rodauth:admin admin
445
+ # (or pu:saas:setup for multi-tenant)
446
+
447
+ # 3. Resources
448
+ rails generate pu:res:scaffold Post title:string body:text --dest=main_app
449
+ rails generate pu:res:scaffold Comment body:text post:belongs_to --dest=main_app
450
+
451
+ # 4. Portal
452
+ rails generate pu:pkg:portal admin --auth=admin
453
+
454
+ # 5. Connect resources
455
+ rails generate pu:res:conn Post Comment --dest=admin_portal
456
+
457
+ # 6. Migrate
458
+ rails db:migrate
459
+
460
+ # 7. Create the first admin
461
+ rails rodauth_admin:create[admin@example.com,password123]
462
+ ```
463
+
464
+ ### Adding a new resource
465
+
466
+ ```bash
467
+ rails g pu:res:scaffold Product name:string price_cents:integer --dest=main_app
468
+ rails db:migrate
469
+ rails g pu:res:conn Product --dest=admin_portal
470
+ ```
471
+
472
+ ### Adding a new portal
473
+
474
+ ```bash
475
+ rails g pu:pkg:portal customer --auth=user --scope=Organization
476
+ rails g pu:res:conn Order --dest=customer_portal
477
+ rails db:migrate
478
+ ```
479
+
480
+ ## Undoing generators
481
+
482
+ ```bash
483
+ rails destroy pu:res:scaffold Post
484
+ rails destroy pu:pkg:portal admin
485
+ ```
486
+
487
+ ## Troubleshooting
488
+
489
+ ### Generator not found
490
+
491
+ Ensure Plutonium is installed and bundle is up to date:
492
+
493
+ ```ruby
494
+ # Gemfile
495
+ gem "plutonium"
496
+ ```
497
+
498
+ ```bash
499
+ bundle install
500
+ ```
501
+
502
+ ### Package not found
503
+
504
+ Generators run from Rails root. Package names are case-sensitive.
505
+
506
+ ### Migration already exists
507
+
508
+ If a migration with the same timestamp exists, wait a second and retry — Rails generates timestamps to one-second resolution.
509
+
510
+ ## Related
511
+
512
+ - [Packages](./packages) — feature vs portal package structure
513
+ - [Portals](./portals) — portal configuration and resource connection
514
+ - [Resource › Model](/reference/resource/model) — field-type syntax for `pu:res:scaffold`
515
+ - [Auth](/reference/auth/) — account type configuration
516
+ - [Tenancy](/reference/tenancy/) — multi-tenancy and invitations
517
+ - [Testing](/reference/testing/) — test scaffolding
@@ -0,0 +1,158 @@
1
+ # App Reference
2
+
3
+ How a Plutonium app is assembled: installation, the package system (feature vs portal), portal engines, route registration, and connecting resources to portals.
4
+
5
+ ## Sub-pages
6
+
7
+ - [Packages](./packages) — feature vs portal packages, structure, namespacing, package loading
8
+ - [Portals](./portals) — portal engines, mounting, controller concerns, `register_resource` (including singular and custom routes), connecting resources via `pu:res:conn`
9
+ - [Generators](./generators) — full `pu:*` generator catalog
10
+
11
+ ## Installation
12
+
13
+ ### New Rails app (recommended)
14
+
15
+ ```bash
16
+ rails new myapp -a propshaft -j esbuild -c tailwind \
17
+ -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
18
+ ```
19
+
20
+ Configures Rails + Propshaft + esbuild + TailwindCSS + Plutonium in one shot.
21
+
22
+ ### Existing Rails app
23
+
24
+ ::: danger Existing app → `base.rb`, not `plutonium.rb`
25
+ The `plutonium.rb` template re-runs the full app bootstrap (dotenv, annotate, solid_*, asset config) and creates generic "initial commit" commits that clobber history. For any pre-existing app, always use `base.rb`.
26
+ :::
27
+
28
+ ```bash
29
+ # Template
30
+ bin/rails app:template \
31
+ LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
32
+
33
+ # Or manual — add `gem "plutonium"` to Gemfile, then:
34
+ bundle install
35
+ rails generate pu:core:install
36
+ ```
37
+
38
+ ## Full setup workflow
39
+
40
+ ```bash
41
+ # 1. Core install — base controllers, policies, definitions, layouts
42
+ rails generate pu:core:install
43
+
44
+ # 2. Auth (if needed)
45
+ rails generate pu:rodauth:install
46
+ rails generate pu:rodauth:account user
47
+
48
+ # 3. Portal
49
+ rails generate pu:pkg:portal admin --auth=user
50
+
51
+ # 4. First resource
52
+ rails generate pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
53
+ rails db:migrate
54
+
55
+ # 5. Connect resource to portal
56
+ rails generate pu:res:conn Post --dest=admin_portal
57
+
58
+ # 6. Mount portal in config/routes.rb
59
+ # mount AdminPortal::Engine, at: "/admin"
60
+
61
+ # 7. Start
62
+ bin/dev # uses Procfile to run Rails + CSS watcher
63
+ ```
64
+
65
+ Visit `http://localhost:3000/admin`.
66
+
67
+ ## What `pu:core:install` creates
68
+
69
+ ```
70
+ app/
71
+ ├── controllers/
72
+ │ ├── plutonium_controller.rb # non-resource base
73
+ │ └── resource_controller.rb # CRUD base — see Behavior › Controllers
74
+ ├── definitions/resource_definition.rb
75
+ ├── interactions/resource_interaction.rb
76
+ ├── models/resource_record.rb # abstract model — includes Plutonium::Resource::Record
77
+ ├── policies/resource_policy.rb
78
+ └── views/layouts/resource.html.erb
79
+
80
+ config/
81
+ ├── initializers/plutonium.rb
82
+ └── packages.rb # auto-loads packages/**/lib/engine.rb
83
+
84
+ packages/.keep
85
+ ```
86
+
87
+ The base classes (`ResourceController`, `ResourcePolicy`, `ResourceDefinition`, `ResourceRecord`, `ResourceInteraction`) are where you put app-wide defaults; resource-specific subclasses come from `pu:res:scaffold`.
88
+
89
+ ## Configuration
90
+
91
+ ```ruby
92
+ # config/initializers/plutonium.rb
93
+ Plutonium.configure do |config|
94
+ config.load_defaults 1.0
95
+
96
+ # Hot reloading (defaults to true in development)
97
+ # config.enable_hotreload = true
98
+
99
+ # Cache discovery (defaults to true in production, false in development)
100
+ # config.cache_discovery = false
101
+
102
+ # Page chrome. Default :modern (topbar + icon rail).
103
+ # :classic preserves the legacy header + sidebar (only when upgrading).
104
+ # config.shell = :classic
105
+
106
+ # Custom assets — see UI › Assets
107
+ # config.assets.stylesheet = "custom_stylesheet"
108
+ # config.assets.script = "custom_script"
109
+ # config.assets.logo = "custom_logo.png"
110
+ # config.assets.favicon = "custom_favicon.ico"
111
+ end
112
+ ```
113
+
114
+ ## Converting an existing model to a resource
115
+
116
+ ```ruby
117
+ # 1. Include the module on your model
118
+ class Post < ApplicationRecord
119
+ include Plutonium::Resource::Record
120
+ end
121
+ ```
122
+
123
+ ```bash
124
+ # 2. Generate supporting files (skips model + migration)
125
+ rails g pu:res:scaffold Post --no-migration --dest=main_app
126
+
127
+ # 3. Connect to portal
128
+ rails g pu:res:conn Post --dest=admin_portal
129
+ ```
130
+
131
+ ## Verifying installation
132
+
133
+ ```bash
134
+ rails runner "puts Plutonium::VERSION"
135
+ ```
136
+
137
+ ## Unattended execution
138
+
139
+ Plutonium generators are interactive by default. For scripts, agents, or CI, pass:
140
+
141
+ | Flag | Generators | Purpose |
142
+ |---|---|---|
143
+ | `--dest=main_app` / `--dest=<package>` | `pu:res:scaffold`, `pu:res:conn`, package-targeted generators | Skip "select destination" prompt |
144
+ | `--force` | any | Overwrite conflicting files (required when re-running `pu:saas:setup` or meta-generators) |
145
+ | `--auth=<account>` / `--public` / `--byo` | `pu:pkg:portal` | Skip auth-type prompt |
146
+ | `--skip-bundle` | gem-installing generators | Avoid mid-run `bundle install` |
147
+ | `--quiet` | most | Reduce output noise |
148
+
149
+ Meta-generators (`pu:saas:setup`) propagate these flags to the generators they chain. Always pass `--force` when re-running a meta-generator on an app that already has some of its outputs.
150
+
151
+ ## Related
152
+
153
+ - [Packages](./packages) — feature vs portal package structure
154
+ - [Portals](./portals) — portal engines, routing, resource connection
155
+ - [Generators](./generators) — full generator reference
156
+ - [Auth](/reference/auth/) — Rodauth setup and account types
157
+ - [UI › Assets](/reference/ui/assets) — Tailwind, Stimulus, design tokens
158
+ - [Tutorial](/getting-started/tutorial/) — step-by-step walkthrough