plutonium 0.67.0 → 0.68.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-dashboard/SKILL.md +17 -2
  3. data/.claude/skills/plutonium-resource/SKILL.md +3 -2
  4. data/.claude/skills/plutonium-ui/SKILL.md +2 -0
  5. data/CHANGELOG.md +13 -0
  6. data/SECURITY.md +1 -1
  7. data/config/locales/en/dashboard.yml +1 -0
  8. data/config/locales/en/ui.yml +3 -0
  9. data/docs/reference/dashboard/dsl.md +54 -3
  10. data/docs/reference/resource/definition.md +2 -0
  11. data/docs/reference/ui/forms.md +23 -0
  12. data/lib/plutonium/dashboard/base.rb +80 -1
  13. data/lib/plutonium/dashboard/card.rb +12 -13
  14. data/lib/plutonium/dashboard/controller.rb +28 -10
  15. data/lib/plutonium/dashboard/dsl.rb +59 -2
  16. data/lib/plutonium/dashboard/evaluation.rb +30 -0
  17. data/lib/plutonium/dashboard/filter.rb +114 -0
  18. data/lib/plutonium/dashboard/link.rb +35 -0
  19. data/lib/plutonium/dashboard/route_resolution.rb +6 -2
  20. data/lib/plutonium/resource/query_object.rb +6 -0
  21. data/lib/plutonium/routing/dashboard_registration.rb +5 -0
  22. data/lib/plutonium/ui/avatar.rb +2 -2
  23. data/lib/plutonium/ui/dashboard/board.rb +4 -1
  24. data/lib/plutonium/ui/dashboard/frame.rb +1 -1
  25. data/lib/plutonium/ui/dashboard/metric.rb +18 -5
  26. data/lib/plutonium/ui/dashboard/toolbar.rb +76 -0
  27. data/lib/plutonium/ui/form/base.rb +20 -0
  28. data/lib/plutonium/ui/form/components/secure_association.rb +31 -8
  29. data/lib/plutonium/ui/form/components/secure_association_checkboxes.rb +30 -0
  30. data/lib/plutonium/ui/form/components/secure_association_choices.rb +123 -0
  31. data/lib/plutonium/ui/form/components/secure_association_radio_buttons.rb +45 -0
  32. data/lib/plutonium/ui/form/components/secure_association_toggles.rb +18 -0
  33. data/lib/plutonium/ui/form/theme.rb +15 -1
  34. data/lib/plutonium/ui/grid/card.rb +12 -4
  35. data/lib/plutonium/ui/page/dashboard.rb +1 -0
  36. data/lib/plutonium/ui/table/components/scopes_bar.rb +1 -1
  37. data/lib/plutonium/ui/table/components/scopes_pills.rb +1 -1
  38. data/lib/plutonium/version.rb +1 -1
  39. data/package.json +1 -1
  40. data/plutonium.gemspec +1 -1
  41. metadata +12 -4
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Form
6
+ module Components
7
+ # A belongs_to / has_one association rendered as one radio button per
8
+ # authorized record (`input :category, as: :association_radio_buttons`).
9
+ #
10
+ # An optional association also gets a blank "None" choice, since a
11
+ # radio group can't otherwise be cleared once something is picked.
12
+ # `include_blank: false` drops it. A required association never gets
13
+ # one: a checked "None" would satisfy the group's `required`.
14
+ class SecureAssociationRadioButtons < SecureAssociationChoices
15
+ protected
16
+
17
+ def control_type = :radio
18
+
19
+ def control_theme = :association_radio_button
20
+
21
+ def supported_macros = %i[belongs_to has_one]
22
+
23
+ def group_role = "radiogroup"
24
+
25
+ def include_blank?
26
+ return false if attributes[:required]
27
+
28
+ @include_blank != false
29
+ end
30
+
31
+ # `required` on any radio in a group makes picking one of them required.
32
+ def control_required? = attributes[:required]
33
+
34
+ def render_blank_choice
35
+ render_choice(value: "", text: blank_choice_text, id: "#{attributes[:id]}_blank", checked: singular_field_value.nil?)
36
+ end
37
+
38
+ def blank_choice_text
39
+ field.placeholder.presence || Plutonium::Translation.t("plutonium.ui.form.association_choices.none")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Form
6
+ module Components
7
+ # Switch-styled {SecureAssociationCheckboxes}
8
+ # (`input :tags, as: :association_toggles`). Same behavior; only the
9
+ # `.pu-toggle` styling differs.
10
+ class SecureAssociationToggles < SecureAssociationCheckboxes
11
+ protected
12
+
13
+ def control_theme = :association_toggle
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -149,7 +149,21 @@ module Plutonium
149
149
  polymorphic_association: :association,
