plutonium 0.66.0 → 0.67.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-resource/SKILL.md +14 -8
  3. data/.claude/skills/plutonium-ui/SKILL.md +11 -3
  4. data/CHANGELOG.md +22 -0
  5. data/SECURITY.md +1 -1
  6. data/app/assets/plutonium.css +1 -1
  7. data/app/assets/plutonium.js +56 -3
  8. data/app/assets/plutonium.js.map +4 -4
  9. data/app/assets/plutonium.min.js +32 -32
  10. data/app/assets/plutonium.min.js.map +4 -4
  11. data/config/brakeman.ignore +1 -1
  12. data/config/locales/en/js.yml +4 -0
  13. data/config/locales/en/ui.yml +16 -0
  14. data/docs/reference/resource/definition.md +12 -7
  15. data/docs/reference/resource/query.md +11 -1
  16. data/docs/reference/ui/displays.md +19 -2
  17. data/docs/reference/ui/forms.md +54 -4
  18. data/gemfiles/rails_7.gemfile +1 -0
  19. data/gemfiles/rails_7.gemfile.lock +13 -3
  20. data/gemfiles/rails_8.0.gemfile +1 -0
  21. data/gemfiles/rails_8.0.gemfile.lock +10 -1
  22. data/gemfiles/rails_8.1.gemfile +1 -0
  23. data/gemfiles/rails_8.1.gemfile.lock +10 -1
  24. data/lib/plutonium/auth/rodauth.rb +7 -0
  25. data/lib/plutonium/core/controller.rb +0 -2
  26. data/lib/plutonium/helpers/content_helper.rb +0 -3
  27. data/lib/plutonium/query/filter.rb +2 -1
  28. data/lib/plutonium/query/filters/tags.rb +36 -0
  29. data/lib/plutonium/ui/component/resolves_tags.rb +13 -0
  30. data/lib/plutonium/ui/display/base.rb +50 -2
  31. data/lib/plutonium/ui/display/components/badge.rb +1 -1
  32. data/lib/plutonium/ui/display/components/binary.rb +20 -0
  33. data/lib/plutonium/ui/display/components/boolean.rb +1 -1
  34. data/lib/plutonium/ui/display/components/code.rb +23 -0
  35. data/lib/plutonium/ui/display/components/color.rb +14 -6
  36. data/lib/plutonium/ui/display/components/duration.rb +31 -0
  37. data/lib/plutonium/ui/display/components/file_size.rb +22 -0
  38. data/lib/plutonium/ui/display/components/key_value.rb +37 -0
  39. data/lib/plutonium/ui/display/components/list.rb +33 -0
  40. data/lib/plutonium/ui/display/components/number.rb +32 -0
  41. data/lib/plutonium/ui/display/components/progress.rb +36 -0
  42. data/lib/plutonium/ui/display/components/rating.rb +36 -0
  43. data/lib/plutonium/ui/display/components/relative_time.rb +25 -0
  44. data/lib/plutonium/ui/display/components/rich_text.rb +23 -0
  45. data/lib/plutonium/ui/display/components/tags.rb +18 -0
  46. data/lib/plutonium/ui/display/options/inferred_types.rb +13 -0
  47. data/lib/plutonium/ui/display/theme.rb +24 -2
  48. data/lib/plutonium/ui/form/base.rb +30 -1
  49. data/lib/plutonium/ui/form/components/binary.rb +40 -0
  50. data/lib/plutonium/ui/form/components/key_value_store.rb +22 -71
  51. data/lib/plutonium/ui/form/components/list.rb +46 -0
  52. data/lib/plutonium/ui/form/components/rating.rb +51 -0
  53. data/lib/plutonium/ui/form/components/rich_text.rb +20 -0
  54. data/lib/plutonium/ui/form/components/tags.rb +45 -0
  55. data/lib/plutonium/ui/form/components/tags_filter.rb +40 -0
  56. data/lib/plutonium/ui/form/components/uppy.rb +1 -1
  57. data/lib/plutonium/ui/form/options/inferred_types.rb +16 -15
  58. data/lib/plutonium/ui/form/theme.rb +20 -4
  59. data/lib/plutonium/ui/grid/card.rb +13 -1
  60. data/lib/plutonium/ui/layout/base.rb +10 -1
  61. data/lib/plutonium/ui/options/rich_text_field.rb +18 -0
  62. data/lib/plutonium/ui/options/secret_field.rb +32 -0
  63. data/lib/plutonium/ui/options/tags_field.rb +23 -0
  64. data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
  65. data/lib/plutonium/ui/table/components/view_switcher.rb +4 -2
  66. data/lib/plutonium/ui/table/display_theme.rb +30 -2
  67. data/lib/plutonium/version.rb +1 -1
  68. data/package.json +1 -1
  69. data/src/css/components.css +43 -0
  70. data/src/css/flatpickr.css +91 -0
  71. data/src/js/controllers/easymde_controller.js +1 -0
  72. data/src/js/controllers/register_controllers.js +2 -0
  73. data/src/js/controllers/slim_select_controller.js +32 -3
  74. data/src/js/controllers/timeago_controller.js +40 -0
  75. metadata +25 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b131f7d0e5b02982f4696e7d1db781f45b17f8b28f17da5fe5cc3b0a4480c7a0
