plutonium 0.55.0 โ†’ 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +1 -1
  3. data/.claude/skills/plutonium-app/SKILL.md +1 -1
  4. data/.claude/skills/plutonium-auth/SKILL.md +1 -1
  5. data/.claude/skills/plutonium-resource/SKILL.md +56 -3
  6. data/.claude/skills/plutonium-ui/SKILL.md +15 -2
  7. data/CHANGELOG.md +44 -0
  8. data/CONTRIBUTING.md +1 -1
  9. data/README.md +38 -16
  10. data/app/assets/plutonium.css +1 -1
  11. data/app/assets/plutonium.js +94 -26
  12. data/app/assets/plutonium.js.map +2 -2
  13. data/app/assets/plutonium.min.js +9 -9
  14. data/app/assets/plutonium.min.js.map +3 -3
  15. data/config/initializers/rabl.rb +16 -0
  16. data/docs/.vitepress/config.ts +1 -0
  17. data/docs/getting-started/installation.md +2 -2
  18. data/docs/getting-started/tutorial/02-first-resource.md +1 -1
  19. data/docs/getting-started/tutorial/03-authentication.md +3 -3
  20. data/docs/guides/adding-resources.md +1 -1
  21. data/docs/guides/authentication.md +1 -1
  22. data/docs/guides/creating-packages.md +1 -1
  23. data/docs/guides/multi-tenancy.md +1 -1
  24. data/docs/guides/nested-resources.md +1 -1
  25. data/docs/guides/user-invites.md +1 -1
  26. data/docs/guides/user-profile.md +1 -1
  27. data/docs/public/templates/lite.rb +10 -0
  28. data/docs/reference/app/generators.md +3 -3
  29. data/docs/reference/app/index.md +1 -1
  30. data/docs/reference/app/portals.md +1 -1
  31. data/docs/reference/auth/profile.md +1 -1
  32. data/docs/reference/generators/lite.md +65 -0
  33. data/docs/reference/resource/actions.md +55 -0
  34. data/docs/reference/resource/definition.md +18 -2
  35. data/docs/reference/resource/index.md +1 -1
  36. data/docs/reference/tenancy/invites.md +1 -1
  37. data/docs/reference/ui/assets.md +14 -0
  38. data/docs/reference/ui/displays.md +27 -1
  39. data/docs/reference/ui/forms.md +2 -1
  40. data/docs/reference/ui/layouts.md +33 -0
  41. data/docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md +857 -0
  42. data/docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md.tasks.json +45 -0
  43. data/docs/superpowers/specs/2026-06-04-sqlite-tune-maintenance-generators-design.md +238 -0
  44. data/gemfiles/rails_7.gemfile.lock +1 -1
  45. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  46. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  47. data/lib/generators/pu/core/typespec/typespec_generator.rb +1 -1
  48. data/lib/generators/pu/core/update/update_generator.rb +4 -1
  49. data/lib/generators/pu/lib/plutonium_generators/concerns/configures_recurring.rb +89 -0
  50. data/lib/generators/pu/lite/maintenance/maintenance_generator.rb +45 -0
  51. data/lib/generators/pu/lite/maintenance/templates/app/jobs/sqlite_maintenance_job.rb.tt +60 -0
  52. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +4 -51
  53. data/lib/generators/pu/lite/rails_pulse/templates/config/initializers/rails_pulse.rb.tt +1 -1
  54. data/lib/generators/pu/lite/tune/tune_generator.rb +105 -0
  55. data/lib/generators/pu/saas/welcome_generator.rb +1 -1
  56. data/lib/plutonium/action/base.rb +19 -2
  57. data/lib/plutonium/action/condition_context.rb +33 -0
  58. data/lib/plutonium/models/has_cents.rb +10 -0
  59. data/lib/plutonium/resource/controllers/interactive_actions.rb +19 -2
  60. data/lib/plutonium/routing/mapper_extensions.rb +5 -0
  61. data/lib/plutonium/ui/display/base.rb +9 -0
  62. data/lib/plutonium/ui/display/components/badge.rb +83 -0
  63. data/lib/plutonium/ui/display/components/boolean.rb +28 -6
  64. data/lib/plutonium/ui/display/components/currency.rb +50 -0
  65. data/lib/plutonium/ui/display/options/inferred_types.rb +13 -0
  66. data/lib/plutonium/ui/display/theme.rb +5 -0
  67. data/lib/plutonium/ui/form/base.rb +5 -0
  68. data/lib/plutonium/ui/form/components/toggle.rb +14 -0
  69. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +14 -25
  70. data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +67 -0
  71. data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +5 -38
  72. data/lib/plutonium/ui/form/interaction.rb +7 -2
  73. data/lib/plutonium/ui/form/options/inferred_types.rb +2 -0
  74. data/lib/plutonium/ui/form/resource.rb +1 -0
  75. data/lib/plutonium/ui/form/theme.rb +12 -0
  76. data/lib/plutonium/ui/grid/card.rb +61 -23
  77. data/lib/plutonium/ui/layout/icon_rail.rb +29 -9
  78. data/lib/plutonium/ui/page/index.rb +1 -1
  79. data/lib/plutonium/ui/page/show.rb +1 -1
  80. data/lib/plutonium/ui/sidebar_menu.rb +29 -0
  81. data/lib/plutonium/ui/table/resource.rb +2 -2
  82. data/lib/plutonium/version.rb +1 -1
  83. data/package.json +1 -1
  84. data/plutonium.gemspec +5 -4
  85. data/src/css/components.css +126 -0
  86. data/src/js/controllers/dirty_form_guard_controller.js +55 -4
  87. data/src/js/controllers/nested_resource_form_fields_controller.js +35 -12
  88. data/src/js/controllers/resource_drop_down_controller.js +49 -14
  89. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecf3e56a7d08f87ee2e77af368b4059655aeea666fcfefcf238256ae8564a9e8
