plutonium 0.54.0 → 0.55.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-behavior/SKILL.md +22 -0
  3. data/.claude/skills/plutonium-resource/SKILL.md +55 -0
  4. data/.claude/skills/plutonium-ui/SKILL.md +2 -1
  5. data/CHANGELOG.md +14 -0
  6. data/app/assets/plutonium.css +1 -1
  7. data/app/assets/plutonium.js +18 -0
  8. data/app/assets/plutonium.js.map +4 -4
  9. data/app/assets/plutonium.min.js +30 -30
  10. data/app/assets/plutonium.min.js.map +4 -4
  11. data/docs/public/images/reference/structured-inputs-removed.png +0 -0
  12. data/docs/public/images/reference/structured-inputs.png +0 -0
  13. data/docs/reference/resource/definition.md +110 -0
  14. data/docs/superpowers/plans/2026-06-02-structured-inputs.md +1061 -0
  15. data/docs/superpowers/plans/2026-06-02-structured-inputs.md.tasks.json +60 -0
  16. data/docs/superpowers/specs/2026-06-01-structured-inputs-design.md +191 -0
  17. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  18. data/lib/plutonium/definition/base.rb +1 -0
  19. data/lib/plutonium/definition/structured_inputs.rb +67 -0
  20. data/lib/plutonium/interaction/README.md +24 -78
  21. data/lib/plutonium/interaction/base.rb +10 -2
  22. data/lib/plutonium/resource/controller.rb +6 -1
  23. data/lib/plutonium/resource/controllers/interactive_actions.rb +10 -6
  24. data/lib/plutonium/structured_inputs/param_cleaner.rb +36 -0
  25. data/lib/plutonium/structured_inputs/params_concern.rb +36 -0
  26. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +3 -3
  27. data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +178 -0
  28. data/lib/plutonium/ui/form/concerns/repeater_field_styles.rb +24 -0
  29. data/lib/plutonium/ui/form/resource.rb +4 -1
  30. data/lib/plutonium/ui/modal/slideover.rb +9 -3
  31. data/lib/plutonium/version.rb +1 -1
  32. data/package.json +1 -1
  33. data/src/css/components.css +10 -5
  34. data/src/js/controllers/register_controllers.js +2 -0
  35. data/src/js/controllers/structured_input_row_controller.js +26 -0
  36. metadata +14 -5
  37. data/docs/superpowers/specs/2026-06-01-interaction-repeater-inputs-design.md +0 -178
  38. data/lib/plutonium/interaction/nested_attributes.rb +0 -93
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65714d92f3f0d6759f5b0fbc0818be12387892214b09959de80d589f859a6a56
4
- data.tar.gz: 2190fd85c3ac535df868581baace2be6565c709263c1059eeccc37bb9ba6c396
3
+ metadata.gz: ecf3e56a7d08f87ee2e77af368b4059655aeea666fcfefcf238256ae8564a9e8
4
+ data.tar.gz: d70895506dda46cafc6933b9a972261365cdbddaff728e6eb8ad84bb3857ce0a
5
5
  SHA512:
6
- metadata.gz: 5ec925506a147113242a8888e96797d56cfb76972e771792752a296b6036ca5adffc8698ad9b967056b043f3c9df86a422d014cea6ec699cac73c692408ee962
7
- data.tar.gz: e8c29ac3f6d9e83b00dba5817e209c1e7d276730b599660dbd4fe5fe4a14684695fb069b0ff5b226b9f0b7a3cc96a7d74bb02edb1cbcf0c23fe345c7d56ace13
6
+ metadata.gz: d8e6d17854893f67732bfb97ecc7cf744501d5469df99bfd0c3e5c9b8f39d16818c93c270e586f6cd486c4559465db33e4e3cdfb6fff0fa9dfb7d3cf027678ef
7
+ data.tar.gz: af4b9df9e92dd343c6db4d73d39d3cfcad6a08fc3a9ce74ad47afabfb25056c0bccc7e8a924ac3a6070e4767a7e05e69d012cff6073732c04bae425bc33dd894
@@ -656,6 +656,28 @@ attribute :date, :datetime
656
656
 
657
657
  The presence of `:resource` / `:resources` / neither determines the action type — see [[plutonium-resource]] › Action Types.
658
658
 
659
+ ### Structured / repeating input
660
+
661
+ To collect a structured object or a repeating list of field-groups, use
662
+ `structured_input` (it declares the backing attribute for you):
663
+
664
+ ```ruby
665
+ structured_input :address do |f| # single → execute sees { street:, city: }
666
+ f.input :street
667
+ f.input :city
668
+ end
669
+
670
+ structured_input :contacts, repeat: 3 do |f| # repeater → [ { label:, phone: }, ... ]
671
+ f.input :label
672
+ f.input :phone
673
+ end
674
+ ```
675
+
676
+ ⚠️ **`nested_input` and `accepts_nested_attributes_for` are NOT available on
677
+ interactions** (they were model-backed). Use `structured_input` instead — it's
678
+ classless and collects plain hashes/arrays. See [[plutonium-resource]] ›
679
+ Structured Inputs for options (`repeat:`, `using:`, `fields:`).
680
+
659
681
  ## Inputs
660
682
 