4
- data.tar.gz: f700d3d65a3fc368e73315c436a7f8d2afb5244cd04b4b0be5618efbc738d26f
3
+ metadata.gz: 7285167839fdb5e60e89da076028dc08b719ec60125cb61faaeb26cd151b9140
4
+ data.tar.gz: 87fe956c9ab3cdcc92e7083a3c5c965c144661f530e4ce0cb609d3b0a8adfb06
5
5
  SHA512:
6
- metadata.gz: 7dc926b3cf2fb69f5dfa4644d10c47e88a39e74f3359be2572a4ea6ea46ad216a5c0447959b0e1596f0ee6ae67190140f46acc53afa332a508e63f275440bdad
7
- data.tar.gz: 4d9f3fba2b94d24ef0db3d5b24b35b30d6c058acc46ac20107d37c1a507f804dac7d47b3a38556f02b16ac229ce9b2a4140e73216152c9e56ae7189a5c71bc00
6
+ metadata.gz: 3cdc13417311bd847b8b721ae46da0b59b12ecb1fafe95be087e556d71ac4d053d236169f2d48fa08edf831f9d49cdfdce27c3e28229b1bf5155dafdfb30cad5
7
+ data.tar.gz: 539932b5d8700edf4878fde1eb3c33ed0cd518d6ebafbbcf83df92de5ff098061dabcc1c1ca1a859cdfcf1e68a3f3e6ceaa55c44499b4132f132192db5cfcd18
@@ -543,19 +543,20 @@ The field-level help keys (`:label`, `:description`, `:hint`, `:placeholder`) ar
543
543
 
544
544
  | Category | Types |
545
545
  |----------|-------|
546
- | Text | `:string`, `:text`, `:email`, `:url`, `:tel`, `:password` |
547
- | Rich Text | `:markdown` (EasyMDE) |
548
- | Numeric | `:number`, `:integer`, `:decimal`, `:range` |
546
+ | Text | `:string`, `:text`, `:email`, `:url`, `:phone`, `:password` (alias `:secret`) |
547
+ | Rich Text | `:rich_text` (Action Text editor: Trix, or Lexxy once installed; **default** for `has_rich_text`), `:markdown` (EasyMDE) |
548
+ | Numeric | `:number`, `:integer`, `:decimal`, `:range`, `:rating` (star picker, `max:`) |
549
549
  | Boolean | `:toggle` / `:switch` (switch, **default** for boolean columns), `:boolean` (plain checkbox) |
550
550
  | Date/Time | `:date`, `:time`, `:datetime` |
551
- | Selection | `:select`, `:slim_select`, `:radio_buttons`, `:check_boxes` |
552
- | Files | `:file`, `:uppy`, `:attachment` |
551
+ | Selection | `:select`, `:slim_select`, `:collection_radio_buttons`, `:collection_checkboxes` |
552
+ | Lists | `:list` (chip input for array values; `suggestions:`, `addable:`, `limit:`), `:tags` (no_fly_list `has_tags` contexts, **auto-detected**) |
553
+ | Files | `:file`, `:uppy`, `:attachment` (Active Storage), `:binary` (bytes in a `binary` column) |
553
554
  | Associations | `:association`, `:secure_association`, `:belongs_to`, `:has_many`, `:has_one` |
554
- | Special | `:hidden`, `:color`, `:phone` |
555
+ | Special | `:hidden`, `:color`, `:json`, `:key_value` |
555
556
 
556
557
  ### Display Types (show / index)
557
558
 