4
- data.tar.gz: d70895506dda46cafc6933b9a972261365cdbddaff728e6eb8ad84bb3857ce0a
3
+ metadata.gz: 17f6996a0503aeccb6070cb0c379d09bfd253e2356a839745368bcaf0110e377
4
+ data.tar.gz: 0603760cfc2a10d6bedaa4b225b33fc5cf5b632a36829c4e6c9e9c4ce7b7971b
5
5
  SHA512:
6
- metadata.gz: d8e6d17854893f67732bfb97ecc7cf744501d5469df99bfd0c3e5c9b8f39d16818c93c270e586f6cd486c4559465db33e4e3cdfb6fff0fa9dfb7d3cf027678ef
7
- data.tar.gz: af4b9df9e92dd343c6db4d73d39d3cfcad6a08fc3a9ce74ad47afabfb25056c0bccc7e8a924ac3a6070e4767a7e05e69d012cff6073732c04bae425bc33dd894
6
+ metadata.gz: b77a88b0c0e04b5a9e143c88b292ec305f7d5c8ef74efe3e0891f20c772e63eef35383a7785338863f4cd9a942c5b76c94c1fa6fedc2995c41da8df680f1e390
7
+ data.tar.gz: 7dd9fd4420c8145227929c4f6ec434520c6701ea387767d9395bb74b413d2e99ce451611c89797604a1cf5a62bb51368a4aa2f5c79d1b698a1c11842979dcb29
@@ -127,7 +127,7 @@ Meta-generators (`pu:saas:setup`) propagate flags to the generators they chain.
127
127
 
128
128
  1. **Load the bootstrap bundle** (or the targeted skill from the router table).
129
129
  2. **Generate** โ€” `rails g pu:res:scaffold Model field:type ... --dest=main_app`.
130
- 3. **Migrate** โ€” `rails db:migrate`.
130
+ 3. **Migrate** โ€” `rails db:prepare`.
131
131
  4. **Connect** โ€” `rails g pu:res:conn Model --dest=portal_name`.
132
132
  5. **Customize** โ€” edit definition / policy as needed.
133
133
  6. **Verify** โ€” hit the route in the browser.
@@ -62,7 +62,7 @@ rails generate pu:pkg:portal admin --auth=user
62
62
 
63
63
  # 4. First resource
64
64
  rails generate pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
65
- rails db:migrate
65
+ rails db:prepare
66
66
 
67
67
  # 5. Connect resource to portal
68
68
  rails generate pu:res:conn Post --dest=admin_portal
@@ -279,7 +279,7 @@ rails g pu:profile:setup date_of_birth:date bio:text \
279
279
  rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
280
280
  --dest=customer
281
281
 
282
- rails db:migrate
282
+ rails db:prepare
283
283
 
284
284
  rails generate pu:profile:conn --dest=customer_portal
