plutonium 0.60.1 → 0.60.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 371d9fea402116abc683c5a86c2a23ff54d6e093a5d5d64860a905cd2fb64b72
4
- data.tar.gz: 6fc951e3e6ab822ee42c17ee843b0373b185e9e2d54b63c68906391bae04d07b
3
+ metadata.gz: f3d9d716f53c4604a7c9189a74cdfd84ee880e5305860331a8a840fa8302111c
4
+ data.tar.gz: cd72d23c2c9d63ddf51db97d29834d7d49ec8a42babd1a9f7508eaa3b5a99630
5
5
  SHA512:
6
- metadata.gz: e3b2bbce710220fe7cdaab63cb0b35c5b271cf4c07fe246fbc6a5a96cc178b1b223947150e547bc763c5adb59454d685e50984e6978a7427813d2f8182f72fdc
7
- data.tar.gz: c243e2aa38c0ca4942655791b6a9ed8efc1e037d90ec63c1f07e1051ef4fe7fc8abf1b12af5dcb916632c301eb3eb9bdb3c7d7f4818a347992fac3a5ecc142e2
6
+ metadata.gz: c93a955e88b5ab92c35f1ded962aa8a360eb3fae6a906ffef119493cc8d84327ab5e67fa4a00f225bb4c6560a191c1ebe61d0084b3b493377bb34b0781ce99bf
7
+ data.tar.gz: 6198cc3eb04146af99e9ac660463e7bb1a84885c09166fee4ef0508f3f552557459b81523649e9057f822ba922833ab2ac0d73a46a0101ac6bccde1ae8b89949
@@ -834,6 +834,30 @@ end
834
834
 
835
835
  `modal:` is the default for framework `:new` / `:edit` *and* every interactive action on this definition. Per-action `modal:` / `size:` overrides win.
836
836
 
837
+ ## Form Layout (`form_layout`)
838
+
839
+ Group form fields into sections **declaratively in the definition** — no `Form` subclass, no view code. Prefer this over hand-rolling a `section` helper in a custom `form_template`.
840
+
841
+ ```ruby
842
+ class PostDefinition < ResourceDefinition
843
+ form_layout do
844
+ section :identity, :name, :email, label: "Identity", description: "Who this is"
845
+ section :address, :street, :city,
846
+ collapsible: true, collapsed: -> { object.persisted? }, columns: 2,
847
+ condition: -> { object.requires_address? } # hide the whole section as a unit
848
+ ungrouped label: "Other" # bucket for unlisted fields; position = where it renders
849
+ end
850
+ end
851
+ ```
852
+
853
+ - **Layout references field KEYS only** — all per-field config (`as:`, `hint:`, blocks, per-field `condition:`) stays on `input`. Never duplicated here.
854
+ - **Options**: `label:`, `description:`, `collapsible:`, `collapsed:`, `columns:` (positive Integer, literal only), `condition:`. Every option except `columns:` may be a **proc** resolved at render in the form context (`object`, `current_user`, `params`, helpers).
855
+ - **Absent fields are skipped.** A key the section lists that isn't in the permitted set (policy, per-action, scoping, nesting, or a typo) is silently dropped — never an error. The same layout serves a richly-permitted `edit` and a minimal `new`.
856
+ - **🚨 Zero-field sections drop entirely** — no heading, no grid. So `+ New` (fewer permitted attributes) won't sprout empty headings. This checks *field presence only*; per-field `condition:` runs later, so to hide a whole section by state, gate it with the **section's own `condition:`**, not by hiding every field inside it.
857
+ - **Works on interactions too** (`Plutonium::Interaction::Base`) — groups `attribute` declarations. There `object` is the interaction instance; for record actions the record is `object.resource`.
858
+
859
+ Full DSL reference: [Resource › Definition › Form layout](/reference/resource/definition#form-layout).
860
+
837
861
  ## Metadata Panel (show page)
838
862
 
839
863
  Declares fields rendered in the show page's right-side aside as label/value rows.
@@ -182,7 +182,21 @@ end
182
182
 
183
183
  ## Custom layouts
184
184
 
185
- ### Sectioned
185
+ ### Sectioned — prefer the `form_layout` DSL
186
+
187
+ **For grouping fields into sections, don't hand-roll a `Form` subclass — declare `form_layout` in the definition.** It handles headings, descriptions, collapsible `<details>`, per-section `columns:`, `condition:`-based visibility, and **auto-drops sections that resolve to zero fields** (so `+ New` doesn't sprout empty headings). See [[plutonium-resource]] › Form Layout.
188
+
189
+ ```ruby
190
+ class PostDefinition < ResourceDefinition
191
+ form_layout do
192
+ section :basic, :title, :slug, label: "Basic"
193
+ section :publishing, :published_at, :category, label: "Publishing", columns: 2
194
+ ungrouped label: "Other"
195
+ end
196
+ end
197
+ ```
198
+
199
+ Only drop to a custom `Form#form_template` when you need layout the DSL can't express (arbitrary wrapper markup, interleaved non-field content). The escape hatch:
186
200
 
187
201
  ```ruby
188
202
  class Form < Form
@@ -211,6 +225,8 @@ class Form < Form
211
225
  end
212
226
  ```
213
227
 
228
+ A hand-rolled `section` like this renders its heading unconditionally — that's exactly the empty-heading problem `form_layout` avoids. If you must hand-roll, guard empty sections yourself.
229
+
214
230
  ### Two-column
215
231
 
216
232
  ```ruby
@@ -506,7 +522,20 @@ Plutonium.configure do |config|
506
522
  end
507
523
  ```
508
524
 
509
- `:plain` keeps the Topbar but drops the icon rail. Override per-controller (and so per-portal, since it's an inherited `class_attribute`) with the `rail` DSL `rail false` / `rail true`; `rail nil` (default) inherits the shell default, `rail?` reads the resolved value:
525
+ `:plain` keeps the Topbar but drops the icon rail. **Shell resolves global engine controller**, each overriding the one above (`nil` falls through); read it with `controller.shell`:
526
+
527
+ ```ruby
528
+ config.shell = :plain # 1. global default
529
+ # 2. per-engine — inside the engine's config.after_initialize (with scope_to_entity)
530
+ class CustomerPortal::Engine
531
+ config.after_initialize { shell :plain }
532
+ end
533
+ class DashboardController; shell :modern; end # 3. per-controller (overrides engine/global)
534
+ ```
535
+
536
+ `shell` takes a symbol so the class body works too, but the generated engine already has a `config.after_initialize` block (home of `scope_to_entity`) — keep it there for consistency.
537
+
538
+ Alongside `shell`, the controller-only `rail` DSL flips just the rail (inherited `class_attribute`, so a portal opts in/out once in its concern) — `rail false` / `rail true`; `rail nil` (default) inherits the resolved shell, `rail?` reads the resolved value:
510
539
 
511
540
  ```ruby
512
541
  module CustomerPortal::Concerns::Controller
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.60.3] - 2026-06-15
2
+
3
+ ### 🚀 Features
4
+
5
+ - *(layout)* Drop form_layout sections that resolve to zero fields
6
+ - *(layout)* Refine form_layout section header styling
7
+
8
+ ### 🚜 Refactor
9
+
10
+ - *(layout)* Move engine shell to Portal::Engine with live cascade
11
+ ## [0.60.2] - 2026-06-15
12
+
13
+ ### 🚀 Features
14
+
15
+ - *(layout)* Resolve shell across global, engine, and controller tiers
1
16
  ## [0.60.1] - 2026-06-15
2
17
 
3
18
  ### 🚀 Features