plutonium 0.60.0 → 0.60.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.
@@ -35,7 +35,7 @@ Loads the baseline defaults for a given framework version. Call this first; late
35
35
  | `development` | `ENV["PLUTONIUM_DEV"]` | Development mode for the framework itself (local assets, hot reload, verbose errors). Query with `config.development?`. You rarely set this in an app — see [Development mode](#development-mode). |
36
36
  | `cache_discovery` | `true` outside `development` env | Cache resource/route discovery. Disable to pick up new resources without a reboot. |
37
37
  | `enable_hotreload` | `true` in `development` env | Hot-reload Plutonium components on change. |
38
- | `shell` | `:modern` | Chrome style: `:modern` (topbar + icon rail) or `:classic` (legacy header + sidebar, only for upgrades). See [Layouts](./ui/layouts). |
38
+ | `shell` | `:modern` | Chrome style: `:modern` (topbar + icon rail), `:plain` (topbar, no icon rail), or `:classic` (legacy header + sidebar, only for upgrades). See [Layouts](./ui/layouts). |
39
39
  | `navii_host_url` | `"https://api.navii.dev"` | Host of the [Navii](https://navii.dev) avatar service used by [`Avatar`](./ui/components#avatar). The component appends `/avatar/:seed`. Repoint to self-host or proxy. |
40
40
  | `assets.logo` | `"plutonium.png"` | Brand logo asset. See [Assets](./ui/assets). |
41
41
  | `assets.favicon` | `"plutonium.ico"` | Favicon asset. |
@@ -562,9 +562,9 @@ class ArticleDefinition < ResourceDefinition
562
562
  end
563
563
  ```
564
564
 
565
- ### Referencing an unknown field raises
565
+ ### Fields not in the permitted set are skipped
566
566
 
567
- A `section` that lists a field key that is not a known attribute of the model raises `ArgumentError` at render time this catches typos early.
567
+ A `section` only renders the fields that are actually in the form's permitted set for the current request. A key it lists that isn't there — a typo, or a field excluded by policy, per-action `permitted_attributes`, entity scoping, or nesting — is **silently dropped**, never an error. This lets a single `form_layout` reference conditionally-permitted fields without crashing the form in the contexts where they're filtered out.
568
568
 
569
569
  ### On interactions
570
570
 
@@ -1,12 +1,13 @@
1
1
  # Layouts
2
2
 
3
- The overall page chrome — topbar, sidebar, footer, body wrapping. Plutonium ships two shells; you can eject the templates or write a custom `ResourceLayout` for total control.
3
+ The overall page chrome — topbar, sidebar, footer, body wrapping. Plutonium ships three shells; you can eject the templates or write a custom `ResourceLayout` for total control.
4
4
 
5
5
  ## Shell
6
6
 
7
7
  ```ruby
8
8
  Plutonium.configure do |config|
9
9
  config.shell = :modern # default — topbar + icon rail
10
+ # config.shell = :plain # topbar, no icon rail (rail-less app)
10
11
  # config.shell = :classic # legacy header + sidebar (only when upgrading)
11
12
  end
12
13
  ```
@@ -15,6 +16,41 @@ end
15
16
  If you're starting fresh, use `:modern`. `:classic` exists so apps upgrading from pre-`:modern` versions can preserve their chrome while migrating.
16
17
  :::
17
18
 
19
+ ## Shell variants & the icon rail
20
+
21
+ `config.shell` selects the chrome for the whole app:
22
+
23
+ - `:modern` (default) — Topbar plus the desktop icon rail.
24
+ - `:plain` — Topbar but **no** icon rail. The Topbar is kept; only the rail is removed, so the whole app is rail-less.
25
+ - `:classic` — legacy Header/Sidebar (upgrade paths only).
26
+
27
+ ### Per-controller / per-portal override
28
+
29
+ Any Plutonium resource controller exposes a class-level `rail` DSL that overrides the shell default. It's a `class_attribute`, so it's inherited: a portal opts its entire surface in or out by calling `rail false` (or `rail true`) once in its controller concern.
30
+
31
+ ```ruby
32
+ module CustomerPortal
33
+ module Concerns
34
+ module Controller
35
+ extend ActiveSupport::Concern
36
+ included { rail false } # entire portal rail-less
37
+ end
38
+ end
39
+ end
40
+ ```
41
+
42
+ `rail nil` (the default) inherits the shell default — the rail shows when `config.shell == :modern`. Read the resolved value with the `rail?` predicate.
43
+
44
+ ### Stable CSS hooks
45
+
46
+ Rail-less rendering exposes a few stable hooks for custom overrides:
47
+
48
+ - `pu-topbar` — class on the Topbar nav.
49
+ - `pu-sticky-footer` — class on the form sticky-footer div.
50
+ - `html.pu-no-rail` — root class present whenever the current page is rail-less.
51
+
52
+ A built-in rule cancels the desktop rail inset on `.pu-topbar` and `.pu-sticky-footer` under `html.pu-no-rail`; target these hooks to layer your own CSS.
53
+
18
54
  ## Eject the chrome for per-portal customization
19
55
 
20
56
  ```bash
@@ -290,7 +290,7 @@ git commit -m "feat(forms): add form_layout/section/ungrouped DSL registry"
290
290
 
291
291
  ### Task 2: Resolve a field list into ordered sections
292
292
 
293
- **Goal:** Add `resolve_form_sections(resource_fields)` to `FormLayout` — assign permitted fields to sections (in declared order), collect leftovers into `ungrouped` (default **last**, else at its declared position), raise on unknown field keys, and keep empty sections (no hiding). _(Amended: default was "first" — see Amendments at the end.)_
293
+ **Goal:** Add `resolve_form_sections(resource_fields)` to `FormLayout` — assign permitted fields to sections (in declared order), collect leftovers into `ungrouped` (default **last**, else at its declared position), skip section keys not in the permitted set, and keep empty sections (no hiding). _(Amended: default was "first", and unknown keys originally raised — see Amendments at the end.)_
294
294
 
295
295
  **Files:**
296
296
  - Modify: `lib/plutonium/definition/form_layout.rb` (add instance method `resolve_form_sections`)
@@ -302,7 +302,7 @@ git commit -m "feat(forms): add form_layout/section/ungrouped DSL registry"
302
302
  - [ ] `ungrouped` collects all unclaimed permitted fields, in `resource_fields` order.
303
303
  - [ ] Without an `ungrouped` macro, leftovers render in a heading-less section placed **last** (appended after all declared sections). _(Amended — was "first".)_
304
304
  - [ ] With an `ungrouped` macro, leftovers render at the macro's declared position with its options.
305
- - [ ] A section referencing a field absent from `resource_fields` *and* not a known attribute raises `ArgumentError`; a field merely filtered by policy is silently dropped (it's still "known"see note).
305
+ - [ ] A section referencing a field not in `resource_fields` (a typo, or a field filtered by policy/scoping/per-action) is **silently skipped**, not raised. _(Amendedoriginally raised `ArgumentError`.)_
306
306
  - [ ] Empty sections are returned (not hidden).
307
307
 
308
308
  > **Known-vs-permitted note:** `resolve_form_sections` only sees the policy-filtered `resource_fields`. To distinguish "typo" from "filtered out", it raises only when the field is not present in `resource_fields` AND the caller passes it as not-an-attribute. For unit purposes we treat any field not in `resource_fields` as unknown → raises. The form passes the *full* attribute set check at render via existing machinery; here we validate against `resource_fields`. Keep it strict: unknown key → raise.
@@ -898,6 +898,15 @@ steps/snippets are left as-built; these supersede them where they conflict.
898
898
  render — visibility + option evaluation in one pass — and `render_form_section`
899
899
  is pure presentation. Example: `collapsed: -> { object.persisted? }`.
900
900
 
901
+ - **Unknown / filtered section keys skipped, not raised** (`form_layout.rb`,
902
+ `resolve_form_sections`). A key not in the form's permitted set
903
+ (`submittable_attributes_for(action)`) is dropped silently. The original raise
904
+ crashed forms whenever a layout referenced a field excluded by per-action
905
+ `permitted_attributes`, entity scoping, nesting, or per-user policy — the
906
+ resolver only ever saw the filtered list, so it couldn't tell a typo from a
907
+ legitimately-filtered field. Test renamed
908
+ `test_unknown_field_raises` → `test_field_not_in_permitted_set_is_skipped_not_raised`.
909
+
901
910
  - **Interactions exercised** — `ReconfigureKitchenSink` (record action on
902
911
  `KitchenSink`) + `test/integration/admin_portal/form_layout_interaction_test.rb`
903
912
  cover form_layout (incl. a dynamic `collapsed:`) in an interaction form, where