285
285
  ```
@@ -29,7 +29,7 @@ For tenancy / `associated_with` / `relation_scope`, load [[plutonium-tenancy]].
29
29
  1. Pick destination: `--dest=main_app` or `--dest=package_name`.
30
30
  2. Run `rails g pu:res:scaffold ResourceName field:type ... --dest=<dest>`.
31
31
  3. Review the generated migration โ€” add cascade deletes, composite indexes, defaults.
32
- 4. `rails db:migrate`.
32
+ 4. `rails db:prepare`.
33
33
  5. `rails g pu:res:conn ResourceName --dest=<portal_name>`.
34
34
  6. Customize the policy's `permitted_attributes_for_*` as needed.
35
35
  7. Open the portal route in the browser.
@@ -484,7 +484,7 @@ end
484
484
  | Text | `:string`, `:text`, `:email`, `:url`, `:tel`, `:password` |
485
485
  | Rich Text | `:markdown` (EasyMDE) |
486
486
  | Numeric | `:number`, `:integer`, `:decimal`, `:range` |
487
- | Boolean | `:boolean` |
487
+ | Boolean | `:toggle` / `:switch` (switch โ€” **default** for boolean columns), `:boolean` (plain checkbox) |
488
488
  | Date/Time | `:date`, `:time`, `:datetime` |
489
489
  | Selection | `:select`, `:slim_select`, `:radio_buttons`, `:check_boxes` |
490
490
  | Files | `:file`, `:uppy`, `:attachment` |
@@ -493,7 +493,26 @@ end
493
493
 
494
494
  ### Display Types (show / index)
495
495
 
496
- `:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`
496
+ `:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:badge`, `:currency`, `:color`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`
497
+
498
+ #### Auto-inferred display formatting
499
+
500
+ These render automatically โ€” declare an `as:` only to override or pass options:
501
+
502
+ | Column | Renders as | Notes |
503
+ |--------|-----------|-------|
504
+ | `boolean` | Yes/No pill (`:boolean`) | green "Yes" / neutral "No". Override labels: `true_label:`, `false_label:` |
505
+ | `enum` | colored status badge (`:badge`) | known statuses (active, pending, failedโ€ฆ) auto-colored; unknown values get a stable decorative color |
506
+ | `has_cents` decimal | currency (`:currency`) | delimited, 2 decimals, **no symbol** unless you add `unit:` |
507
+
508
+ ```ruby
509
+ display :status, as: :badge, colors: {archived: :neutral, vip: :accent} # override per-value color
510
+ display :price, as: :currency, unit: "ยฃ" # literal symbol
511
+ display :price, as: :currency, unit: :currency_symbol # Symbol โ†’ read off the record (per-row)
512
+ display :active, as: :boolean, true_label: "Live", false_label: "Off"
513
+ ```
514
+
515
+ Badge color keys: `:neutral`, `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:accent`.
497
516
 
498
517
  ## Field Options
499
518
 
@@ -1031,6 +1050,10 @@ action :name,
1031
1050
  collection_record_action: true,
1032
1051
  bulk_action: true,
1033
1052
 
1053
+ # Conditional visibility โ€” display-only toggle, NOT authorization (see below).
1054
+ # `-> { false }` keeps the route live but hides the button (e.g. API-only).
1055
+ condition: -> { params[:beta] == "1" },
1056
+
1034
1057
  # Grouping
1035
1058
  category: :primary, # :primary, :secondary, :danger
1036
1059
  position: 50,
@@ -1043,6 +1066,36 @@ action :name,
1043
1066
  size: :lg # :sm / :md / :lg / :xl / :auto / :full โ€” overrides definition's modal size
