plutonium 0.65.0 → 0.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +43 -43
- data/.claude/skills/plutonium-app/SKILL.md +101 -59
- data/.claude/skills/plutonium-async-interactions/SKILL.md +19 -19
- data/.claude/skills/plutonium-auth/SKILL.md +119 -53
- data/.claude/skills/plutonium-behavior/SKILL.md +110 -78
- data/.claude/skills/plutonium-dashboard/SKILL.md +11 -4
- data/.claude/skills/plutonium-kanban/SKILL.md +81 -49
- data/.claude/skills/plutonium-resource/SKILL.md +144 -133
- data/.claude/skills/plutonium-tenancy/SKILL.md +104 -55
- data/.claude/skills/plutonium-testing/SKILL.md +130 -33
- data/.claude/skills/plutonium-ui/SKILL.md +151 -96
- data/.claude/skills/plutonium-wizard/SKILL.md +93 -82
- data/CHANGELOG.md +21 -0
- data/README.md +9 -9
- data/SECURITY.md +1 -1
- data/app/assets/plutonium.css +1 -1
- data/docs/.vitepress/sync-skills.mjs +6 -3
- data/docs/blog/introducing-plutonium-dashboards.md +4 -5
- data/docs/blog/introducing-plutonium-i18n.md +4 -5
- data/docs/getting-started/installation.md +5 -5
- data/docs/getting-started/tutorial/02-first-resource.md +3 -3
- data/docs/getting-started/tutorial/03-authentication.md +7 -7
- data/docs/getting-started/tutorial/04-authorization.md +21 -4
- data/docs/getting-started/tutorial/05-custom-actions.md +2 -2
- data/docs/getting-started/tutorial/06-nested-resources.md +5 -2
- data/docs/getting-started/tutorial/07-author-portal.md +2 -2
- data/docs/getting-started/tutorial/08-customizing-ui.md +45 -30
- data/docs/getting-started/tutorial/index.md +1 -1
- data/docs/guides/adding-resources.md +10 -7
- data/docs/guides/authentication.md +25 -25
- data/docs/guides/authorization.md +24 -24
- data/docs/guides/creating-packages.md +17 -17
- data/docs/guides/custom-actions.md +32 -32
- data/docs/guides/customizing-ui.md +29 -26
- data/docs/guides/dashboards.md +1 -1
- data/docs/guides/index.md +3 -3
- data/docs/guides/kanban.md +55 -55
- data/docs/guides/multi-tenancy.md +35 -22
- data/docs/guides/nested-resources.md +21 -21
- data/docs/guides/performance.md +3 -3
- data/docs/guides/search-filtering.md +13 -13
- data/docs/guides/testing.md +16 -12
- data/docs/guides/theming.md +32 -17
- data/docs/guides/troubleshooting.md +2 -2
- data/docs/guides/user-invites.md +17 -17
- data/docs/guides/user-profile.md +51 -24
- data/docs/guides/wizards.md +55 -55
- data/docs/reference/app/generators.md +22 -22
- data/docs/reference/app/index.md +15 -18
- data/docs/reference/app/packages.md +8 -8
- data/docs/reference/app/portals.md +75 -29
- data/docs/reference/auth/accounts.md +15 -15
- data/docs/reference/auth/index.md +12 -12
- data/docs/reference/auth/profile.md +67 -29
- data/docs/reference/behavior/async-interactions.md +24 -24
- data/docs/reference/behavior/controllers.md +28 -28
- data/docs/reference/behavior/index.md +5 -5
- data/docs/reference/behavior/interactions.md +44 -44
- data/docs/reference/behavior/policies.md +48 -28
- data/docs/reference/configuration.md +6 -6
- data/docs/reference/dashboard/dsl.md +2 -2
- data/docs/reference/dashboard/index.md +1 -1
- data/docs/reference/generators/lite.md +7 -7
- data/docs/reference/i18n.md +23 -0
- data/docs/reference/index.md +1 -1
- data/docs/reference/kanban/authorization.md +9 -9
- data/docs/reference/kanban/dsl.md +32 -32
- data/docs/reference/kanban/index.md +1 -1
- data/docs/reference/kanban/positioning.md +17 -15
- data/docs/reference/resource/actions.md +51 -51
- data/docs/reference/resource/definition.md +73 -73
- data/docs/reference/resource/export.md +6 -6
- data/docs/reference/resource/index.md +16 -16
- data/docs/reference/resource/model.md +24 -24
- data/docs/reference/resource/positioning.md +78 -76
- data/docs/reference/resource/query.md +13 -13
- data/docs/reference/tenancy/entity-scoping.md +65 -35
- data/docs/reference/tenancy/index.md +11 -11
- data/docs/reference/tenancy/invites.md +20 -20
- data/docs/reference/tenancy/nested-resources.md +13 -13
- data/docs/reference/testing/index.md +116 -22
- data/docs/reference/ui/assets.md +57 -25
- data/docs/reference/ui/components.md +20 -20
- data/docs/reference/ui/displays.md +14 -14
- data/docs/reference/ui/forms.md +35 -35
- data/docs/reference/ui/index.md +17 -15
- data/docs/reference/ui/layouts.md +21 -21
- data/docs/reference/ui/pages.md +22 -22
- data/docs/reference/ui/tables.md +7 -7
- data/docs/reference/wizard/anchoring-resume.md +33 -32
- data/docs/reference/wizard/dsl.md +44 -44
- data/docs/reference/wizard/index.md +6 -6
- data/docs/reference/wizard/one-time.md +18 -18
- data/docs/reference/wizard/registration-launch.md +32 -32
- data/docs/reference/wizard/storage-config.md +23 -23
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/profile/conn_generator.rb +6 -0
- data/lib/plutonium/resource/record/associated_with.rb +23 -2
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +7 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/css/components.css +10 -10
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b131f7d0e5b02982f4696e7d1db781f45b17f8b28f17da5fe5cc3b0a4480c7a0
|
|
4
|
+
data.tar.gz: f700d3d65a3fc368e73315c436a7f8d2afb5244cd04b4b0be5618efbc738d26f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dc926b3cf2fb69f5dfa4644d10c47e88a39e74f3359be2572a4ea6ea46ad216a5c0447959b0e1596f0ee6ae67190140f46acc53afa332a508e63f275440bdad
|
|
7
|
+
data.tar.gz: 4d9f3fba2b94d24ef0db3d5b24b35b30d6c058acc46ac20107d37c1a507f804dac7d47b3a38556f02b16ac229ce9b2a4140e73216152c9e56ae7189a5c71bc00
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium
|
|
3
|
-
description: Use BEFORE starting any Plutonium work
|
|
3
|
+
description: 'Use BEFORE starting any Plutonium work: new app, new feature, or first edit in an unfamiliar area. Routes you to the right skill and bootstraps greenfield work.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Plutonium
|
|
6
|
+
# Plutonium: Router & Bootstrapper
|
|
7
7
|
|
|
8
8
|
Entry point for all Plutonium work. Does three things:
|
|
9
9
|
|
|
@@ -14,57 +14,57 @@ Entry point for all Plutonium work. Does three things:
|
|
|
14
14
|
## 🚨 Critical (read first)
|
|
15
15
|
|
|
16
16
|
- **Plutonium is generator-driven.** Almost every file you'd hand-write has a `pu:*` generator. Hand-written files drift from conventions and break future generator runs.
|
|
17
|
-
- **For greenfield** (new app, substantial new feature, first resource in a new domain)
|
|
18
|
-
- **For targeted edits
|
|
19
|
-
- **For anything touching tenant scoping
|
|
17
|
+
- **For greenfield** (new app, substantial new feature, first resource in a new domain), load the **bootstrap bundle** below before writing code.
|
|
18
|
+
- **For targeted edits**: use the **router table** to jump to the right skill.
|
|
19
|
+
- **For anything touching tenant scoping**: load `plutonium-tenancy`. Don't reach for `where(organization: ...)` in a policy; fix the model instead.
|
|
20
20
|
- **Unattended execution:** always pass `--dest=`, `--force` (when re-running meta-generators), `--auth=`, `--skip-bundle`, `--quiet` so generators don't block on prompts. See [Unattended execution](#unattended-execution).
|
|
21
|
-
- **Inspect before you act.** Every targeted skill now opens with a CHECK gate
|
|
21
|
+
- **Inspect before you act.** Every targeted skill now opens with a CHECK gate: read the relevant files yourself before scaffolding or editing. Don't ask the user to describe their app when you can read it.
|
|
22
22
|
|
|
23
|
-
## The mental model (read once
|
|
23
|
+
## The mental model (read once: it decides what you should write)
|
|
24
24
|
|
|
25
|
-
Plutonium applies Rails' bargain
|
|
25
|
+
Plutonium applies Rails' bargain (follow the convention and the framework carries you; reach for an escape hatch when you need one) to the layer **above CRUD**: auth, authorization, multi-tenancy, admin UI, business operations. Four consequences change what you should actually type.
|
|
26
26
|
|
|
27
27
|
### 1. Everything is derived from something you already declared
|
|
28
28
|
|
|
29
|
-
Not "defaults someone picked for you"
|
|
29
|
+
Not "defaults someone picked for you": **computed from existing declarations**:
|
|
30
30
|
|
|
31
31
|
| Derived | From |
|
|
32
32
|
|---|---|
|
|
33
33
|
| Field types, required markers, select choices | model columns, associations, attachments, enums, **and validations** (`presence: true` → required; `inclusion:` → select choices) |
|
|
34
|
-
| A collection's preloads (index, kanban, export) | the policy's permitted field set
|
|
35
|
-
| Tenant scope | your associations
|
|
34
|
+
| A collection's preloads (index, kanban, export) | the policy's permitted field set; there is **no `includes` list to write or maintain** |
|
|
35
|
+
| Tenant scope | your associations: direct `belongs_to`, then `has_one`/`has_one :through`, then reverse `has_many` |
|
|
36
36
|
| Action type (record / bulk / resource) | whether the interaction declares `:resource`, `:resources`, or neither |
|
|
37
37
|
| An association input's typeahead | the **target resource's own `search` block** |
|
|
38
38
|
| CRUD, nested and action routes | one `register_resource` line |
|
|
39
39
|
|
|
40
|
-
⇒ **Declare only what differs.** A `field :title` matching the detected type is dead code
|
|
40
|
+
⇒ **Declare only what differs.** A `field :title` matching the detected type is dead code, and one more line to fall out of step when the column changes. This is the single most common way generated-looking code goes wrong.
|
|
41
41
|
|
|
42
42
|
### 2. Definition and policy answer different questions
|
|
43
43
|
|
|
44
44
|
- **Definition** = *how* a field renders.
|
|
45
45
|
- **Policy** = *whether it appears at all*.
|
|
46
46
|
|
|
47
|
-
"Only admins see this field" is `permitted_attributes_for_*`. Never a definition declaration, and never a `condition:` (that only hides UI
|
|
47
|
+
"Only admins see this field" is `permitted_attributes_for_*`. Never a definition declaration, and never a `condition:` (that only hides UI; the route stays live).
|
|
48
48
|
|
|
49
49
|
### 3. Overrides are plain Ruby inheritance
|
|
50
50
|
|
|
51
|
-
`AdminPortal::PostDefinition < ::PostDefinition`, and the same for policies and controllers. App-level default, portal-level subclass. No registry of overrides, no precedence DSL, no merge semantics
|
|
51
|
+
`AdminPortal::PostDefinition < ::PostDefinition`, and the same for policies and controllers. App-level default, portal-level subclass. No registry of overrides, no precedence DSL, no merge semantics, so "why does this field show here but not there" is always readable as a class hierarchy.
|
|
52
52
|
|
|
53
53
|
### 4. Climb the escape-hatch ladder only as far as the problem requires
|
|
54
54
|
|
|
55
|
-
1. **Change an option
|
|
56
|
-
2. **Render inline
|
|
57
|
-
3. **Write a component
|
|
58
|
-
4. **Implement a hook
|
|
59
|
-
5. **Replace the page
|
|
55
|
+
1. **Change an option**: `input :content, as: :markdown`
|
|
56
|
+
2. **Render inline**: `display :priority do |f| … end` (a block, `instance_exec`ed in Phlex, emits markup directly and gives you `f.object`)
|
|
57
|
+
3. **Write a component**: a *field* component (subclasses the Phlexi base) plugs into `as:`; anything with its own constructor goes through a block (`display :card do |field| … end`)
|
|
58
|
+
4. **Implement a hook**: controller hooks instead of reopening `create`/`update`; page `render_before_*` / `render_after_*` instead of `view_template`
|
|
59
|
+
5. **Replace the page**: `view_template` on the nested class, or an ERB view at the controller path (ERB wins when both exist)
|
|
60
60
|
|
|
61
61
|
Reaching for rung 5 on a rung-1 problem is how you end up owning breadcrumbs, the header and turbo frame wiring you never meant to touch.
|
|
62
62
|
|
|
63
63
|
**Underneath all of it, it stays Rails.** Models are plain ActiveRecord, controllers inherit from Rails controllers, views resolve through Rails view paths. A Plutonium resource and a hand-written controller coexist in one app.
|
|
64
64
|
|
|
65
|
-
## ✅ Orient before you route (CHECK
|
|
65
|
+
## ✅ Orient before you route (CHECK: read the app, don't assume)
|
|
66
66
|
|
|
67
|
-
A one-line request rarely says whether this is a new app, a half-built one, or a multi-tenant one
|
|
67
|
+
A one-line request rarely says whether this is a new app, a half-built one, or a multi-tenant one, and those change which path you take. Spend 30 seconds reading the app **before** loading a bundle or running anything:
|
|
68
68
|
|
|
69
69
|
| Read | Tells you |
|
|
70
70
|
|---|---|
|
|
@@ -80,16 +80,16 @@ This is the global "look before you leap"; each targeted skill carries its own A
|
|
|
80
80
|
| Skill | Covers |
|
|
81
81
|
|---|---|
|
|
82
82
|
| **[[plutonium-app]]** | Installation, packages (feature + portal), portal engines, mounting, `register_resource` (including singular and custom routes), `pu:res:conn` |
|
|
83
|
-
| **[[plutonium-resource]]** | The resource itself
|
|
83
|
+
| **[[plutonium-resource]]** | The resource itself: `pu:res:scaffold`, field types, model layer (`Plutonium::Resource::Record`, `has_cents`, SGID, routing), definition layer (fields/inputs/displays/columns, search/filters/scopes/sorting, custom actions, bulk actions, index views, page customization) |
|
|
84
84
|
| **[[plutonium-behavior]]** | Controllers (hooks, key methods, presentation), policies (action methods, `permitted_attributes_for_*`, `permitted_associations`), interactions (structure, outcomes, chaining, URL generation) |
|
|
85
|
-
| **[[plutonium-async-interactions]]** | Async interactions
|
|
85
|
+
| **[[plutonium-async-interactions]]** | Async interactions: `async`, the Run STI model, failure policies (`halt`/`continue`/`transactional`), authorization re-derivation at perform time, registering AsyncRun as a resource (progress page + running banner), scheduling `ReapJob` |
|
|
86
86
|
| **[[plutonium-ui]]** | Page classes, forms, displays, tables, custom Phlex components, layouts, modals & tabs, Tailwind config, Stimulus, design tokens, `.pu-*` classes, Phlexi themes |
|
|
87
|
-
| **[[plutonium-kanban]]** | `kanban do…end` DSL in a Definition
|
|
88
|
-
| **[[plutonium-dashboard]]** | Dashboards
|
|
87
|
+
| **[[plutonium-kanban]]** | `kanban do…end` DSL in a Definition, columns, `card_fields`, `position_on`, `realtime`, column actions, `kanban_move?` policy, quick-add, static vs dynamic boards |
|
|
88
|
+
| **[[plutonium-dashboard]]** | Dashboards, `Plutonium::Dashboard::Base`, `metric` / `chart` / `card`, `register_dashboard`, lazy turbo-frame cards, `refresh`, `condition:`, `authorize?`, `pu:dashboard` |
|
|
89
89
|
| **[[plutonium-auth]]** | Rodauth install, account types (basic / admin / SaaS), profile resource, security section |
|
|
90
90
|
| **[[plutonium-tenancy]]** | Entity scoping (`associated_with`, `default_relation_scope`, three model shapes), nested resources, invites |
|
|
91
91
|
| **[[plutonium-testing]]** | `pu:test:install`, `pu:test:scaffold`, `ResourceCrud`/`ResourcePolicy`/`ResourceDefinition`/`ResourceModel`/`NestedResource`/`PortalAccess`/`ResourceInteraction`, `AuthHelpers` |
|
|
92
|
-
| **[[plutonium-wizard]]** | Multi-step flows
|
|
92
|
+
| **[[plutonium-wizard]]** | Multi-step flows: the wizard DSL (`step`/`review`/`using:`/`condition:`, per-step `on_submit`/`persist`/`on_rollback`, `execute`), anchoring & resume, one-time wizards + gate, registration (`wizard` macro + `register_wizard`), storage/config + SweepJob |
|
|
93
93
|
|
|
94
94
|
## Greenfield bootstrap bundle
|
|
95
95
|
|
|
@@ -97,10 +97,10 @@ Triggers: installing Plutonium, building a new app, adding the first resource in
|
|
|
97
97
|
|
|
98
98
|
**Load these before writing code:**
|
|
99
99
|
|
|
100
|
-
1. **`plutonium-app
|
|
101
|
-
2. **`plutonium-resource
|
|
102
|
-
3. **`plutonium-behavior
|
|
103
|
-
4. **`plutonium-tenancy
|
|
100
|
+
1. **`plutonium-app`**: install, portals, packages, routes.
|
|
101
|
+
2. **`plutonium-resource`**: scaffold, model, definition (the bulk of the work).
|
|
102
|
+
3. **`plutonium-behavior`**: controllers, policies, interactions.
|
|
103
|
+
4. **`plutonium-tenancy`**: only if multi-tenant; load before declaring entity scoping.
|
|
104
104
|
|
|
105
105
|
Add when relevant:
|
|
106
106
|
- **`plutonium-auth`** for login / accounts / profile.
|
|
@@ -114,7 +114,7 @@ Add when relevant:
|
|
|
114
114
|
| Install Plutonium, create a portal or package, mount engines, register routes (incl. singular / custom routes) | **[[plutonium-app]]** |
|
|
115
115
|
| Run `pu:res:scaffold`, pick field types, set scaffold options | **[[plutonium-resource]]** |
|
|
116
116
|
| Edit a model, add associations, use `has_cents`, override `to_param` / `to_label` | **[[plutonium-resource]]** |
|
|
117
|
-
| Edit a definition
|
|
117
|
+
| Edit a definition, fields, inputs, displays, columns, search, filters, scopes, custom actions, bulk actions, index views, modal/slideover, page titles | **[[plutonium-resource]]** |
|
|
118
118
|
| Override a controller action, hook, redirect, or `resource_params` | **[[plutonium-behavior]]** |
|
|
119
119
|
| Write `relation_scope`, `permitted_attributes_for_*`, `permitted_associations`, action methods, or any policy override | **[[plutonium-behavior]]** (+ **[[plutonium-tenancy]]** if scoping) |
|
|
120
120
|
| Write an interaction class for business logic | **[[plutonium-behavior]]** |
|
|
@@ -122,24 +122,24 @@ Add when relevant:
|
|
|
122
122
|
| Scope a model to a tenant, write `associated_with`, set portal entity strategy | **[[plutonium-tenancy]]** |
|
|
123
123
|
| Configure parent/child nested routes, custom parent resolution | **[[plutonium-tenancy]]** |
|
|
124
124
|
| Set up user invitations or entity membership | **[[plutonium-tenancy]]** |
|
|
125
|
-
| Build or customize a kanban board view
|
|
126
|
-
| Build a dashboard, KPI overview or chart page
|
|
125
|
+
| Build or customize a kanban board view, `kanban do…end`, columns, `card_fields`, `position_on`, `realtime`, column actions, `kanban_move?` policy | **[[plutonium-kanban]]** |
|
|
126
|
+
| Build a dashboard, KPI overview or chart page, `pu:dashboard`, `metric` / `chart` / `card`, `register_dashboard`, refresh, per-card conditions | **[[plutonium-dashboard]]** |
|
|
127
127
|
| Build a custom page (override `ShowPage`/`IndexPage`/`NewPage`/`EditPage`), custom form, custom display, custom table, custom Phlex component | **[[plutonium-ui]]** |
|
|
128
128
|
| Configure Tailwind, register Stimulus controllers, edit design tokens, theme forms/displays/tables, write a custom layout | **[[plutonium-ui]]** |
|
|
129
129
|
| Install Rodauth, set up accounts, configure login flow, add the profile resource | **[[plutonium-auth]]** |
|
|
130
130
|
| Write tests for a resource, run `pu:test:scaffold`, include `Plutonium::Testing::*` concerns | **[[plutonium-testing]]** |
|
|
131
|
-
| Build a multi-step flow
|
|
131
|
+
| Build a multi-step flow, onboarding, checkout, branching create, register a `wizard` / `register_wizard`, gate a one-time wizard | **[[plutonium-wizard]]** |
|
|
132
132
|
|
|
133
133
|
## Resource architecture at a glance
|
|
134
134
|
|
|
135
|
-
A **resource** is four cooperating layers
|
|
135
|
+
A **resource** is four cooperating layers; Plutonium auto-fills defaults from the model, so you only declare overrides:
|
|
136
136
|
|
|
137
137
|
| Layer | File | Purpose |
|
|
138
138
|
|---|---|---|
|
|
139
139
|
| **Model** | `app/models/post.rb` | Data, validations, associations |
|
|
140
|
-
| **Definition** | `app/definitions/post_definition.rb` | UI
|
|
141
|
-
| **Policy** | `app/policies/post_policy.rb` | Authorization
|
|
142
|
-
| **Controller** | `app/controllers/posts_controller.rb` | Request handling (rarely edited
|
|
140
|
+
| **Definition** | `app/definitions/post_definition.rb` | UI, fields, filters, actions |
|
|
141
|
+
| **Policy** | `app/policies/post_policy.rb` | Authorization: who, what |
|
|
142
|
+
| **Controller** | `app/controllers/posts_controller.rb` | Request handling (rarely edited; use hooks) |
|
|
143
143
|
|
|
144
144
|
Plus one optional fifth layer:
|
|
145
145
|
|
|
@@ -191,8 +191,8 @@ Meta-generators (`pu:saas:setup`) propagate flags to the generators they chain.
|
|
|
191
191
|
## Workflow summary
|
|
192
192
|
|
|
193
193
|
1. **Load the bootstrap bundle** (or the targeted skill from the router table).
|
|
194
|
-
2. **Generate
|
|
195
|
-
3. **Migrate
|
|
196
|
-
4. **Connect
|
|
197
|
-
5. **Customize
|
|
198
|
-
6. **Verify
|
|
194
|
+
2. **Generate**: `rails g pu:res:scaffold Model field:type ... --dest=main_app`.
|
|
195
|
+
3. **Migrate**: `rails db:prepare`.
|
|
196
|
+
4. **Connect**: `rails g pu:res:conn Model --dest=portal_name`.
|
|
197
|
+
5. **Customize**: edit definition / policy as needed.
|
|
198
|
+
6. **Verify**: hit the route in the browser.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-app
|
|
3
|
-
description: Use BEFORE installing Plutonium, creating a portal or feature package, mounting an engine, or registering resources/routes. Covers initial setup, the package system, portal engines, route registration (including singular and custom routes), and resource-to-portal wiring.
|
|
3
|
+
description: 'Use BEFORE installing Plutonium, creating a portal or feature package, mounting an engine, or registering resources/routes. Covers initial setup, the package system, portal engines, route registration (including singular and custom routes), and resource-to-portal wiring.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Plutonium App
|
|
6
|
+
# Plutonium App: Installation, Packages, Portals, Routes
|
|
7
7
|
|
|
8
8
|
How a Plutonium app is assembled: the install bootstrap, the package system (feature vs portal), portal engines, and the routing surface that exposes resources to the web.
|
|
9
9
|
|
|
@@ -15,54 +15,54 @@ For the resources themselves (model + definition + scaffold options), see [[plut
|
|
|
15
15
|
- **Existing app → `base.rb`. New app → `plutonium.rb`.** The `plutonium.rb` template re-runs full bootstrap (dotenv, annotate, solid_*, asset config) and creates generic "initial commit" commits that clobber history. For any pre-existing app use `base.rb`.
|
|
16
16
|
- **Pass `--dest`, `--auth`, `--force`, `--skip-bundle`** etc. for unattended runs so generators don't block on prompts.
|
|
17
17
|
- **Feature vs portal is a hard split.** Feature packages hold models/policies/definitions/interactions. Portal packages hold controllers/views/routes/auth. Don't mix.
|
|
18
|
-
- **Package classes are auto-namespaced
|
|
19
|
-
- **Always connect resources with `pu:res:conn
|
|
20
|
-
- **For custom routes on a registered resource, pass `as
|
|
18
|
+
- **Package classes are auto-namespaced**: `packages/blogging/app/models/blogging/post.rb` → `Blogging::Post`. Don't fight it.
|
|
19
|
+
- **Always connect resources with `pu:res:conn`**: until connected, a resource has no portal routes and is invisible.
|
|
20
|
+
- **For custom routes on a registered resource, pass `as:`**: otherwise `resource_url_for` can't build URLs.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
## 🛑 Before you install or scaffold structure: confirm the shape (ASK
|
|
24
|
+
## 🛑 Before you install or scaffold structure: confirm the shape (ASK: don't infer)
|
|
25
25
|
|
|
26
|
-
"Set up Plutonium" / "make an admin area" / "create a billing package" each hide a high-blast-radius decision. Get one wrong and you **clobber a year of git history**, build the wrong kind of package, or ship a portal nobody can log into. Resolve each
|
|
26
|
+
"Set up Plutonium" / "make an admin area" / "create a billing package" each hide a high-blast-radius decision. Get one wrong and you **clobber a year of git history**, build the wrong kind of package, or ship a portal nobody can log into. Resolve each, confirming by inspection (next section), not assumption:
|
|
27
27
|
|
|
28
|
-
1. **Fresh app or existing one?** Existing ⇒ `bundle add plutonium` + `pu:core:install` (the `base.rb` path). **NEVER the `plutonium.rb` fresh-app template on an existing app
|
|
29
|
-
2. **Feature package or portal package?** Business logic (models/policies/definitions/interactions) ⇒ `pu:pkg:package` (feature, no UI). A web surface (controllers/views/routes/auth) ⇒ `pu:pkg:portal`. Hard split
|
|
30
|
-
3. **Auth per portal.** `--auth=<account>` / `--public` / `--byo` / `--scope=<Entity>` (multi-tenant). Unguessable from "admin area"
|
|
31
|
-
4. **Don't stop half-wired.** A resource reaches the browser only after: scaffold → migrate → `pu:res:conn --dest=portal` →
|
|
28
|
+
1. **Fresh app or existing one?** Existing ⇒ `bundle add plutonium` + `pu:core:install` (the `base.rb` path). **NEVER the `plutonium.rb` fresh-app template on an existing app**: it re-bootstraps (dotenv/annotate/solid_*/assets) and drops "initial commit" commits that clobber history. This is the single most dangerous mistake in this skill; confirm it's greenfield *before* reaching for `plutonium.rb`.
|
|
29
|
+
2. **Feature package or portal package?** Business logic (models/policies/definitions/interactions) ⇒ `pu:pkg:package` (feature, no UI). A web surface (controllers/views/routes/auth) ⇒ `pu:pkg:portal`. Hard split: "billing" is a *feature*; "admin area" is a *portal*. A feature package is invisible until its resources are `pu:res:conn`'d into a portal.
|
|
30
|
+
3. **Auth per portal.** `--auth=<account>` / `--public` / `--byo` / `--scope=<Entity>` (multi-tenant). Unguessable from "admin area"; decide, don't default silently.
|
|
31
|
+
4. **Don't stop half-wired.** A resource reaches the browser only after: scaffold → migrate → `pu:res:conn --dest=portal` → registered in the portal's routes (conn does this) → portal engine mounted (`pu:pkg:portal` already did this). Name the whole chain before you start.
|
|
32
32
|
|
|
33
|
-
**Never ship a guessed schema, portal name, or auth flag as applied commands
|
|
33
|
+
**Never ship a guessed schema, portal name, or auth flag as applied commands**; read them off the app first; fall back to `AskUserQuestion` only for genuine product choices (separate staff accounts vs shared, which payment backend). The decisions compound: *existing app ⇒ base.rb path*; *feature package ⇒ needs a portal to be visible*; *new portal ⇒ pick auth + mount it*.
|
|
34
34
|
|
|
35
|
-
## ✅ Before you run a generator: verify the ground truth (CHECK
|
|
35
|
+
## ✅ Before you run a generator: verify the ground truth (CHECK: read it, don't ask for it)
|
|
36
36
|
|
|
37
|
-
You have file access
|
|
37
|
+
You have file access, **inspect**; don't ask the user to describe their app.
|
|
38
38
|
|
|
39
39
|
| Check | How | Why it matters |
|
|
40
40
|
|---|---|---|
|
|
41
|
-
| Greenfield vs existing | `git log --oneline \| head`; is there a populated `Gemfile`/`app/`? | An existing app must use the `base.rb` path
|
|
41
|
+
| Greenfield vs existing | `git log --oneline \| head`; is there a populated `Gemfile`/`app/`? | An existing app must use the `base.rb` path, **never** `plutonium.rb` |
|
|
42
42
|
| Plutonium already installed | grep `Gemfile` for `plutonium`; `ls config/packages.rb app/controllers/resource_controller.rb` | Avoid re-installing / double bootstrap |
|
|
43
|
-
| Package/portal already exists | `ls packages/<name>` | Don't duplicate
|
|
43
|
+
| Package/portal already exists | `ls packages/<name>` | Don't duplicate; connect to / extend the existing one |
|
|
44
44
|
| Existing auth | grep `Gemfile`/`app/models` for `rodauth`/`devise`/`has_secure_password` | Drives `--auth` vs `--byo` |
|
|
45
|
-
| Portal engine mounted | grep `config/routes.rb` for `mount <Portal>::Engine` |
|
|
45
|
+
| Portal engine mounted | grep `packages/<portal>/config/routes.rb` for `mount <Portal>::Engine` | `pu:pkg:portal` writes the mount there. Don't add a second one to `config/routes.rb` (duplicate route name error) |
|
|
46
46
|
| Resource registered | grep the portal's `config/routes.rb` for `register_resource ::<X>` | Unregistered ⇒ no URLs (`resource_url_for` fails) |
|
|
47
47
|
| Migrations applied | `rails db:migrate:status` before `pu:res:conn` | `conn` seeds the policy from columns |
|
|
48
48
|
|
|
49
49
|
Inspect with your own tools **before** running any generator.
|
|
50
50
|
|
|
51
|
-
## 🛠 Use the generator
|
|
51
|
+
## 🛠 Use the generator: pick the right install path
|
|
52
52
|
|
|
53
53
|
Never hand-write base controllers, engine files, layouts, or route registration. Pass `--dest`/`--auth`/`--force`/`--skip-bundle` for unattended runs.
|
|
54
54
|
|
|
55
55
|
| Task | Generator | Verify first |
|
|
56
56
|
|---|---|---|
|
|
57
|
-
| Install
|
|
58
|
-
| Install
|
|
57
|
+
| Install, **existing** app | `bundle add plutonium` + `pu:core:install` | It's an existing app (use `base.rb`, **not** `plutonium.rb`) |
|
|
58
|
+
| Install, **fresh** app | `rails new … -m …/plutonium.rb` | Brand-new app **only** |
|
|
59
59
|
| Feature package | `pu:pkg:package <name>` | Not already present |
|
|
60
|
-
| Portal package | `pu:pkg:portal <name> --auth=…/--public/--byo/--scope=…` | Auth strategy decided
|
|
60
|
+
| Portal package | `pu:pkg:portal <name> --auth=…/--public/--byo/--scope=…` | Auth strategy decided. The generator also mounts the engine (see Mounting) |
|
|
61
61
|
| Connect a resource | `pu:res:conn <Res> --dest=portal` | Migrated; target portal exists |
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
# Part 1
|
|
65
|
+
# Part 1: Installation
|
|
66
66
|
|
|
67
67
|
## Fresh Rails app (recommended)
|
|
68
68
|
|
|
@@ -78,11 +78,11 @@ Configures Rails + Propshaft + esbuild + TailwindCSS + Plutonium in one shot.
|
|
|
78
78
|
⚠️ Use `base.rb`, **not** `plutonium.rb`.
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
# Option 1
|
|
81
|
+
# Option 1: template
|
|
82
82
|
bin/rails app:template \
|
|
83
83
|
LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
|
|
84
84
|
|
|
85
|
-
# Option 2
|
|
85
|
+
# Option 2: manual
|
|
86
86
|
# Add `gem "plutonium"` to Gemfile, then:
|
|
87
87
|
bundle install
|
|
88
88
|
rails generate pu:core:install
|
|
@@ -108,10 +108,7 @@ rails db:prepare
|
|
|
108
108
|
# 5. Connect resource to portal
|
|
109
109
|
rails generate pu:res:conn Post --dest=admin_portal
|
|
110
110
|
|
|
111
|
-
# 6.
|
|
112
|
-
# mount AdminPortal::Engine, at: "/admin"
|
|
113
|
-
|
|
114
|
-
# 7. Start
|
|
111
|
+
# 6. Start (step 3 already mounted the portal at /admin)
|
|
115
112
|
rails server
|
|
116
113
|
```
|
|
117
114
|
|
|
@@ -121,10 +118,10 @@ rails server
|
|
|
121
118
|
app/
|
|
122
119
|
├── controllers/
|
|
123
120
|
│ ├── plutonium_controller.rb # non-resource base
|
|
124
|
-
│ └── resource_controller.rb # CRUD base
|
|
121
|
+
│ └── resource_controller.rb # CRUD base; see plutonium-behavior
|
|
125
122
|
├── definitions/resource_definition.rb
|
|
126
123
|
├── interactions/resource_interaction.rb
|
|
127
|
-
├── models/resource_record.rb # abstract model
|
|
124
|
+
├── models/resource_record.rb # abstract model that includes Plutonium::Resource::Record
|
|
128
125
|
├── policies/resource_policy.rb
|
|
129
126
|
└── views/layouts/resource.html.erb
|
|
130
127
|
|
|
@@ -174,7 +171,7 @@ end
|
|
|
174
171
|
|
|
175
172
|
---
|
|
176
173
|
|
|
177
|
-
# Part 2
|
|
174
|
+
# Part 2: The Package System
|
|
178
175
|
|
|
179
176
|
Two kinds, hard split:
|
|
180
177
|
|
|
@@ -275,19 +272,19 @@ Switch locale with a normal Rails `around_action` in the portal's controller con
|
|
|
275
272
|
|
|
276
273
|
## When to use which
|
|
277
274
|
|
|
278
|
-
**Feature packages**
|
|
275
|
+
**Feature packages** are domain logic that:
|
|
279
276
|
- Could be reused across multiple portals (admin and customer both edit `Blogging::Post`)
|
|
280
277
|
- Has no inherent UI / auth (it's just behavior)
|
|
281
278
|
- You want to keep isolated from other domains (`billing` should not depend on `blogging`)
|
|
282
279
|
|
|
283
|
-
**Portal packages**
|
|
280
|
+
**Portal packages** are user-facing surfaces that:
|
|
284
281
|
- Have a specific auth flow (admin vs customer vs public)
|
|
285
282
|
- Render different views of the same underlying resources
|
|
286
283
|
- Need different policies / definitions per audience
|
|
287
284
|
|
|
288
285
|
---
|
|
289
286
|
|
|
290
|
-
# Part 3
|
|
287
|
+
# Part 3: Portal Engines
|
|
291
288
|
|
|
292
289
|
A portal is a Rails engine mixing in `Plutonium::Portal::Engine`. It defines its own routes, controller concern, and (optionally) entity scoping.
|
|
293
290
|
|
|
@@ -302,7 +299,7 @@ rails g pu:pkg:portal <name>
|
|
|
302
299
|
| Option | Description |
|
|
303
300
|
|---|---|
|
|
304
301
|
| `--auth=NAME` | Rodauth account to use (e.g. `--auth=user`) |
|
|
305
|
-
| `--public` | Public access
|
|
302
|
+
| `--public` | Public access, no auth |
|
|
306
303
|
| `--byo` | Bring your own auth |
|
|
307
304
|
| `--scope=CLASS` | Entity class for multi-tenancy (e.g. `--scope=Organization`) |
|
|
308
305
|
|
|
@@ -333,7 +330,9 @@ end
|
|
|
333
330
|
|
|
334
331
|
## Controller concern (auth)
|
|
335
332
|
|
|
336
|
-
Every portal has a `Concerns::Controller
|
|
333
|
+
Every portal has a `Concerns::Controller`, included by both its `ResourceController` (resource pages) and its `PlutoniumController` (dashboard and other non-resource pages). The generator wires this up; you customize it for auth / before_action hooks.
|
|
334
|
+
|
|
335
|
+
Portal-wide helpers the layout calls belong here, declared with `helper_method`, so they work on the dashboard as well as resource pages. Defining one on a single resource controller (or on the app's `::ResourceController`) leaves the dashboard without it. The common case is `profile_url`: `Plutonium::Auth::Rodauth` defines it as `nil`, and the avatar menu only shows a Profile link when it is truthy. `pu:profile:conn --dest=<portal>` writes the override into this concern; it reads `current_user.profile`, the `has_one :profile` association that `pu:profile:install` adds (see [[plutonium-auth]]). A plain `pu:res:conn --singular` registers the route but does not define `profile_url`. When testing the link, request both the portal root and a resource page.
|
|
337
336
|
|
|
338
337
|
### Rodauth
|
|
339
338
|
|
|
@@ -371,19 +370,62 @@ end
|
|
|
371
370
|
|
|
372
371
|
## Mounting
|
|
373
372
|
|
|
373
|
+
`pu:pkg:portal` writes the mount at the bottom of the portal's own `packages/<name>_portal/config/routes.rb`, at `/<name>`, wrapped in an auth constraint when `--auth` is given:
|
|
374
|
+
|
|
374
375
|
```ruby
|
|
375
|
-
# config/routes.rb
|
|
376
|
+
# packages/admin_portal/config/routes.rb (after the engine's routes.draw block)
|
|
376
377
|
Rails.application.routes.draw do
|
|
377
|
-
# Authenticated mount
|
|
378
378
|
constraints Rodauth::Rails.authenticate(:user) do
|
|
379
379
|
mount AdminPortal::Engine, at: "/admin"
|
|
380
380
|
end
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
To change the path, edit `at:` in place. Don't mount the engine again in `config/routes.rb`: the second `mount` reuses the route name (`admin_portal`) and Rails raises `ArgumentError: Invalid route name, already in use`.
|
|
385
|
+
|
|
386
|
+
Route order matters: the app's `config/routes.rb` is drawn first, then each package's routes file, then gem engines (Active Storage, Turbo).
|
|
387
|
+
|
|
388
|
+
### Mounting a portal at `/`
|
|
389
|
+
|
|
390
|
+
Two things change when a portal is mounted at `"/"`.
|
|
391
|
+
|
|
392
|
+
**Drop the `Rodauth::Rails.authenticate` constraint and authenticate in the controller concern instead.** The constraint does not fail the match for an anonymous visitor; it calls `rodauth.require_account`, which redirects to login. A constrained mount at `/` matches every path the app's own routes did not claim, so it redirects anonymous requests meant for routes drawn after it (other portals, Active Storage) and turns unknown URLs into login redirects.
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
# packages/desk_portal/config/routes.rb
|
|
396
|
+
Rails.application.routes.draw do
|
|
397
|
+
mount DeskPortal::Engine, at: "/"
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# packages/desk_portal/app/controllers/desk_portal/concerns/controller.rb
|
|
401
|
+
module DeskPortal
|
|
402
|
+
module Concerns
|
|
403
|
+
module Controller
|
|
404
|
+
extend ActiveSupport::Concern
|
|
405
|
+
include Plutonium::Portal::Controller
|
|
406
|
+
include Plutonium::Auth::Rodauth(:user)
|
|
407
|
+
# add concerns above.
|
|
408
|
+
|
|
409
|
+
included do
|
|
410
|
+
before_action { rodauth.require_account }
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Move the engine's root off `/` but keep the name.** The app's `root` is drawn first, so the generated `root to: "dashboard#index"` is unreachable and the portal's `root_path` points at the app's home page. Plutonium's header, icon rail, breadcrumbs and wizard exits all link to `root_path`, so the portal still needs a route named `root`:
|
|
381
418
|
|
|
382
|
-
|
|
383
|
-
|
|
419
|
+
```ruby
|
|
420
|
+
DeskPortal::Engine.routes.draw do
|
|
421
|
+
get "dashboard", to: "dashboard#index", as: :root
|
|
422
|
+
register_resource ::Comment
|
|
423
|
+
# register resources above.
|
|
384
424
|
end
|
|
385
425
|
```
|
|
386
426
|
|
|
427
|
+
The same applies to `register_dashboard ..., at: "/"`. Confirm with `Rails.application.routes.recognize_path("/")` (still the app's home) and `recognize_path("/dashboard")`.
|
|
428
|
+
|
|
387
429
|
## Controller hierarchy
|
|
388
430
|
|
|
389
431
|
Portal controllers inherit from the feature-package controller if one exists, OR from the portal's `ResourceController` otherwise.
|
|
@@ -412,13 +454,13 @@ end
|
|
|
412
454
|
## Per-portal overrides
|
|
413
455
|
|
|
414
456
|
```ruby
|
|
415
|
-
# Definition
|
|
457
|
+
# Definition: how fields render (NOT whether they appear)
|
|
416
458
|
class AdminPortal::PostDefinition < ::PostDefinition
|
|
417
459
|
scope :pending_review
|
|
418
460
|
input :internal_notes, hint: "Not shown to the author"
|
|
419
461
|
end
|
|
420
462
|
|
|
421
|
-
# Policy
|
|
463
|
+
# Policy: whether a field appears at all
|
|
422
464
|
class AdminPortal::PostPolicy < ::PostPolicy
|
|
423
465
|
include AdminPortal::ResourcePolicy
|
|
424
466
|
def destroy? = true
|
|
@@ -436,7 +478,7 @@ end
|
|
|
436
478
|
|
|
437
479
|
---
|
|
438
480
|
|
|
439
|
-
# Part 4
|
|
481
|
+
# Part 4: Routes & `register_resource`
|
|
440
482
|
|
|
441
483
|
Portal routes live in `packages/<name>_portal/config/routes.rb`:
|
|
442
484
|
|
|
@@ -452,7 +494,7 @@ AdminPortal::Engine.routes.draw do
|
|
|
452
494
|
end
|
|
453
495
|
```
|
|
454
496
|
|
|
455
|
-
## `register_resource
|
|
497
|
+
## `register_resource`: what it does
|
|
456
498
|
|
|
457
499
|
For each call, Plutonium auto-generates:
|
|
458
500
|
|
|
@@ -460,11 +502,11 @@ For each call, Plutonium auto-generates:
|
|
|
460
502
|
- Nested routes for every registered `has_many` / `has_one` parent (prefixed `nested_`)
|
|
461
503
|
- Route names that `resource_url_for` can resolve
|
|
462
504
|
|
|
463
|
-
You list every resource the portal exposes. If a resource isn't registered, it has no URLs in that portal
|
|
505
|
+
You list every resource the portal exposes. If a resource isn't registered, it has no URLs in that portal, so `resource_url_for` will fail.
|
|
464
506
|
|
|
465
507
|
## Singular (singleton) resources
|
|
466
508
|
|
|
467
|
-
For resources with no collection
|
|
509
|
+
For resources with no collection, a single per-user `Profile`, app-wide `Settings`, etc.:
|
|
468
510
|
|
|
469
511
|
```ruby
|
|
470
512
|
register_resource ::Profile, singular: true
|
|
@@ -505,9 +547,9 @@ register_resource ::Post do
|
|
|
505
547
|
end
|
|
506
548
|
```
|
|
507
549
|
|
|
508
|
-
**Always pass `as:`.** Without it, `resource_url_for(@post, action: :preview)` fails because there's no named route to look up
|
|
550
|
+
**Always pass `as:`.** Without it, `resource_url_for(@post, action: :preview)` fails because there's no named route to look up, which is especially critical for nested resources.
|
|
509
551
|
|
|
510
|
-
For most operations with business logic, prefer **interactive actions** (definition + interaction
|
|
552
|
+
For most operations with business logic, prefer **interactive actions** (definition + interaction, see [[plutonium-resource]] › Actions) over custom controller routes. The action routes are wired automatically with no `register_resource` block needed.
|
|
511
553
|
|
|
512
554
|
## Cross-package and nested URLs
|
|
513
555
|
|
|
@@ -515,7 +557,7 @@ See [[plutonium-behavior]] for full `resource_url_for` signature and [[plutonium
|
|
|
515
557
|
|
|
516
558
|
---
|
|
517
559
|
|
|
518
|
-
# Part 5
|
|
560
|
+
# Part 5: Connecting Resources to Portals (`pu:res:conn`)
|
|
519
561
|
|
|
520
562
|
A resource is invisible until connected to at least one portal. The generator wires up the portal-specific controller, policy, definition, and route registration.
|
|
521
563
|
|
|
@@ -525,7 +567,7 @@ A resource is invisible until connected to at least one portal. The generator wi
|
|
|
525
567
|
rails g pu:res:conn RESOURCE [RESOURCE...] --dest=PORTAL_NAME [--singular]
|
|
526
568
|
```
|
|
527
569
|
|
|
528
|
-
Pass resources directly
|
|
570
|
+
Pass resources directly; this avoids interactive prompts. No `--src` needed.
|
|
529
571
|
|
|
530
572
|
## Usage
|
|
531
573
|
|
|
@@ -540,7 +582,7 @@ rails g pu:res:conn Blogging::Post Blogging::Comment --dest=admin_portal
|
|
|
540
582
|
rails g pu:res:conn Profile --dest=customer_portal --singular
|
|
541
583
|
```
|
|
542
584
|
|
|
543
|
-
**Run after migrations
|
|
585
|
+
**Run after migrations**: the generator reads model columns to seed the policy's `permitted_attributes_for_*`.
|
|
544
586
|
|
|
545
587
|
## What gets generated
|
|
546
588
|
|
|
@@ -560,7 +602,7 @@ register_resource ::Post
|
|
|
560
602
|
register_resource ::Profile, singular: true # if --singular
|
|
561
603
|
```
|
|
562
604
|
|
|
563
|
-
Re-running `pu:res:conn` for the same resource is **idempotent
|
|
605
|
+
Re-running `pu:res:conn` for the same resource is **idempotent**: already-registered entries report `identical` and are not duplicated. Insertion falls back gracefully when the conventional `# register resources above` marker is missing (uses the `routes.draw do` opening), and warns clearly if it can't find any anchor.
|
|
564
606
|
|
|
565
607
|
### Generated controller
|
|
566
608
|
|
|
@@ -590,7 +632,7 @@ class AdminPortal::PostPolicy < ::PostPolicy
|
|
|
590
632
|
end
|
|
591
633
|
```
|
|
592
634
|
|
|
593
|
-
Review and trim
|
|
635
|
+
Review and trim: the generator is liberal. Especially: drop `_id` fields when the form uses the association name, and add `:price` (not `:price_cents`) for `has_cents` fields.
|
|
594
636
|
|
|
595
637
|
---
|
|
596
638
|
|
|
@@ -621,9 +663,9 @@ Review and trim — the generator is liberal. Especially: drop `_id` fields when
|
|
|
621
663
|
|
|
622
664
|
## Related skills
|
|
623
665
|
|
|
624
|
-
- [[plutonium-resource]]
|
|
625
|
-
- [[plutonium-behavior]]
|
|
626
|
-
- [[plutonium-tenancy]]
|
|
627
|
-
- [[plutonium-auth]]
|
|
628
|
-
- [[plutonium-ui]]
|
|
629
|
-
- [[plutonium-testing]]
|
|
666
|
+
- [[plutonium-resource]]: what a resource IS (model + definition + scaffold options)
|
|
667
|
+
- [[plutonium-behavior]]: controllers, policies, interactions
|
|
668
|
+
- [[plutonium-tenancy]]: entity scoping, nested resources, invites
|
|
669
|
+
- [[plutonium-auth]]: Rodauth account configuration
|
|
670
|
+
- [[plutonium-ui]]: layouts, page classes, custom Phlex components, assets
|
|
671
|
+
- [[plutonium-testing]]: testing portals, packages, controllers
|