558
- `:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:badge`, `:currency`, `:color`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`
559
+ `:string`, `:text`, `:email`, `:url`, `:phone`, `:markdown`, `:number`, `:integer`, `:decimal`, `:boolean`, `:badge`, `:currency`, `:color`, `:date`, `:time`, `:datetime`, `:association`, `:attachment`, `:key_value`, `:binary`, `:list`, `:tags`, `:rich_text`, `:relative_time`, `:code`, `:duration`, `:file_size`, `:progress`, `:rating`
559
560
 
560
561
  #### Auto-inferred display formatting
561
562
 
@@ -565,7 +566,10 @@ These render automatically: declare an `as:` only to override or pass options:
565
566
  |--------|-----------|-------|
566
567
  | `boolean` | Yes/No pill (`:boolean`) | green "Yes" / neutral "No". Override labels: `true_label:`, `false_label:` |
567
568
  | `enum` | colored status badge (`:badge`) | known statuses (active, pending, failed…) auto-colored; unknown values get a stable decorative color |
568
- | `has_cents` decimal | currency (`:currency`) | delimited, 2 decimals, **no symbol** unless you add `unit:` |
569
+ | `binary` | size (`:binary`) | `Binary data (12 KB)`, never the bytes |
570
+ | `has_rich_text` | HTML (`:rich_text`) | rendered and sanitized by Action Text |
571
+ | no_fly_list `has_tags` | chips (`:tags`) | declare by context name (`:labels`), not `labels_list` |
572
+ | `has_cents` decimal | currency (`:currency`) | delimited, 2 decimals; symbol from `unit:` (display or `has_cents`), else `config.default_currency_unit` / i18n. `unit: false` drops it |
569
573
 
570
574
  ```ruby
571
575
  display :status, as: :badge, colors: {archived: :neutral, vip: :accent} # override per-value color
@@ -1277,6 +1281,7 @@ end
1277
1281
  | Date Range | `:date_range` | `from`, `to` | `from_label:`, `to_label:` |
1278
1282
  | Select | `:select` | `value` | `choices:`, `multiple:` |
1279
1283
  | Association | `:association` | `value` | `class_name:`, `multiple:` |
1284
+ | Tags | `:tags` | `value` | `match:` (`:any` default, `:all`); no_fly_list contexts, options limited to the policy scope |
1280
1285
 
1281
1286
  **Text predicates:** `:eq`, `:not_eq`, `:contains`, `:not_contains`, `:starts_with`, `:ends_with`, `:matches`, `:not_matches`
1282
1287
  **Date predicates:** `:eq`, `:not_eq`, `:lt`, `:lteq`, `:gt`, `:gteq`
@@ -1291,6 +1296,7 @@ filter :category, with: :select, choices: -> { Category.pluck(:name) }
1291
1296
  filter :tags, with: :select, choices: %w[ruby rails js], multiple: true
1292
1297
  filter :category, with: :association
1293
1298
  filter :author, with: :association, class_name: User
1299
+ filter :labels, with: :tags # no_fly_list context
1294
1300
  ```
1295
1301
 
1296
1302
  **Custom filter class:**
@@ -309,7 +309,7 @@ render field(:title).wrapped(class: "col-span-full") { |f| f.input_tag }
309
309
  | Tag | Input |
310
310
  |---|---|
311
311
  | `input_tag` | text (auto-detected type) |
312
- | `string_tag`, `text_tag`, `number_tag`, `email_tag`, `password_tag`, `url_tag`, `tel_tag`, `hidden_tag` | standard HTML inputs |
312
+ | `string_tag`, `text_tag`, `number_tag`, `email_tag`, `password_tag` / `secret_tag`, `url_tag`, `hidden_tag` | standard HTML inputs |
313
313
  | `checkbox_tag`, `select_tag`, `radio_button_tag` | standard |
314
314
  | `toggle_tag` / `switch_tag` | switch-styled boolean (`as: :toggle` / `:switch`), default for boolean columns; `as: :boolean` for a plain checkbox |
315
315
 
@@ -325,6 +325,13 @@ render field(:title).wrapped(class: "col-span-full") { |f| f.input_tag }
325
325
  | `secure_association_tag` | Association with policy-checked options |
326
326
  | `belongs_to_tag` / `has_many_tag` / `has_one_tag` | Association selects |
327
327
  | `key_value_store_tag` | Key/value pairs editor |
328
+ | `currency_tag` | Money input (number field + unit prefix) |
329
+ | `rich_text_tag` | Action Text editor (Trix, or Lexxy once the app installs it); default for `has_rich_text` |
330
+ | `list_tag` | Chip input for array values (`suggestions:`, `limit:`, `addable: false`) |
331
+ | `tags_tag` | Chip input for a no_fly_list `has_tags` context; default for those contexts |
332
+ | `rating_tag` | Star picker (`max:`, default 5); a Clear option unless required |
333
+ | `binary_tag` | Plain file input whose bytes go into a `binary` column |
334
+ | `json_tag` | JSON editor; default for `json`/`jsonb` columns |
328
335
 
329
336
  ```ruby
330
337
  render field(:published_at).wrapped { |f| f.flatpickr_tag(min_date: Date.today, enable_time: true) }