1044
1067
  ```
1045
1068
 
1069
+ ### Conditional Actions (`condition:`)
1070
+
1071
+ Like `condition:` on inputs/displays/columns โ€” define an action but render its **button** only when a runtime proc is truthy. The action and its route stay live either way; `condition:` only toggles the UI.
1072
+
1073
+ Headline use case: **expose an action's endpoint without a button** โ€” one you call from the API, a webhook, or another service. Hide it with an always-falsy condition; the route still works:
1074
+
1075
+ ```ruby
1076
+ # Defined and callable (API / programmatic), but no button anywhere:
1077
+ action :sync_inventory, interaction: SyncInventoryInteraction, condition: -> { false }
1078
+
1079
+ # Per-record display state โ€” object is the row/shown record:
1080
+ action :reopen, interaction: ReopenInteraction, condition: -> { object.closed? }
1081
+
1082
+ # View/request-level toggle (feature flag, beta mode):
1083
+ action :preview, interaction: PreviewInteraction, condition: -> { params[:beta] == "1" }
1084
+ ```
1085
+
1086
+ Inside the proc, `object`/`record` is the contextual record โ€” the row/shown record for **record** and **collection-record** actions, **nil** for **resource** and **bulk** actions (guard with `object&.โ€ฆ` if shared). Every other call delegates to the **view context**: `current_user`, `current_parent`, `params`, `request`, `allowed_to?`, `resource_record!`, etc. `object` is evaluated per row in tables/grids, so per-record show/hide works there.
1087
+
1088
+ ๐Ÿšจ **`condition:` is NOT authorization โ€” it only hides the button.** A hidden action still has a live route; anyone with the URL can trigger it. "Who may run this" belongs in the policy:
1089
+
1090
+ ```ruby
1091
+ # ๐Ÿšซ WRONG โ€” does not stop non-admins; the route is live.
1092
+ action :wipe, interaction: WipeInteraction, condition: -> { current_user.admin? }
1093
+ # โœ… RIGHT โ€” authorization in the policy, enforced regardless of condition:
1094
+ def wipe? = current_user.admin?
1095
+ ```
1096
+
1097
+ The two compose: an action's button shows only when the policy permits **and** the condition is truthy; execution is gated by the policy alone. Use `object` in `condition:` for per-record *display*; use the policy for per-record *authorization*.
1098
+
1046
1099
  `Action#with(...)` โ€” actions are frozen value objects; clone with overrides:
1047
1100
 
1048
1101
  ```ruby
@@ -252,6 +252,7 @@ render field(:title).wrapped(class: "col-span-full") { |f| f.input_tag }
252
252
  | `input_tag` | text (auto-detected type) |
253
253
  | `string_tag`, `text_tag`, `number_tag`, `email_tag`, `password_tag`, `url_tag`, `tel_tag`, `hidden_tag` | standard HTML inputs |
254
254
  | `checkbox_tag`, `select_tag`, `radio_button_tag` | standard |
255
+ | `toggle_tag` / `switch_tag` | switch-styled boolean (`as: :toggle` / `:switch`) โ€” default for boolean columns; `as: :boolean` for a plain checkbox |
255
256
 
256
257
  ### Plutonium-enhanced tags
257
258
 
@@ -513,6 +514,16 @@ rails generate pu:eject:layout
513
514
 
514
515
  These copy `_resource_header.html.erb`, `_resource_sidebar.html.erb`, and `layouts/resource.html.erb` into the portal so you can edit them directly.
515
516
 
517
+ ## Navigation menu items
518
+
519
+ The sidebar/icon-rail menu is built with `Phlexi::Menu::Builder` in `_resource_sidebar.html.erb`. Extra options on `item` are spread onto the rendered `<a>`, so an item can opt into `target` / `rel` / `data:` / `aria:`:
520
+
521
+ ```ruby
522
+ m.item "Inbox", url: inbox_path, icon: Icon, target: "_blank", rel: "noopener", data: {turbo_frame: "_top"}
523
+ ```
524
+
525
+ Applies to both shells (icon-rail leaf, parent flyout trigger, and flyout children; classic sidebar). Framework `class`/`data`/`aria` win on conflict โ€” `class:` merges with the base classes, and on a parent trigger `data:`/`aria:` merge with the flyout wiring so options can't break the toggle. Phlexi's reserved `:active` key is never emitted as an attribute.
526
+
516
527
  ## Custom layout class (Phlex)
517
528
 
518
529
  ```ruby
@@ -733,7 +744,8 @@ Ready-to-use styled components in `src/css/components.css`. **Prefer these over
733
744
  ### Inputs, cards, panels, tables, toolbars, empty states
734
745
 