150
150
  valid_polymorphic_association: :valid_association,
151
151
  invalid_polymorphic_association: :invalid_association,
152
- neutral_polymorphic_association: :neutral_association
152
+ neutral_polymorphic_association: :neutral_association,
153
+
154
+ # List-style association variants (`as: :association_checkboxes`,
155
+ # `:association_toggles`, `:association_radio_buttons`). The bare key
156
+ # styles the list; `association_<control>` styles each control.
157
+ association_checkboxes: "flex flex-col gap-2",
158
+ association_toggles: "flex flex-col gap-2",
159
+ association_radio_buttons: "flex flex-col gap-2",
160
+ association_choice_label: "inline-flex items-center gap-2 text-sm text-[var(--pu-text)] cursor-pointer",
161
+ association_checkbox: :checkbox,
162
+ invalid_association_checkbox: :invalid_boolean,
163
+ association_toggle: :toggle,
164
+ invalid_association_toggle: :invalid_toggle,
165
+ association_radio_button: :radio_button,
166
+ invalid_association_radio_button: "pu-radio pu-input-invalid"
153
167
  })
154
168
  end
155
169
  end
@@ -105,13 +105,21 @@ module Plutonium
105
105
  value = field_value(slots[:image])
106
106
 
107
107
  if size == :cover
108
- # Cover is a full-width banner, not an avatar: only render when an
109
- # actual image resolves (no deterministic fallback).
108
+ # Cover is a full-width banner, not an avatar, so a record with no
109
+ # image gets no generated stand-in. It still gets the banner's
110
+ # space, as an empty slot elsewhere gets its em-dash: without it the
111
+ # card is a banner shorter than its neighbours, and the row stretches
112
+ # it into a block of blank card under its text.
110
113
  src = Plutonium::UI::Avatar.resolve_image_src(value, helpers)
111
- return unless src
112
114
 
113
115
  div(class: "w-full aspect-video bg-[var(--pu-surface-alt)] overflow-hidden") do
114
- img(src: src, alt: header_text.to_s, class: "w-full h-full object-cover")
116
+ if src
117
+ img(src: src, alt: header_text.to_s, class: "w-full h-full object-cover")
118
+ else
119
+ div(class: "w-full h-full flex items-center justify-center text-[var(--pu-text-subtle)]", aria_hidden: "true") do
120
+ render Phlex::TablerIcons::Photo.new(class: "w-8 h-8")
121
+ end
122
+ end
115
123
  end
116
124
  else
117
125
  # Small avatar slot: render the resolved image, or Avatar's generic
@@ -36,6 +36,7 @@ module Plutonium
36
36
  attr_reader :dashboard
37
37
 
38
38
  def render_content
39
+ render Plutonium::UI::Dashboard::Toolbar.new(dashboard:)
39
40
  render Plutonium::UI::Dashboard::Board.new(dashboard:)
40
41
  end
41
42
 
@@ -42,7 +42,7 @@ module Plutonium
42
42
  end
43
43
 
44
44
  def scope_label(name)
45
- Plutonium::Translation.label_for(:scope, current_query_object.resource_class, name) || name.to_s.humanize
45
+ current_query_object.scope_label(name)
46
46
  end
47
47
 
48
48
  def current_scope
@@ -32,7 +32,7 @@ module Plutonium
32
32
 
33
33
  def render_pill(key)