661
683
  Same DSL as definition `input` (load [[plutonium-resource]] for the full list of `as:` types, options, dynamic blocks, etc.):
@@ -684,6 +684,61 @@ end
684
684
  - For custom class names, use `class_name:` in the model and `using:` in the definition.
685
685
  - `update_only: true` hides the Add button.
686
686
 
687
+ ## Structured Inputs
688
+
689
+ `structured_input` collects a **classless** group of fields — a single hash, or
690
+ (with `repeat:`) an array of hashes. No association or model class is involved.
691
+ On resources the value is stored in a **JSON/jsonb column**; use it when you
692
+ want structured data in a JSON column rather than a real association (which is
693
+ `nested_input`'s job).
694
+
695
+ ```ruby
696
+ class Spec < ResourceRecord
697
+ # t.json :payload / t.json :rows (jsonb in production)
698
+ end
699
+
700
+ class SpecDefinition < ResourceDefinition
701
+ structured_input :payload do |f| # single → { title:, notes: }
702
+ f.input :title
703
+ f.input :notes
704
+ end
705
+
706
+ structured_input :rows, repeat: 5 do |f| # repeater → [ { key:, value: }, ... ] (max 5)
707
+ f.input :key
708
+ f.input :value
709
+ end
710
+ end
711
+
712
+ class SpecPolicy < ResourcePolicy
713
+ # NOTE: unlike nested_input, you DO permit the column name here.
714
+ # (update inherits permitted_attributes_for_create automatically.)
715
+ def permitted_attributes_for_create = [:payload, :rows]
716
+ end
717
+ ```
718
+
719
+ `execute`/the record sees `payload => { "title" => …, "notes" => … }` and
720
+ `rows => [ { "key" => …, "value" => … }, … ]` (string keys from the JSON column;
721
+ blank rows are dropped, `_destroy` stripped).
722
+
723
+ ### Options
724
+
725
+ | Option | Description |
726
+ |--------|-------------|
727
+ | `repeat` | Presence ⇒ array (repeater). `Integer` = max rows; `true` = default cap (10); absent = single hash |
728
+ | `using` | A fields definition class instead of a block |
729
+ | `fields` | Subset of fields from the referenced definition |
730
+
731
+ ### Gotchas
732
+
733
+ - The column must be `json`/`jsonb` (or otherwise hold a hash/array). No model macro is needed — the value assigns directly.
734
+ - **Unlike `nested_input`, you DO permit the column name** in `permitted_attributes_for_*` (it's a regular attribute on a JSON column).
735
+ - `repeat: 1` is "array, max one row" — **not** the single form. Presence of `repeat:` always means an array.
736
+ - Rows are positional plain hashes — **no ids, no per-row class, no type coercion**.
737
+ - **No automatic validation.** Classless ⇒ nothing to attach `validates` to. `required:` and a select's `choices:` are **client-side only**, not enforced on the server. To enforce, add a model `validate` (resource) or a `validate` on the interaction (ActiveModel, checked before `execute`).
738
+ - **`as: :select` drops unknown values.** If a stored value isn't in `choices:`, the `<select>` renders blank and **saving overwrites it with `nil`** (standard `<select>` behaviour). Keep `choices:` a stable superset or use free text when values can drift.
739
+ - Inside repeater rows, prefer **native** field types (string, number, text, native `select`, checkbox). JS-enhanced inputs (slim-select, flatpickr, easymde, uppy, intl-tel) transform the DOM and may not survive the repeater's clone-by-innerHTML — verify before relying on them.
740
+ - Same DSL works on **interactions** (see [[plutonium-behavior]] › Interactions) — there it backs an ActiveModel attribute reaching `execute`.
741
+
687
742
  ## File Uploads
688
743
 
689
744
  ```ruby
@@ -302,7 +302,8 @@ end
302
302
  These all live in the definition layer:
303
303
 
304
304
  - **Pre-submit / dynamic forms** — see [[plutonium-resource]] › Dynamic Forms.
305
- - **Nested inputs** (`nested_input :variants`) — see [[plutonium-resource]] › Nested Inputs.
305
+ - **Nested inputs** (`nested_input :variants`) — association-backed inline forms; see [[plutonium-resource]] › Nested Inputs.
306
+ - **Structured inputs** (`structured_input :payload`, `structured_input :rows, repeat: 5`) — classless hash / array-of-hashes into a JSON column (resources) or an attribute (interactions); reuses the repeater chrome. See [[plutonium-resource]] › Structured Inputs.
306
307
  - **Interaction forms** — interactions define their own `attribute` / `input` and inherit `Plutonium::UI::Form::Interaction`; see [[plutonium-behavior]] › Interactions.
307
308
 
308
309
  ---
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.55.0] - 2026-06-03
2
+
3
+ ### 🚀 Features
4
+
5
+ - Structured_input — classless structured & repeater inputs (resources + interactions) (#60)
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ - *(ui)* Keep modal backdrop static to smooth dialog dismiss
10
+
11
+ ### 🧪 Testing
12
+
13
+ - *(dummy)* Land authenticated users on the entity-scoped org portal
14
+ - *(dummy)* Serve the Organization resource in the org portal
1
15
  ## [0.54.0] - 2026-06-01
2
16
 
3
17
  ### 🚀 Features