735
746
  ```
736
- .pu-input / -invalid / -valid .pu-label / -required .pu-hint / .pu-error .pu-checkbox
747
+ .pu-input / -invalid / -valid .pu-label / -required .pu-hint / .pu-error .pu-checkbox / .pu-toggle
748
+ .pu-badge / -neutral / -primary / -secondary / -success / -danger / -warning / -info / -accent
737
749
  .pu-card / .pu-card-body
738
750
  .pu-panel-header / -title / -description
739
751
  .pu-table-wrapper / .pu-table / -header / -header-cell / -body-row / -body-row-selected / -body-cell / .pu-selection-cell
@@ -845,7 +857,7 @@ end
845
857
 
846
858
  ### Display theme keys
847
859
 
848
- `fields_wrapper`, `label`, `description`, `string`, `text`, `link`, `email`, `phone`, `markdown`, `json`.
860
+ `fields_wrapper`, `label`, `description`, `string`, `text`, `link`, `email`, `phone`, `markdown`, `json`, `boolean`, `badge`, `currency`, `color`.
849
861
 
850
862
  ## Table theme
851
863
 
@@ -914,6 +926,7 @@ end
914
926
  - **Dark mode is `selector`, not `class`.** Toggle via `document.documentElement.classList.toggle('dark')`.
915
927
  - **Tokens are CSS variables, not Tailwind keys** โ€” `bg-[var(--pu-surface)]`, not `bg-pu-surface`.
916
928
  - **`render_actions` is mandatory in custom `form_template`** โ€” otherwise no submit button.
929
+ - **Dropdowns (`resource-drop-down`) teleport their menu to `<body>` while open.** popper's `fixed` strategy alone is still clipped by a transformed + `overflow:hidden` ancestor (e.g. grid cards, app shells), so the controller reparents the open menu to `<body>` and restores it on close. Don't rely on the menu being a DOM child of its trigger while open.
917
930
 
918
931
  ---
919
932
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,47 @@
1
+ ## [0.56.1] - 2026-06-05
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - *(actions)* Add display-only condition: to actions
6
+
7
+ ### ๐Ÿ› Bug Fixes
8
+
9
+ - *(dummy)* Version-adapt kitchen_sinks migration
10
+
11
+ ### ๐Ÿ“š Documentation
12
+
13
+ - Recommend db:prepare, firm up README, document conditional actions
14
+ ## [0.56.0] - 2026-06-05
15
+
16
+ ### ๐Ÿš€ Features
17
+
18
+ - *(ui)* Auto-rendered components for boolean, enum & money fields
19
+ - *(generators/lite)* Add pu:lite:tune and pu:lite:maintenance for SQLite tuning + maintenance
20
+ - *(ui)* Sidebar menu items accept arbitrary link attributes
21
+ - *(ui)* Restore deleted nested rows + shared, polished removed bar
22
+ - *(ui)* Type-aware grid cards + overhaul KitchenSink dummy resource
23
+
24
+ ### ๐Ÿ› Bug Fixes
25
+
26
+ - *(generators/update)* Sync skills in a fresh process; pin post-install notice to 0.49.0
27
+ - *(ui)* Native multi-selects render at a usable height
28
+ - *(ui)* Dropdown menu teleports to <body> to escape overflow clipping
29
+ - *(routing)* Force :resources route_type for has_many nested routes
30
+ - *(ui)* Record-scoped commit URL for actions with record_action: false
31
+ - *(api)* Serialize JSON values via as_json (ISO 8601 datetimes)
32
+ - *(generators)* Add reading role to Rails Pulse connects_to config
33
+ - *(actions)* Bind subject during interaction param extraction
34
+ - *(ui)* Dirty-form-guard tracks edits via first-interaction baseline
35
+ - *(ui)* Give the JSON form input dark-mode styling
36
+
37
+ ### ๐Ÿงช Testing
38
+
39
+ - Fix stale generator assertions and drop committed dummy schema
40
+ - *(dummy)* Add KitchenSink resource exercising every input/display type
41
+
42
+ ### โš™๏ธ Miscellaneous Tasks
43
+
44
+ - Sync appraisal gemfile.lock files to v0.55.0
1
45
  ## [0.55.0] - 2026-06-03
2
46
 
3
47
  ### ๐Ÿš€ Features
data/CONTRIBUTING.md CHANGED
@@ -153,7 +153,7 @@ Use the dummy app:
153
153
  ```bash
154
154
  cd test/dummy
155
155
  rails g pu:res:scaffold TestModel name:string --dest=main_app
156
- rails db:migrate
156
+ rails db:prepare
157
157
  bin/dev
158
158
  ```
159
159
 
data/README.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/plutonium.svg)](https://badge.fury.io/rb/plutonium)
4
4
  [![Ruby](https://github.com/radioactive-labs/plutonium-core/actions/workflows/main.yml/badge.svg)](https://github.com/radioactive-labs/plutonium-core/actions/workflows/main.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.txt)
5
6
 
6
- Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, and AI-ready. Plutonium picks up where Rails left off, adding application-level concepts that make building complex apps faster.
7
+ **The Rails framework for things you should never write again.**
8
+
9
+ Convention over configuration, extended to everything you keep rebuilding: **CRUD. Auth. Authorization. Multi-tenancy. Admin portals. Search, filters, bulk actions.** All generated. All customizable. All Rails.
7
10
 
8
11
  ## Quick Start
9
12
 
@@ -12,20 +15,36 @@ rails new myapp -a propshaft -j esbuild -c tailwind \
12
15
  -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
13
16
  ```