34
34
  active = current_query_object.selected_scope.to_s == key.to_s
35
- label = Plutonium::Translation.label_for(:scope, current_query_object.resource_class, key) || key.to_s.humanize
35
+ label = current_query_object.scope_label(key)
36
36
 
37
37
  a(
38
38
  id: "#{key}-scope",
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.67.0"
2
+ VERSION = "0.68.0"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.67.0",
3
+ "version": "0.68.0",
4
4
  "description": "Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
data/plutonium.gemspec CHANGED
@@ -60,7 +60,7 @@ Gem::Specification.new do |spec|
60
60
  spec.add_dependency "phlex-rails"
61
61
  spec.add_dependency "phlex-tabler_icons"
62
62
  spec.add_dependency "phlexi-field", ">= 0.2.0"
63
- spec.add_dependency "phlexi-form", ">= 0.14.3"
63
+ spec.add_dependency "phlexi-form", ">= 0.14.4"
64
64
  spec.add_dependency "phlexi-table", ">= 0.2.0"
65
65
  spec.add_dependency "phlexi-display", ">= 0.2.0"
66
66
  spec.add_dependency "phlexi-menu", ">= 0.4.1"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.67.0
4
+ version: 0.68.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-09 00:00:00.000000000 Z
10
+ date: 2026-10-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -203,14 +203,14 @@ dependencies:
203
203
  requirements:
204
204
  - - ">="
205
205
  - !ruby/object:Gem::Version
206
- version: 0.14.3
206
+ version: 0.14.4
207
207
  type: :runtime
208
208
  prerelease: false
209
209
  version_requirements: !ruby/object:Gem::Requirement
210
210
  requirements:
211
211
  - - ">="
212
212
  - !ruby/object:Gem::Version
213
- version: 0.14.3
213
+ version: 0.14.4
214
214
  - !ruby/object:Gem::Dependency
215
215
  name: phlexi-table
216
216
  requirement: !ruby/object:Gem::Requirement
@@ -1144,6 +1144,9 @@ files:
1144
1144
  - lib/plutonium/dashboard/card.rb
1145
1145
  - lib/plutonium/dashboard/controller.rb
1146
1146
  - lib/plutonium/dashboard/dsl.rb
1147
+ - lib/plutonium/dashboard/evaluation.rb
1148
+ - lib/plutonium/dashboard/filter.rb
1149
+ - lib/plutonium/dashboard/link.rb
1147
1150
  - lib/plutonium/dashboard/register.rb
1148
1151
  - lib/plutonium/dashboard/route_resolution.rb
1149
1152
  - lib/plutonium/definition/actions.rb
@@ -1308,6 +1311,7 @@ files:
1308
1311
  - lib/plutonium/ui/dashboard/frame.rb
1309
1312
  - lib/plutonium/ui/dashboard/metric.rb
1310
1313
  - lib/plutonium/ui/dashboard/skeleton.rb
1314
+ - lib/plutonium/ui/dashboard/toolbar.rb
1311
1315
  - lib/plutonium/ui/display/base.rb
1312
1316
  - lib/plutonium/ui/display/components/association.rb
1313
1317
  - lib/plutonium/ui/display/components/attachment.rb
@@ -1354,6 +1358,10 @@ files:
1354
1358
  - lib/plutonium/ui/form/components/rich_text.rb
1355
1359
  - lib/plutonium/ui/form/components/section.rb
1356
1360
  - lib/plutonium/ui/form/components/secure_association.rb
1361
+ - lib/plutonium/ui/form/components/secure_association_checkboxes.rb
1362
+ - lib/plutonium/ui/form/components/secure_association_choices.rb
1363
+ - lib/plutonium/ui/form/components/secure_association_radio_buttons.rb
1364
+ - lib/plutonium/ui/form/components/secure_association_toggles.rb
1357
1365
  - lib/plutonium/ui/form/components/secure_polymorphic_association.rb
1358
1366
  - lib/plutonium/ui/form/components/sticky_footer.rb
1359
1367
  - lib/plutonium/ui/form/components/tags.rb