plutonium 0.55.0 → 0.56.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-resource/SKILL.md +21 -2
  3. data/.claude/skills/plutonium-ui/SKILL.md +15 -2
  4. data/CHANGELOG.md +31 -0
  5. data/app/assets/plutonium.css +1 -1
  6. data/app/assets/plutonium.js +94 -26
  7. data/app/assets/plutonium.js.map +2 -2
  8. data/app/assets/plutonium.min.js +9 -9
  9. data/app/assets/plutonium.min.js.map +3 -3
  10. data/config/initializers/rabl.rb +16 -0
  11. data/docs/.vitepress/config.ts +1 -0
  12. data/docs/public/templates/lite.rb +10 -0
  13. data/docs/reference/generators/lite.md +65 -0
  14. data/docs/reference/resource/definition.md +18 -2
  15. data/docs/reference/ui/assets.md +14 -0
  16. data/docs/reference/ui/displays.md +27 -1
  17. data/docs/reference/ui/forms.md +2 -1
  18. data/docs/reference/ui/layouts.md +33 -0
  19. data/docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md +857 -0
  20. data/docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md.tasks.json +45 -0
  21. data/docs/superpowers/specs/2026-06-04-sqlite-tune-maintenance-generators-design.md +238 -0
  22. data/gemfiles/rails_7.gemfile.lock +1 -1
  23. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  24. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  25. data/lib/generators/pu/core/update/update_generator.rb +4 -1
  26. data/lib/generators/pu/lib/plutonium_generators/concerns/configures_recurring.rb +89 -0
  27. data/lib/generators/pu/lite/maintenance/maintenance_generator.rb +45 -0
  28. data/lib/generators/pu/lite/maintenance/templates/app/jobs/sqlite_maintenance_job.rb.tt +60 -0
  29. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +4 -51
  30. data/lib/generators/pu/lite/rails_pulse/templates/config/initializers/rails_pulse.rb.tt +1 -1
  31. data/lib/generators/pu/lite/tune/tune_generator.rb +105 -0
  32. data/lib/plutonium/models/has_cents.rb +10 -0
  33. data/lib/plutonium/resource/controllers/interactive_actions.rb +19 -2
  34. data/lib/plutonium/routing/mapper_extensions.rb +5 -0
  35. data/lib/plutonium/ui/display/base.rb +9 -0
  36. data/lib/plutonium/ui/display/components/badge.rb +83 -0
  37. data/lib/plutonium/ui/display/components/boolean.rb +28 -6
  38. data/lib/plutonium/ui/display/components/currency.rb +50 -0
  39. data/lib/plutonium/ui/display/options/inferred_types.rb +13 -0
  40. data/lib/plutonium/ui/display/theme.rb +5 -0
  41. data/lib/plutonium/ui/form/base.rb +5 -0
  42. data/lib/plutonium/ui/form/components/toggle.rb +14 -0
  43. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +14 -25
  44. data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +67 -0
  45. data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +5 -38
  46. data/lib/plutonium/ui/form/interaction.rb +7 -2
  47. data/lib/plutonium/ui/form/options/inferred_types.rb +2 -0
  48. data/lib/plutonium/ui/form/resource.rb +1 -0
  49. data/lib/plutonium/ui/form/theme.rb +12 -0
  50. data/lib/plutonium/ui/grid/card.rb +58 -21
  51. data/lib/plutonium/ui/layout/icon_rail.rb +29 -9
  52. data/lib/plutonium/ui/sidebar_menu.rb +29 -0
  53. data/lib/plutonium/version.rb +1 -1
  54. data/package.json +1 -1
  55. data/plutonium.gemspec +5 -4
  56. data/src/css/components.css +126 -0
  57. data/src/js/controllers/dirty_form_guard_controller.js +55 -4
  58. data/src/js/controllers/nested_resource_form_fields_controller.js +35 -12
  59. data/src/js/controllers/resource_drop_down_controller.js +49 -14
  60. metadata +19 -6