14
17
 
15
- Then create your first resource:
18
+ Then scaffold a resource, create a portal, and connect them:
16
19
 
17
20
  ```bash
18
21
  cd myapp
19
- rails g pu:res:scaffold Post title:string body:text --dest=main_app
20
- rails db:migrate
22
+
23
+ # Scaffold a resource โ€” model, migration, definition, policy
24
+ rails g pu:res:scaffold Post title:string body:text published_at:datetime --dest=main_app
25
+ rails db:prepare
26
+
27
+ # Create a portal (web interface) and connect the resource to it
28
+ rails g pu:pkg:portal app --public
29
+ rails g pu:res:conn Post --dest=app_portal
30
+
21
31
  bin/dev
22
32
  ```
23
33
 
24
- Visit `http://localhost:3000` - you have a complete CRUD interface.
34
+ Visit `http://localhost:3000/app/posts` โ€” you have a complete CRUD interface.
25
35
 
26
- ## What You Get
36
+ ## What You Stop Writing
27
37
 
28
- **Resource-oriented architecture** - Models, policies, definitions, and controllers that work together:
38
+ Same scaffold command you already know. A very different surface area.
39
+
40
+ ```bash
41
+ rails g scaffold Post ... # Rails: just CRUD
42
+ rails g pu:res:scaffold Post ... # Plutonium: full CRUD + search + filters + bulk actions
43
+ ```
44
+
45
+ And it doesn't stop at scaffolds:
46
+
47
+ **Resource-oriented architecture** โ€” models, policies, definitions, and controllers that work together:
29
48
 
30
49
  ```ruby
31
50
  # Policy controls WHO can do WHAT
@@ -47,7 +66,7 @@ class PostDefinition < ResourceDefinition
47
66
  end
48
67
  ```
49
68
 
50
- **Packages for organization** - Split your app into feature packages and portals:
69
+ **Packages and portals** โ€” split your app into feature engines and themed web interfaces:
51
70
 
52
71
  ```bash
53
72
  rails g pu:pkg:package blogging # Business logic
@@ -65,7 +84,7 @@ rails g pu:rodauth:account user
65
84
  **Multi-tenancy** with entity scoping:
66
85
 
67
86
  ```ruby
68
- # In portal engine
87
+ # In a portal engine
69
88
  scope_to_entity Organization, strategy: :path
70
89
  # Routes become /organizations/:organization_id/posts
71
90
  ```
@@ -86,6 +105,13 @@ class PublishInteraction < ResourceInteraction
86
105
  end
87
106
  ```
88
107
 
108
+ ## Why Plutonium
109
+
110
+ - **Convention over configuration** โ€” extended to resources, policies, portals, and tenancy, not just routes and views.
111
+ - **It's just Rails** โ€” generated code lives in your repo. Edit it, override it, delete it. The "magic" is regular Ruby mixins you can read.
112
+ - **Multi-tenant ready** โ€” path or domain tenancy, scoped relations, invites and memberships out of the box.
113
+ - **AI-readable** โ€” predictable file layout and naming, plus built-in [Claude Code skills](.claude/skills) that teach AI assistants the patterns.
114
+
89
115
  ## Documentation
90
116
 
91
117
  Full documentation at **[radioactive-labs.github.io/plutonium-core](https://radioactive-labs.github.io/plutonium-core/)**
@@ -97,18 +123,14 @@ Full documentation at **[radioactive-labs.github.io/plutonium-core](https://radi
97
123
 
98
124
  ## Requirements
99
125
 
100
- - Ruby 3.2+
101
- - Rails 7.1+ (Rails 8 recommended)
126
+ - Ruby 3.2.2+
127
+ - Rails 7.2+ (Rails 8 recommended)
102
128
  - Node.js 18+
103
129
 
104
130
  ## Contributing
105
131
 
106
132
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
107
133
 
108
- ## Status
109
-
110
- Plutonium is used in production but still evolving. APIs may change between minor versions. Pin your version in Gemfile.
111
-
112
134
  ## License
113
135
 
114
- MIT License - see [LICENSE](LICENSE).
136
+ MIT License โ€” see [LICENSE.txt](LICENSE.txt).