plutonium 0.64.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 +44 -41
- data/.claude/skills/plutonium-app/SKILL.md +115 -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 +168 -0
- data/.claude/skills/plutonium-kanban/SKILL.md +81 -49
- data/.claude/skills/plutonium-resource/SKILL.md +236 -145
- 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 +171 -95
- data/.claude/skills/plutonium-wizard/SKILL.md +93 -82
- data/CHANGELOG.md +80 -0
- data/CLAUDE.md +282 -210
- data/README.md +9 -9
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/sync-skills.mjs +6 -3
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +174 -0
- data/docs/blog/introducing-plutonium-i18n.md +90 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- 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 +300 -0
- data/docs/guides/index.md +4 -3
- data/docs/guides/kanban.md +56 -56
- 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/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +24 -24
- data/docs/reference/app/index.md +15 -18
- data/docs/reference/app/packages.md +8 -8
- data/docs/reference/app/portals.md +77 -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 +88 -31
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/generators/lite.md +7 -7
- data/docs/reference/i18n.md +262 -0
- data/docs/reference/index.md +11 -2
- data/docs/reference/kanban/authorization.md +9 -9
- data/docs/reference/kanban/dsl.md +33 -33
- data/docs/reference/kanban/index.md +1 -1
- data/docs/reference/kanban/positioning.md +18 -16
- data/docs/reference/resource/actions.md +51 -51
- data/docs/reference/resource/definition.md +166 -88
- data/docs/reference/resource/export.md +11 -7
- data/docs/reference/resource/index.md +16 -14
- data/docs/reference/resource/model.md +24 -24
- data/docs/reference/{positioning.md → resource/positioning.md} +78 -75
- data/docs/reference/resource/query.md +14 -12
- 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 +65 -25
- data/docs/reference/ui/components.md +20 -20
- data/docs/reference/ui/displays.md +19 -22
- 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 +45 -45
- 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/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +2 -2
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/profile/conn_generator.rb +6 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/resource/record/associated_with.rb +23 -2
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +7 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +4 -1
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +101 -10
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +64 -16
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
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,15 +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
|
|
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` |
|
|
88
89
|
| **[[plutonium-auth]]** | Rodauth install, account types (basic / admin / SaaS), profile resource, security section |
|
|
89
90
|
| **[[plutonium-tenancy]]** | Entity scoping (`associated_with`, `default_relation_scope`, three model shapes), nested resources, invites |
|
|
90
91
|
| **[[plutonium-testing]]** | `pu:test:install`, `pu:test:scaffold`, `ResourceCrud`/`ResourcePolicy`/`ResourceDefinition`/`ResourceModel`/`NestedResource`/`PortalAccess`/`ResourceInteraction`, `AuthHelpers` |
|
|
91
|
-
| **[[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 |
|
|
92
93
|
|
|
93
94
|
## Greenfield bootstrap bundle
|
|
94
95
|
|
|
@@ -96,10 +97,10 @@ Triggers: installing Plutonium, building a new app, adding the first resource in
|
|
|
96
97
|
|
|
97
98
|
**Load these before writing code:**
|
|
98
99
|
|
|
99
|
-
1. **`plutonium-app
|
|
100
|
-
2. **`plutonium-resource
|
|
101
|
-
3. **`plutonium-behavior
|
|
102
|
-
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.
|
|
103
104
|
|
|
104
105
|
Add when relevant:
|
|
105
106
|
- **`plutonium-auth`** for login / accounts / profile.
|
|
@@ -113,7 +114,7 @@ Add when relevant:
|
|
|
113
114
|
| Install Plutonium, create a portal or package, mount engines, register routes (incl. singular / custom routes) | **[[plutonium-app]]** |
|
|
114
115
|
| Run `pu:res:scaffold`, pick field types, set scaffold options | **[[plutonium-resource]]** |
|
|
115
116
|
| Edit a model, add associations, use `has_cents`, override `to_param` / `to_label` | **[[plutonium-resource]]** |
|
|
116
|
-
| 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]]** |
|
|
117
118
|
| Override a controller action, hook, redirect, or `resource_params` | **[[plutonium-behavior]]** |
|
|
118
119
|
| Write `relation_scope`, `permitted_attributes_for_*`, `permitted_associations`, action methods, or any policy override | **[[plutonium-behavior]]** (+ **[[plutonium-tenancy]]** if scoping) |
|
|
119
120
|
| Write an interaction class for business logic | **[[plutonium-behavior]]** |
|
|
@@ -121,23 +122,24 @@ Add when relevant:
|
|
|
121
122
|
| Scope a model to a tenant, write `associated_with`, set portal entity strategy | **[[plutonium-tenancy]]** |
|
|
122
123
|
| Configure parent/child nested routes, custom parent resolution | **[[plutonium-tenancy]]** |
|
|
123
124
|
| Set up user invitations or entity membership | **[[plutonium-tenancy]]** |
|
|
124
|
-
| Build or customize a kanban board view
|
|
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]]** |
|
|
125
127
|
| Build a custom page (override `ShowPage`/`IndexPage`/`NewPage`/`EditPage`), custom form, custom display, custom table, custom Phlex component | **[[plutonium-ui]]** |
|
|
126
128
|
| Configure Tailwind, register Stimulus controllers, edit design tokens, theme forms/displays/tables, write a custom layout | **[[plutonium-ui]]** |
|
|
127
129
|
| Install Rodauth, set up accounts, configure login flow, add the profile resource | **[[plutonium-auth]]** |
|
|
128
130
|
| Write tests for a resource, run `pu:test:scaffold`, include `Plutonium::Testing::*` concerns | **[[plutonium-testing]]** |
|
|
129
|
-
| 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]]** |
|
|
130
132
|
|
|
131
133
|
## Resource architecture at a glance
|
|
132
134
|
|
|
133
|
-
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:
|
|
134
136
|
|
|
135
137
|
| Layer | File | Purpose |
|
|
136
138
|
|---|---|---|
|
|
137
139
|
| **Model** | `app/models/post.rb` | Data, validations, associations |
|
|
138
|
-
| **Definition** | `app/definitions/post_definition.rb` | UI
|
|
139
|
-
| **Policy** | `app/policies/post_policy.rb` | Authorization
|
|
140
|
-
| **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) |
|
|
141
143
|
|
|
142
144
|
Plus one optional fifth layer:
|
|
143
145
|
|
|
@@ -169,6 +171,7 @@ Every Plutonium generator is discoverable via `rails g pu:<tab>`. Always pass `-
|
|
|
169
171
|
| `pu:eject:shell` | Eject topbar/sidebar partials | `plutonium-ui` |
|
|
170
172
|
| `pu:test:install` | Install `Plutonium::Testing` scaffolding | `plutonium-testing` |
|
|
171
173
|
| `pu:test:scaffold NAME --portals=...` | Scaffold integration tests | `plutonium-testing` |
|
|
174
|
+
| `pu:dashboard NAME --dest=PORTAL [--at=/]` | Dashboard class + `register_dashboard` route | `plutonium-dashboard` |
|
|
172
175
|
| `pu:skills:sync` | Sync Plutonium Claude skills into the project | (this skill) |
|
|
173
176
|
|
|
174
177
|
## Unattended execution
|
|
@@ -188,8 +191,8 @@ Meta-generators (`pu:saas:setup`) propagate flags to the generators they chain.
|
|
|
188
191
|
## Workflow summary
|
|
189
192
|
|
|
190
193
|
1. **Load the bootstrap bundle** (or the targeted skill from the router table).
|
|
191
|
-
2. **Generate
|
|
192
|
-
3. **Migrate
|
|
193
|
-
4. **Connect
|
|
194
|
-
5. **Customize
|
|
195
|
-
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
|
|
|
@@ -263,21 +260,31 @@ end
|
|
|
263
260
|
|
|
264
261
|
This is loaded from `config/application.rb`. Migrations from all packages are picked up by `rails db:migrate` automatically.
|
|
265
262
|
|
|
263
|
+
## Locale files
|
|
264
|
+
|
|
265
|
+
Packages and portals are Rails engines, so `packages/<name>/config/locales/*.yml` loads automatically, ahead of the app's own `config/locales` (app wins, then packages, then the gem's defaults). Both generators scaffold `config/locales/en.yml`.
|
|
266
|
+
|
|
267
|
+
- Feature package: model/attribute names (`activerecord.models`, `activerecord.attributes`) and field text (`plutonium.fields.<model>.<attr>.{placeholder,hint,description}`).
|
|
268
|
+
- Portal: portal-specific wording under `plutonium.portals.<portal_namespace>.…` (same tree as the global keys: `fields`, `actions`, `scopes`, `filters`, `kanban_columns`, `values`). Wins over the global key only inside that portal.
|
|
269
|
+
- Any of Plutonium's own strings (`plutonium.*` in the gem's `config/locales/en/*.yml`) can be overridden from either place. Pagination text comes from Pagy's dictionaries instead.
|
|
270
|
+
|
|
271
|
+
Switch locale with a normal Rails `around_action` in the portal's controller concern; Plutonium reads `I18n.locale` and never sets it. See `docs/reference/i18n.md`.
|
|
272
|
+
|
|
266
273
|
## When to use which
|
|
267
274
|
|
|
268
|
-
**Feature packages**
|
|
275
|
+
**Feature packages** are domain logic that:
|
|
269
276
|
- Could be reused across multiple portals (admin and customer both edit `Blogging::Post`)
|
|
270
277
|
- Has no inherent UI / auth (it's just behavior)
|
|
271
278
|
- You want to keep isolated from other domains (`billing` should not depend on `blogging`)
|
|
272
279
|
|
|
273
|
-
**Portal packages**
|
|
280
|
+
**Portal packages** are user-facing surfaces that:
|
|
274
281
|
- Have a specific auth flow (admin vs customer vs public)
|
|
275
282
|
- Render different views of the same underlying resources
|
|
276
283
|
- Need different policies / definitions per audience
|
|
277
284
|
|
|
278
285
|
---
|
|
279
286
|
|
|
280
|
-
# Part 3
|
|
287
|
+
# Part 3: Portal Engines
|
|
281
288
|
|
|
282
289
|
A portal is a Rails engine mixing in `Plutonium::Portal::Engine`. It defines its own routes, controller concern, and (optionally) entity scoping.
|
|
283
290
|
|
|
@@ -292,7 +299,7 @@ rails g pu:pkg:portal <name>
|
|
|
292
299
|
| Option | Description |
|
|
293
300
|
|---|---|
|
|
294
301
|
| `--auth=NAME` | Rodauth account to use (e.g. `--auth=user`) |
|
|
295
|
-
| `--public` | Public access
|
|
302
|
+
| `--public` | Public access, no auth |
|
|
296
303
|
| `--byo` | Bring your own auth |
|
|
297
304
|
| `--scope=CLASS` | Entity class for multi-tenancy (e.g. `--scope=Organization`) |
|
|
298
305
|
|
|
@@ -323,7 +330,9 @@ end
|
|
|
323
330
|
|
|
324
331
|
## Controller concern (auth)
|
|
325
332
|
|
|
326
|
-
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.
|
|
327
336
|
|
|
328
337
|
### Rodauth
|
|
329
338
|
|
|
@@ -361,19 +370,62 @@ end
|
|
|
361
370
|
|
|
362
371
|
## Mounting
|
|
363
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
|
+
|
|
364
375
|
```ruby
|
|
365
|
-
# config/routes.rb
|
|
376
|
+
# packages/admin_portal/config/routes.rb (after the engine's routes.draw block)
|
|
366
377
|
Rails.application.routes.draw do
|
|
367
|
-
# Authenticated mount
|
|
368
378
|
constraints Rodauth::Rails.authenticate(:user) do
|
|
369
379
|
mount AdminPortal::Engine, at: "/admin"
|
|
370
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
|
+
```
|
|
371
416
|
|
|
372
|
-
|
|
373
|
-
|
|
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`:
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
DeskPortal::Engine.routes.draw do
|
|
421
|
+
get "dashboard", to: "dashboard#index", as: :root
|
|
422
|
+
register_resource ::Comment
|
|
423
|
+
# register resources above.
|
|
374
424
|
end
|
|
375
425
|
```
|
|
376
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
|
+
|
|
377
429
|
## Controller hierarchy
|
|
378
430
|
|
|
379
431
|
Portal controllers inherit from the feature-package controller if one exists, OR from the portal's `ResourceController` otherwise.
|
|
@@ -402,13 +454,13 @@ end
|
|
|
402
454
|
## Per-portal overrides
|
|
403
455
|
|
|
404
456
|
```ruby
|
|
405
|
-
# Definition
|
|
457
|
+
# Definition: how fields render (NOT whether they appear)
|
|
406
458
|
class AdminPortal::PostDefinition < ::PostDefinition
|
|
407
459
|
scope :pending_review
|
|
408
460
|
input :internal_notes, hint: "Not shown to the author"
|
|
409
461
|
end
|
|
410
462
|
|
|
411
|
-
# Policy
|
|
463
|
+
# Policy: whether a field appears at all
|
|
412
464
|
class AdminPortal::PostPolicy < ::PostPolicy
|
|
413
465
|
include AdminPortal::ResourcePolicy
|
|
414
466
|
def destroy? = true
|
|
@@ -426,7 +478,7 @@ end
|
|
|
426
478
|
|
|
427
479
|
---
|
|
428
480
|
|
|
429
|
-
# Part 4
|
|
481
|
+
# Part 4: Routes & `register_resource`
|
|
430
482
|
|
|
431
483
|
Portal routes live in `packages/<name>_portal/config/routes.rb`:
|
|
432
484
|
|
|
@@ -442,7 +494,7 @@ AdminPortal::Engine.routes.draw do
|
|
|
442
494
|
end
|
|
443
495
|
```
|
|
444
496
|
|
|
445
|
-
## `register_resource
|
|
497
|
+
## `register_resource`: what it does
|
|
446
498
|
|
|
447
499
|
For each call, Plutonium auto-generates:
|
|
448
500
|
|
|
@@ -450,11 +502,11 @@ For each call, Plutonium auto-generates:
|
|
|
450
502
|
- Nested routes for every registered `has_many` / `has_one` parent (prefixed `nested_`)
|
|
451
503
|
- Route names that `resource_url_for` can resolve
|
|
452
504
|
|
|
453
|
-
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.
|
|
454
506
|
|
|
455
507
|
## Singular (singleton) resources
|
|
456
508
|
|
|
457
|
-
For resources with no collection
|
|
509
|
+
For resources with no collection, a single per-user `Profile`, app-wide `Settings`, etc.:
|
|
458
510
|
|
|
459
511
|
```ruby
|
|
460
512
|
register_resource ::Profile, singular: true
|
|
@@ -475,6 +527,10 @@ Use the `--singular` flag on `pu:res:conn`:
|
|
|
475
527
|
rails g pu:res:conn Profile --dest=customer_portal --singular
|
|
476
528
|
```
|
|
477
529
|
|
|
530
|
+
## Dashboards
|
|
531
|
+
|
|
532
|
+
`register_dashboard SomeDashboard, at: "overview"` mounts a `Plutonium::Dashboard::Base` subclass (metric / chart / free-form cards, each in its own lazy turbo frame) on the portal at `/dashboards/overview`; `at: "/"` (generator: `--at=/`) makes it the portal root in place of `root to: "dashboard#index"`; delete the generated `DashboardController` and view afterwards. Generate one with `rails g pu:dashboard Overview --dest=<portal>`. See [[plutonium-dashboard]].
|
|
533
|
+
|
|
478
534
|
## Custom member / collection routes
|
|
479
535
|
|
|
480
536
|
```ruby
|
|
@@ -491,9 +547,9 @@ register_resource ::Post do
|
|
|
491
547
|
end
|
|
492
548
|
```
|
|
493
549
|
|
|
494
|
-
**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.
|
|
495
551
|
|
|
496
|
-
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.
|
|
497
553
|
|
|
498
554
|
## Cross-package and nested URLs
|
|
499
555
|
|
|
@@ -501,7 +557,7 @@ See [[plutonium-behavior]] for full `resource_url_for` signature and [[plutonium
|
|
|
501
557
|
|
|
502
558
|
---
|
|
503
559
|
|
|
504
|
-
# Part 5
|
|
560
|
+
# Part 5: Connecting Resources to Portals (`pu:res:conn`)
|
|
505
561
|
|
|
506
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.
|
|
507
563
|
|
|
@@ -511,7 +567,7 @@ A resource is invisible until connected to at least one portal. The generator wi
|
|
|
511
567
|
rails g pu:res:conn RESOURCE [RESOURCE...] --dest=PORTAL_NAME [--singular]
|
|
512
568
|
```
|
|
513
569
|
|
|
514
|
-
Pass resources directly
|
|
570
|
+
Pass resources directly; this avoids interactive prompts. No `--src` needed.
|
|
515
571
|
|
|
516
572
|
## Usage
|
|
517
573
|
|
|
@@ -526,7 +582,7 @@ rails g pu:res:conn Blogging::Post Blogging::Comment --dest=admin_portal
|
|
|
526
582
|
rails g pu:res:conn Profile --dest=customer_portal --singular
|
|
527
583
|
```
|
|
528
584
|
|
|
529
|
-
**Run after migrations
|
|
585
|
+
**Run after migrations**: the generator reads model columns to seed the policy's `permitted_attributes_for_*`.
|
|
530
586
|
|
|
531
587
|
## What gets generated
|
|
532
588
|
|
|
@@ -546,7 +602,7 @@ register_resource ::Post
|
|
|
546
602
|
register_resource ::Profile, singular: true # if --singular
|
|
547
603
|
```
|
|
548
604
|
|
|
549
|
-
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.
|
|
550
606
|
|
|
551
607
|
### Generated controller
|
|
552
608
|
|
|
@@ -576,7 +632,7 @@ class AdminPortal::PostPolicy < ::PostPolicy
|
|
|
576
632
|
end
|
|
577
633
|
```
|
|
578
634
|
|
|
579
|
-
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.
|
|
580
636
|
|
|
581
637
|
---
|
|
582
638
|
|
|
@@ -607,9 +663,9 @@ Review and trim — the generator is liberal. Especially: drop `_id` fields when
|
|
|
607
663
|
|
|
608
664
|
## Related skills
|
|
609
665
|
|
|
610
|
-
- [[plutonium-resource]]
|
|
611
|
-
- [[plutonium-behavior]]
|
|
612
|
-
- [[plutonium-tenancy]]
|
|
613
|
-
- [[plutonium-auth]]
|
|
614
|
-
- [[plutonium-ui]]
|
|
615
|
-
- [[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
|