@@ -335,11 +342,12 @@ render field(:avatar).wrapped { |f| f.uppy_tag(allowed_file_types: %w[.jpg
335
342
 
336
343
  `password_tag` masks the stored value and **never emits the secret into the DOM**. A stored secret renders a sentinel; an untouched submit keeps it, an edit-to-new-value then failed re-render comes back blank + `required` (re-type; secrets are never echoed back), a *cleared* field comes back blank but **not** `required` (the clear may be intentional), a deliberately emptied field clears it (clear-by-blank), a typed value sets it. The sentinel is guarded by the `password-sentinel` Stimulus controller: the first edit (incl. **backspace**) wipes the whole field so a partial edit can't corrupt it.
337
344
 
338
- Auto-detected by name: `password`/`token`/`salt`, `encrypted_*`, `*_password`/`*_digest`/`*_hash`/`*_token`/`*_key`/`*_salt`, or any name containing `secret`. A convenience, **not** a guarantee: odd-named secrets (`recovery_phrase`, `pin`) still leak unless masked explicitly.
345
+ Auto-detected by name: `password`/`token`/`salt`, `encrypted_*`, `*_password`/`*_digest`/`*_hash`/`*_token`/`*_key`/`*_salt`, or any name containing `secret`. The same names render `••••••••` on the show page, table, wizard summary and grid/kanban cards; `field`/`display` `as:` overrides it there too. A convenience, **not** a guarantee: odd-named secrets (`recovery_phrase`, `pin`) still leak unless masked explicitly.
339
346
 
340
347
  ```ruby
341
348
  field :api_token, as: :string # opt OUT: show a readable value (token to copy, checksum)
342
349
  field :recovery_phrase, as: :password # opt IN: mask a secret the heuristic misses
350
+ field :pin, as: :secret # :secret is an alias of :password
343
351
  ```
344
352
 
345
353
  ## Submit buttons
@@ -1114,7 +1122,7 @@ end
1114
1122
 
1115
1123
  ### Display theme keys
1116
1124
 
1117
- `fields_wrapper`, `fields_inner`, `sections_wrapper`, `section_grid`, `label`, `description`, `string`, `text`, `link`, `email`, `phone`, `markdown`, `json`, `boolean`, `badge`, `currency`, `color`.
1125
+ `fields_wrapper`, `fields_inner`, `sections_wrapper`, `section_grid`, `label`, `description`, `string`, `text`, `link`, `email`, `phone`, `markdown`, `rich_text`, `json`, `key_value`, `list` / `list_item`, `boolean`, `badge`, `currency`, `color`, `binary`, `relative_time`, `code`, `duration`, `file_size`, `progress`, `rating`. Table cells use `Plutonium::UI::Table::DisplayTheme`, which has its own (more compact) entry for each.
1118
1126
 
1119
1127
  ⚠️ **`fields_wrapper` is the CARD, `fields_inner` is the grid.** `fields_wrapper` is merged into a `Plutonium::UI::Block` (which supplies `pu-card` itself), so putting grid classes there styles the card, not the fields. Override `fields_inner` to change the unsectioned grid, and `section_grid` to change the grid inside a `display_layout` section.
1120
1128
 
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.67.0] - 2026-10-09
6
+
7
+ ### Bug Fixes
8
+
9
+ - Keep view switcher from clipping on narrow screens
10
+ - Restore CSRF verification for cookie sessions ([#175](https://github.com/radioactive-labs/plutonium-core/issues/175))
11
+ - Let slim-select dropdown overflow modal edge instead of clipping
12
+ - Load Font Awesome 4.7 from jsdelivr for EasyMDE toolbar icons
13
+ - Mask secret fields on every display surface
14
+ - Fixes from the component audit
15
+
16
+ ### Features
17
+
18
+ - Binary column input and display
19
+ - Key/value, list, rating and value display components
20
+ - No_fly_list tags support
21
+ - [**breaking**] Render has_rich_text through Action Text's editor
22
+
23
+ ### Testing
24
+
25
+ - Search slim-select typeaheads by email in system tests ([#177](https://github.com/radioactive-labs/plutonium-core/issues/177))
26
+
5
27
  ## [0.66.0] - 2026-10-08
6
28
 
7
29
  ### Bug Fixes
data/SECURITY.md CHANGED
@@ -12,7 +12,7 @@ please upgrade before reporting an issue to confirm it still reproduces.
12
12
 
13
13
  | Version | Supported |
14
14
  | ------- | ------------------ |
15
- | Latest release (`0.66.x`) | :white_check_mark: |
15
+ | Latest release (`0.67.x`) | :white_check_mark: |
16
16
  | Older releases | :x: |
17
17
 
18
18
  ## Reporting a Vulnerability