@@ -27,9 +27,16 @@ export default class extends Controller {
27
27
 
28
28
  init() {
29
29
  if (this.triggerTarget && this.menuTarget && !this.initialized) {
30
+ // Capture a direct reference to the menu node. While open we teleport
31
+ // it to <body> (see show/hide), which moves it out of this controller's
32
+ // scope — so `this.menuTarget` would throw. Remember its home so we can
33
+ // put it back on hide/disconnect.
34
+ this.menu = this.menuTarget
35
+ this.menuHome = { parent: this.menu.parentNode, next: this.menu.nextSibling }
36
+
30
37
  // Initialize popper instance
31
38
  // Use 'fixed' strategy to escape overflow containers (e.g., table rows)
32
- this.popperInstance = createPopper(this.triggerTarget, this.menuTarget, {
39
+ this.popperInstance = createPopper(this.triggerTarget, this.menu, {
33
40
  strategy: 'fixed',
34
41
  placement: this.options.placement,
35
42
  modifiers: [
@@ -72,20 +79,46 @@ export default class extends Controller {
72
79
  // Remove hover event listeners
73
80
  if (this.options.triggerType === 'hover') {
74
81
  this.triggerTarget.removeEventListener('mouseenter', this.hoverShowTriggerHandler)
75
- this.menuTarget.removeEventListener('mouseenter', this.hoverShowMenuHandler)
82
+ this.menu.removeEventListener('mouseenter', this.hoverShowMenuHandler)
76
83
  this.triggerTarget.removeEventListener('mouseleave', this.hoverHideHandler)
77
- this.menuTarget.removeEventListener('mouseleave', this.hoverHideHandler)
84
+ this.menu.removeEventListener('mouseleave', this.hoverHideHandler)
78
85
  }
79
86
 
80
87
  // Remove click outside listener
81
88
  this.removeClickOutsideListener()
82
89
 
90
+ // Put the menu back where it belongs before we tear down, so a teleported
91
+ // menu isn't orphaned in <body> after the controller goes away. If its
92
+ // home is gone (e.g. turbo swapped the surrounding content), drop it.
93
+ this.restoreMenu()
94
+ if (this.menu.parentNode === document.body) {
95
+ this.menu.remove()
96
+ }
97
+
83
98
  // Destroy popper instance
84
99
  this.popperInstance.destroy()
85
100
  this.initialized = false
86
101
  }
87
102
  }
88
103
 
104
+ // Move the menu to <body> so no ancestor's overflow + containing-block
105
+ // (transform/filter/will-change) can clip it. popper's 'fixed' strategy
106
+ // positions relative to the viewport, but painting is still clipped by a
107
+ // transformed, overflow-hidden ancestor (e.g. grid cards) — teleporting
108
+ // sidesteps that entirely.
109
+ teleportMenu() {
110
+ if (this.menu.parentNode !== document.body) {
111
+ document.body.appendChild(this.menu)
112
+ }
113
+ }
114
+
115
+ restoreMenu() {
116
+ const home = this.menuHome
117
+ if (home && home.parent && home.parent.isConnected && this.menu.parentNode !== home.parent) {
118
+ home.parent.insertBefore(this.menu, home.next)
119
+ }
120
+ }
121
+
89
122
  setupEventListeners() {
90
123
  // Bind handlers to preserve context
91
124
  this.clickHandler = this.toggle.bind(this)
@@ -103,7 +136,7 @@ export default class extends Controller {
103
136
  }
104
137
  this.hoverHideHandler = () => {
105
138
  setTimeout(() => {
106
- if (!this.menuTarget.matches(':hover')) {
139
+ if (!this.menu.matches(':hover')) {
107
140
  this.hide()
108
141
  }
109
142
  }, this.options.delay)
@@ -114,9 +147,9 @@ export default class extends Controller {
114
147
  this.triggerTarget.addEventListener('click', this.clickHandler)
115
148
  } else if (this.options.triggerType === 'hover') {
116
149
  this.triggerTarget.addEventListener('mouseenter', this.hoverShowTriggerHandler)
117
- this.menuTarget.addEventListener('mouseenter', this.hoverShowMenuHandler)
150
+ this.menu.addEventListener('mouseenter', this.hoverShowMenuHandler)
118
151
  this.triggerTarget.addEventListener('mouseleave', this.hoverHideHandler)
119
- this.menuTarget.addEventListener('mouseleave', this.hoverHideHandler)
152
+ this.menu.addEventListener('mouseleave', this.hoverHideHandler)
120
153
  }
121
154
  }
122
155
 
@@ -140,8 +173,8 @@ export default class extends Controller {
140
173
  const isFloatingUI = clickedEl.closest('.flatpickr-calendar, .ss-main, .ss-content')
141
174
 
142
175
  if (
143
- clickedEl !== this.menuTarget &&
144
- !this.menuTarget.contains(clickedEl) &&
176
+ clickedEl !== this.menu &&
177
+ !this.menu.contains(clickedEl) &&
145
178
  !this.triggerTarget.contains(clickedEl) &&
146
179
  !isIgnored &&
147
180
  !isFloatingUI &&
@@ -169,9 +202,10 @@ export default class extends Controller {
169
202
  }
170
203
 
171
204
  show() {
172
- this.menuTarget.classList.remove('hidden')
173
- this.menuTarget.classList.add('block')
174
- this.menuTarget.removeAttribute('aria-hidden')
205
+ this.teleportMenu()
206
+ this.menu.classList.remove('hidden')
207
+ this.menu.classList.add('block')
208
+ this.menu.removeAttribute('aria-hidden')
175
209
 
176
210
  // Enable popper event listeners
177
211
  this.popperInstance.setOptions((options) => ({
@@ -188,9 +222,9 @@ export default class extends Controller {
188
222
  }
189
223
 
190
224
  hide() {
191
- this.menuTarget.classList.remove('block')
192
- this.menuTarget.classList.add('hidden')
193
- this.menuTarget.setAttribute('aria-hidden', 'true')
225
+ this.menu.classList.remove('block')
226
+ this.menu.classList.add('hidden')
227
+ this.menu.setAttribute('aria-hidden', 'true')
194
228
 
195
229
  // Disable popper event listeners
196
230
  this.popperInstance.setOptions((options) => ({
@@ -202,6 +236,7 @@ export default class extends Controller {
202
236
  }))
203
237
 
204
238
  this.removeClickOutsideListener()
239
+ this.restoreMenu()
205
240
  this.visible = false
206
241
  }
207
242
  }
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.55.0
4
+ version: 0.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-06-03 00:00:00.000000000 Z
10
+ date: 2026-06-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -637,6 +637,7 @@ files:
637
637
  - docs/reference/behavior/interactions.md
638
638
  - docs/reference/behavior/policies.md
639
639
  - docs/reference/configuration.md
640
+ - docs/reference/generators/lite.md
640
641
  - docs/reference/index.md
641
642
  - docs/reference/resource/actions.md
642
643
  - docs/reference/resource/definition.md
@@ -667,6 +668,8 @@ files:
667
668
  - docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json
668
669
  - docs/superpowers/plans/2026-06-02-structured-inputs.md
669
670
  - docs/superpowers/plans/2026-06-02-structured-inputs.md.tasks.json
671
+ - docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md
672
+ - docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md.tasks.json
670
673
  - docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md
671
674
  - docs/superpowers/specs/2026-04-14-plutonium-testing-design.md
672
675
  - docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md
@@ -676,6 +679,7 @@ files:
676
679
  - docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md
677
680
  - docs/superpowers/specs/2026-05-29-avatar-component-design.md
678
681
  - docs/superpowers/specs/2026-06-01-structured-inputs-design.md
682
+ - docs/superpowers/specs/2026-06-04-sqlite-tune-maintenance-generators-design.md
679
683
  - esbuild.config.js
680
684
  - exe/pug
681
685
  - gemfiles/rails_7.gemfile
@@ -778,6 +782,7 @@ files:
778
782
  - lib/generators/pu/lib/plutonium_generators/cli.rb
779
783
  - lib/generators/pu/lib/plutonium_generators/concerns/actions.rb
780
784
  - lib/generators/pu/lib/plutonium_generators/concerns/config.rb
785
+ - lib/generators/pu/lib/plutonium_generators/concerns/configures_recurring.rb
781
786
  - lib/generators/pu/lib/plutonium_generators/concerns/configures_sqlite.rb
782
787
  - lib/generators/pu/lib/plutonium_generators/concerns/logger.rb
783
788
  - lib/generators/pu/lib/plutonium_generators/concerns/mounts_engines.rb
@@ -790,6 +795,8 @@ files:
790
795
  - lib/generators/pu/lib/plutonium_generators/model_generator_base.rb
791
796
  - lib/generators/pu/lib/plutonium_generators/non_interactive_prompt.rb
792
797
  - lib/generators/pu/lite/litestream/litestream_generator.rb
798
+ - lib/generators/pu/lite/maintenance/maintenance_generator.rb
799
+ - lib/generators/pu/lite/maintenance/templates/app/jobs/sqlite_maintenance_job.rb.tt
793
800
  - lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb
794
801
  - lib/generators/pu/lite/rails_pulse/templates/config/initializers/rails_pulse.rb.tt
795
802
  - lib/generators/pu/lite/setup/setup_generator.rb
@@ -797,6 +804,7 @@ files:
797
804
  - lib/generators/pu/lite/solid_cache/solid_cache_generator.rb
798
805
  - lib/generators/pu/lite/solid_errors/solid_errors_generator.rb
799
806
  - lib/generators/pu/lite/solid_queue/solid_queue_generator.rb
807
+ - lib/generators/pu/lite/tune/tune_generator.rb
800
808
  - lib/generators/pu/pkg/package/package_generator.rb
801
809
  - lib/generators/pu/pkg/package/templates/.keep
802
810
  - lib/generators/pu/pkg/package/templates/app/controllers/resource_controller.rb.tt
@@ -1084,8 +1092,10 @@ files:
1084
1092
  - lib/plutonium/ui/display/base.rb
1085
1093
  - lib/plutonium/ui/display/components/association.rb
1086
1094
  - lib/plutonium/ui/display/components/attachment.rb
1095
+ - lib/plutonium/ui/display/components/badge.rb
1087
1096
  - lib/plutonium/ui/display/components/boolean.rb
1088
1097
  - lib/plutonium/ui/display/components/color.rb
1098
+ - lib/plutonium/ui/display/components/currency.rb
1089
1099
  - lib/plutonium/ui/display/components/markdown.rb
1090
1100
  - lib/plutonium/ui/display/components/phlexi_render.rb
1091
1101
  - lib/plutonium/ui/display/options/inferred_types.rb
@@ -1105,8 +1115,10 @@ files:
1105
1115
  - lib/plutonium/ui/form/components/secure_association.rb
1106
1116
  - lib/plutonium/ui/form/components/secure_polymorphic_association.rb
1107
1117
  - lib/plutonium/ui/form/components/sticky_footer.rb
1118
+ - lib/plutonium/ui/form/components/toggle.rb
1108
1119
  - lib/plutonium/ui/form/components/uppy.rb
1109
1120
  - lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb
1121
+ - lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb
1110
1122
  - lib/plutonium/ui/form/concerns/renders_structured_inputs.rb
1111
1123
  - lib/plutonium/ui/form/concerns/repeater_field_styles.rb
1112
1124
  - lib/plutonium/ui/form/concerns/typeahead_attributes.rb
@@ -1239,17 +1251,18 @@ metadata:
1239
1251
  homepage_uri: https://radioactive-labs.github.io/plutonium-core/
1240
1252
  source_code_uri: https://github.com/radioactive-labs/plutonium-core
1241
1253
  post_install_message: |
1242
- ⚠️ Plutonium 0.55.0 — breaking change
1254
+ ℹ️ Plutonium — breaking change introduced in 0.49.0
1243
1255
 
1244
- Entity-scoped URL helpers and path params have been renamed from
1256
+ Entity-scoped URL helpers and path params were renamed in 0.49.0 from
1245
1257
  `<entity>_scope_*` to `<entity>_scoped_*`.
1246
1258
 
1247
1259
  Examples:
1248
1260
  organization_scope_widgets_path → organization_scoped_widgets_path
1249
1261
  params[:organization_scope] → params[:organization_scoped]
1250
1262
 
1251
- If you reference these helpers or params directly (e.g. in tests, custom
1252
- redirects, or hand-written links), update them to the new names.
1263
+ If you are upgrading from 0.48.0 or earlier and reference these helpers or
1264
+ params directly (e.g. in tests, custom redirects, or hand-written links),
1265
+ update them to the new names.
1253
1266
 
1254
1267
  Apps that only use `resource_url_for` are unaffected.
1255
1268
  rdoc_options: []