plutonium 0.60.5 → 0.61.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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +19 -1
  3. data/.claude/skills/plutonium-app/SKILL.md +41 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +40 -0
  5. data/.claude/skills/plutonium-behavior/SKILL.md +47 -1
  6. data/.claude/skills/plutonium-kanban/SKILL.md +313 -0
  7. data/.claude/skills/plutonium-resource/SKILL.md +40 -0
  8. data/.claude/skills/plutonium-tenancy/SKILL.md +43 -0
  9. data/.claude/skills/plutonium-testing/SKILL.md +38 -0
  10. data/.claude/skills/plutonium-ui/SKILL.md +51 -0
  11. data/.claude/skills/plutonium-wizard/SKILL.md +469 -0
  12. data/.cliff.toml +6 -0
  13. data/Appraisals +3 -0
  14. data/CHANGELOG.md +549 -439
  15. data/CLAUDE.md +15 -7
  16. data/app/assets/plutonium.css +1 -1
  17. data/app/assets/plutonium.js +895 -193
  18. data/app/assets/plutonium.js.map +4 -4
  19. data/app/assets/plutonium.min.js +53 -53
  20. data/app/assets/plutonium.min.js.map +4 -4
  21. data/app/views/layouts/basic.html.erb +7 -0
  22. data/app/views/plutonium/_flash_toasts.html.erb +2 -46
  23. data/app/views/plutonium/_toast.html.erb +52 -0
  24. data/app/views/resource/_resource_kanban.html.erb +1 -0
  25. data/db/migrate/wizard/20260615000001_create_plutonium_wizard_sessions.rb +57 -0
  26. data/docs/.vitepress/config.ts +24 -0
  27. data/docs/guides/index.md +2 -0
  28. data/docs/guides/kanban.md +447 -0
  29. data/docs/guides/wizards.md +447 -0
  30. data/docs/public/images/guides/kanban-after-move.png +0 -0
  31. data/docs/public/images/guides/kanban-board-light.png +0 -0
  32. data/docs/public/images/guides/kanban-board.png +0 -0
  33. data/docs/public/images/guides/kanban-show-centered-modal.png +0 -0
  34. data/docs/public/images/guides/kanban-wip-toast.png +0 -0
  35. data/docs/public/images/guides/wizards-chooser.png +0 -0
  36. data/docs/public/images/guides/wizards-completed.png +0 -0
  37. data/docs/public/images/guides/wizards-index-action.png +0 -0
  38. data/docs/public/images/guides/wizards-repeater.png +0 -0
  39. data/docs/public/images/guides/wizards-review.png +0 -0
  40. data/docs/public/images/guides/wizards-step.png +0 -0
  41. data/docs/reference/behavior/policies.md +1 -1
  42. data/docs/reference/index.md +14 -0
  43. data/docs/reference/kanban/authorization.md +62 -0
  44. data/docs/reference/kanban/dsl.md +293 -0
  45. data/docs/reference/kanban/index.md +40 -0
  46. data/docs/reference/kanban/positioning.md +162 -0
  47. data/docs/reference/resource/definition.md +16 -0
  48. data/docs/reference/ui/forms.md +36 -0
  49. data/docs/reference/ui/pages.md +2 -0
  50. data/docs/reference/wizard/anchoring-resume.md +194 -0
  51. data/docs/reference/wizard/dsl.md +332 -0
  52. data/docs/reference/wizard/index.md +33 -0
  53. data/docs/reference/wizard/one-time.md +129 -0
  54. data/docs/reference/wizard/registration-launch.md +177 -0
  55. data/docs/reference/wizard/storage-config.md +151 -0
  56. data/docs/superpowers/plans/2026-06-14-form-sectioning.md +2 -2
  57. data/docs/superpowers/plans/2026-06-15-wizard-dsl.md +1619 -0
  58. data/docs/superpowers/plans/2026-06-15-wizard-dsl.md.tasks.json +68 -0
  59. data/docs/superpowers/plans/2026-06-26-kanban-dsl.md +1128 -0
  60. data/docs/superpowers/plans/2026-06-26-kanban-dsl.md.tasks.json +24 -0
  61. data/docs/superpowers/specs/2026-06-15-wizard-dsl-design.md +836 -0
  62. data/docs/superpowers/specs/2026-06-15-wizard-dsl-examples.rb +245 -0
  63. data/docs/superpowers/specs/2026-06-17-wizard-relaunch-prompt-design.md +86 -0
  64. data/docs/superpowers/specs/2026-06-18-wizard-attachments-design.md +101 -0
  65. data/docs/superpowers/specs/2026-06-18-wizard-hosting-design.md +220 -0
  66. data/docs/superpowers/specs/2026-06-26-kanban-dsl-design.md +388 -0
  67. data/gemfiles/postgres.gemfile +8 -0
  68. data/gemfiles/postgres.gemfile.lock +321 -0
  69. data/gemfiles/rails_7.gemfile +1 -0
  70. data/gemfiles/rails_7.gemfile.lock +1 -1
  71. data/gemfiles/rails_8.0.gemfile +1 -0
  72. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  73. data/gemfiles/rails_8.1.gemfile +1 -0
  74. data/gemfiles/rails_8.1.gemfile.lock +14 -1
  75. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +6 -1
  76. data/lib/plutonium/action/base.rb +9 -0
  77. data/lib/plutonium/auth/rodauth.rb +1 -2
  78. data/lib/plutonium/configuration.rb +4 -0
  79. data/lib/plutonium/core/controller.rb +20 -1
  80. data/lib/plutonium/definition/base.rb +25 -0
  81. data/lib/plutonium/definition/form_layout.rb +54 -35
  82. data/lib/plutonium/definition/index_views.rb +54 -1
  83. data/lib/plutonium/definition/wizards.rb +209 -0
  84. data/lib/plutonium/invites/concerns/invite_token.rb +9 -0
  85. data/lib/plutonium/invites/concerns/invite_user.rb +9 -0
  86. data/lib/plutonium/invites/controller.rb +4 -1
  87. data/lib/plutonium/kanban/action.rb +7 -0
  88. data/lib/plutonium/kanban/board.rb +40 -0
  89. data/lib/plutonium/kanban/broadcaster.rb +54 -0
  90. data/lib/plutonium/kanban/column.rb +69 -0
  91. data/lib/plutonium/kanban/context.rb +15 -0
  92. data/lib/plutonium/kanban/dsl.rb +71 -0
  93. data/lib/plutonium/kanban/grouping.rb +51 -0
  94. data/lib/plutonium/kanban/positioning.rb +75 -0
  95. data/lib/plutonium/kanban.rb +11 -0
  96. data/lib/plutonium/migrations.rb +40 -0
  97. data/lib/plutonium/positioning.rb +146 -0
  98. data/lib/plutonium/railtie.rb +33 -0
  99. data/lib/plutonium/resource/controller.rb +2 -0
  100. data/lib/plutonium/resource/controllers/crud_actions.rb +1 -1
  101. data/lib/plutonium/resource/controllers/kanban_actions.rb +455 -0
  102. data/lib/plutonium/resource/controllers/wizard_actions.rb +165 -0
  103. data/lib/plutonium/resource/policy.rb +8 -0
  104. data/lib/plutonium/routing/mapper_extensions.rb +44 -0
  105. data/lib/plutonium/routing/wizard_registration.rb +289 -0
  106. data/lib/plutonium/ui/display/resource.rb +17 -12
  107. data/lib/plutonium/ui/form/base.rb +19 -5
  108. data/lib/plutonium/ui/form/components/password.rb +126 -0
  109. data/lib/plutonium/ui/form/components/uppy.rb +6 -3
  110. data/lib/plutonium/ui/form/options/inferred_types.rb +20 -0
  111. data/lib/plutonium/ui/form/resource.rb +1 -1
  112. data/lib/plutonium/ui/form/wizard.rb +63 -0
  113. data/lib/plutonium/ui/grid/card.rb +16 -5
  114. data/lib/plutonium/ui/kanban/card.rb +67 -0
  115. data/lib/plutonium/ui/kanban/color_dot.rb +36 -0
  116. data/lib/plutonium/ui/kanban/column.rb +324 -0
  117. data/lib/plutonium/ui/kanban/resource.rb +212 -0
  118. data/lib/plutonium/ui/layout/resource_layout.rb +7 -1
  119. data/lib/plutonium/ui/modal/base.rb +30 -3
  120. data/lib/plutonium/ui/modal/centered.rb +5 -2
  121. data/lib/plutonium/ui/page/index.rb +1 -0
  122. data/lib/plutonium/ui/page/show.rb +23 -0
  123. data/lib/plutonium/ui/page/wizard.rb +371 -0
  124. data/lib/plutonium/ui/page/wizard_chooser.rb +97 -0
  125. data/lib/plutonium/ui/page/wizard_completed.rb +86 -0
  126. data/lib/plutonium/ui/table/base.rb +1 -1
  127. data/lib/plutonium/ui/table/components/view_switcher.rb +2 -1
  128. data/lib/plutonium/ui/wizard/review.rb +196 -0
  129. data/lib/plutonium/ui/wizard/stepper.rb +122 -0
  130. data/lib/plutonium/ui/wizard/summary_display.rb +59 -0
  131. data/lib/plutonium/version.rb +1 -1
  132. data/lib/plutonium/wizard/attachment_data.rb +42 -0
  133. data/lib/plutonium/wizard/attachments.rb +226 -0
  134. data/lib/plutonium/wizard/base.rb +216 -0
  135. data/lib/plutonium/wizard/base_controller.rb +31 -0
  136. data/lib/plutonium/wizard/configuration.rb +42 -0
  137. data/lib/plutonium/wizard/controller.rb +162 -0
  138. data/lib/plutonium/wizard/data.rb +134 -0
  139. data/lib/plutonium/wizard/driving.rb +639 -0
  140. data/lib/plutonium/wizard/dsl.rb +336 -0
  141. data/lib/plutonium/wizard/errors.rb +27 -0
  142. data/lib/plutonium/wizard/field_capture.rb +157 -0
  143. data/lib/plutonium/wizard/field_importer.rb +208 -0
  144. data/lib/plutonium/wizard/gate.rb +171 -0
  145. data/lib/plutonium/wizard/instance_key.rb +97 -0
  146. data/lib/plutonium/wizard/lazy_persisted.rb +77 -0
  147. data/lib/plutonium/wizard/resume.rb +250 -0
  148. data/lib/plutonium/wizard/review_step.rb +48 -0
  149. data/lib/plutonium/wizard/route_resolution.rb +40 -0
  150. data/lib/plutonium/wizard/runner.rb +684 -0
  151. data/lib/plutonium/wizard/session.rb +53 -0
  152. data/lib/plutonium/wizard/state.rb +35 -0
  153. data/lib/plutonium/wizard/step.rb +61 -0
  154. data/lib/plutonium/wizard/step_adapter.rb +103 -0
  155. data/lib/plutonium/wizard/store/active_record.rb +174 -0
  156. data/lib/plutonium/wizard/store/base.rb +42 -0
  157. data/lib/plutonium/wizard/store/memory.rb +44 -0
  158. data/lib/plutonium/wizard/sweep_job.rb +76 -0
  159. data/lib/plutonium/wizard.rb +86 -0
  160. data/lib/plutonium.rb +5 -0
  161. data/lib/rodauth/features/case_insensitive_login.rb +1 -1
  162. data/lib/tasks/release.rake +144 -191
  163. data/package.json +3 -3
  164. data/src/css/components.css +132 -0
  165. data/src/js/controllers/attachment_input_controller.js +15 -1
  166. data/src/js/controllers/dirty_form_guard_controller.js +155 -27
  167. data/src/js/controllers/kanban_controller.js +330 -0
  168. data/src/js/controllers/password_sentinel_controller.js +39 -0
  169. data/src/js/controllers/register_controllers.js +6 -0
  170. data/src/js/controllers/remote_modal_controller.js +10 -0
  171. data/src/js/controllers/row_click_controller.js +14 -1
  172. data/src/js/controllers/wizard_controller.js +54 -0
  173. data/src/js/turbo/turbo_confirm.js +1 -1
  174. data/yarn.lock +271 -282
  175. metadata +100 -1
@@ -0,0 +1,321 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ action_text-trix (2.1.19)
5
+ railties
6
+ actioncable (8.1.3)
7
+ actionpack (= 8.1.3)
8
+ activesupport (= 8.1.3)
9
+ nio4r (~> 2.0)
10
+ websocket-driver (>= 0.6.1)
11
+ zeitwerk (~> 2.6)
12
+ actionmailbox (8.1.3)
13
+ actionpack (= 8.1.3)
14
+ activejob (= 8.1.3)
15
+ activerecord (= 8.1.3)
16
+ activestorage (= 8.1.3)
17
+ activesupport (= 8.1.3)
18
+ mail (>= 2.8.0)
19
+ actionmailer (8.1.3)
20
+ actionpack (= 8.1.3)
21
+ actionview (= 8.1.3)
22
+ activejob (= 8.1.3)
23
+ activesupport (= 8.1.3)
24
+ mail (>= 2.8.0)
25
+ rails-dom-testing (~> 2.2)
26
+ actionpack (8.1.3)
27
+ actionview (= 8.1.3)
28
+ activesupport (= 8.1.3)
29
+ nokogiri (>= 1.8.5)
30
+ rack (>= 2.2.4)
31
+ rack-session (>= 1.0.1)
32
+ rack-test (>= 0.6.3)
33
+ rails-dom-testing (~> 2.2)
34
+ rails-html-sanitizer (~> 1.6)
35
+ useragent (~> 0.16)
36
+ actiontext (8.1.3)
37
+ action_text-trix (~> 2.1.15)
38
+ actionpack (= 8.1.3)
39
+ activerecord (= 8.1.3)
40
+ activestorage (= 8.1.3)
41
+ activesupport (= 8.1.3)
42
+ globalid (>= 0.6.0)
43
+ nokogiri (>= 1.8.5)
44
+ actionview (8.1.3)
45
+ activesupport (= 8.1.3)
46
+ builder (~> 3.1)
47
+ erubi (~> 1.11)
48
+ rails-dom-testing (~> 2.2)
49
+ rails-html-sanitizer (~> 1.6)
50
+ activejob (8.1.3)
51
+ activesupport (= 8.1.3)
52
+ globalid (>= 0.3.6)
53
+ activemodel (8.1.3)
54
+ activesupport (= 8.1.3)
55
+ activerecord (8.1.3)
56
+ activemodel (= 8.1.3)
57
+ activesupport (= 8.1.3)
58
+ timeout (>= 0.4.0)
59
+ activestorage (8.1.3)
60
+ actionpack (= 8.1.3)
61
+ activejob (= 8.1.3)
62
+ activerecord (= 8.1.3)
63
+ activesupport (= 8.1.3)
64
+ marcel (~> 1.0)
65
+ activesupport (8.1.3)
66
+ base64
67
+ bigdecimal
68
+ concurrent-ruby (~> 1.0, >= 1.3.1)
69
+ connection_pool (>= 2.2.5)
70
+ drb
71
+ i18n (>= 1.6, < 2)
72
+ json
73
+ logger (>= 1.4.2)
74
+ minitest (>= 5.1)
75
+ securerandom (>= 0.3)
76
+ tzinfo (~> 2.0, >= 2.0.5)
77
+ uri (>= 0.13.1)
78
+ base64 (0.3.0)
79
+ bigdecimal (4.1.2)
80
+ builder (3.3.0)
81
+ concurrent-ruby (1.3.7)
82
+ connection_pool (3.0.2)
83
+ crass (1.0.7)
84
+ date (3.5.1)
85
+ drb (2.2.3)
86
+ erb (6.0.4)
87
+ erubi (1.13.1)
88
+ globalid (1.4.0)
89
+ activesupport (>= 6.1)
90
+ i18n (1.15.2)
91
+ concurrent-ruby (~> 1.0)
92
+ io-console (0.8.2)
93
+ irb (1.18.0)
94
+ pp (>= 0.6.0)
95
+ prism (>= 1.3.0)
96
+ rdoc (>= 4.0.0)
97
+ reline (>= 0.4.2)
98
+ json (2.20.0)
99
+ logger (1.7.0)
100
+ loofah (2.25.1)
101
+ crass (~> 1.0.2)
102
+ nokogiri (>= 1.12.0)
103
+ mail (2.9.0)
104
+ logger
105
+ mini_mime (>= 0.1.1)
106
+ net-imap
107
+ net-pop
108
+ net-smtp
109
+ marcel (1.2.1)
110
+ mini_mime (1.1.5)
111
+ mini_portile2 (2.8.9)
112
+ minitest (6.0.6)
113
+ drb (~> 2.0)
114
+ prism (~> 1.5)
115
+ net-imap (0.6.4.1)
116
+ date
117
+ net-protocol
118
+ net-pop (0.1.2)
119
+ net-protocol
120
+ net-protocol (0.2.2)
121
+ timeout
122
+ net-smtp (0.5.1)
123
+ net-protocol
124
+ nio4r (2.7.5)
125
+ nokogiri (1.19.4)
126
+ mini_portile2 (~> 2.8.2)
127
+ racc (~> 1.4)
128
+ nokogiri (1.19.4-aarch64-linux-gnu)
129
+ racc (~> 1.4)
130
+ nokogiri (1.19.4-aarch64-linux-musl)
131
+ racc (~> 1.4)
132
+ nokogiri (1.19.4-arm-linux-gnu)
133
+ racc (~> 1.4)
134
+ nokogiri (1.19.4-arm-linux-musl)
135
+ racc (~> 1.4)
136
+ nokogiri (1.19.4-arm64-darwin)
137
+ racc (~> 1.4)
138
+ nokogiri (1.19.4-x86_64-darwin)
139
+ racc (~> 1.4)
140
+ nokogiri (1.19.4-x86_64-linux-gnu)
141
+ racc (~> 1.4)
142
+ nokogiri (1.19.4-x86_64-linux-musl)
143
+ racc (~> 1.4)
144
+ pg (1.6.3)
145
+ pg (1.6.3-aarch64-linux)
146
+ pg (1.6.3-aarch64-linux-musl)
147
+ pg (1.6.3-arm64-darwin)
148
+ pg (1.6.3-x86_64-darwin)
149
+ pg (1.6.3-x86_64-linux)
150
+ pg (1.6.3-x86_64-linux-musl)
151
+ pp (0.6.4)
152
+ prettyprint
153
+ prettyprint (0.2.0)
154
+ prism (1.9.0)
155
+ racc (1.8.1)
156
+ rack (3.2.6)
157
+ rack-session (2.1.2)
158
+ base64 (>= 0.1.0)
159
+ rack (>= 3.0.0)
160
+ rack-test (2.2.0)
161
+ rack (>= 1.3)
162
+ rackup (2.3.1)
163
+ rack (>= 3)
164
+ rails (8.1.3)
165
+ actioncable (= 8.1.3)
166
+ actionmailbox (= 8.1.3)
167
+ actionmailer (= 8.1.3)
168
+ actionpack (= 8.1.3)
169
+ actiontext (= 8.1.3)
170
+ actionview (= 8.1.3)
171
+ activejob (= 8.1.3)
172
+ activemodel (= 8.1.3)
173
+ activerecord (= 8.1.3)
174
+ activestorage (= 8.1.3)
175
+ activesupport (= 8.1.3)
176
+ bundler (>= 1.15.0)
177
+ railties (= 8.1.3)
178
+ rails-dom-testing (2.3.0)
179
+ activesupport (>= 5.0.0)
180
+ minitest
181
+ nokogiri (>= 1.6)
182
+ rails-html-sanitizer (1.7.0)
183
+ loofah (~> 2.25)
184
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
185
+ railties (8.1.3)
186
+ actionpack (= 8.1.3)
187
+ activesupport (= 8.1.3)
188
+ irb (~> 1.13)
189
+ rackup (>= 1.0.0)
190
+ rake (>= 12.2)
191
+ thor (~> 1.0, >= 1.2.2)
192
+ tsort (>= 0.2)
193
+ zeitwerk (~> 2.6)
194
+ rake (13.4.2)
195
+ rbs (4.0.3)
196
+ logger
197
+ prism (>= 1.6.0)
198
+ tsort
199
+ rdoc (8.0.0)
200
+ erb
201
+ prism (>= 1.6.0)
202
+ rbs (>= 4.0.0)
203
+ tsort
204
+ reline (0.6.3)
205
+ io-console (~> 0.5)
206
+ securerandom (0.4.1)
207
+ thor (1.5.0)
208
+ timeout (0.6.1)
209
+ tsort (0.2.0)
210
+ tzinfo (2.0.6)
211
+ concurrent-ruby (~> 1.0)
212
+ uri (1.1.1)
213
+ useragent (0.16.11)
214
+ websocket-driver (0.8.2)
215
+ base64
216
+ websocket-extensions (>= 0.1.0)
217
+ websocket-extensions (0.1.5)
218
+ zeitwerk (2.8.2)
219
+
220
+ PLATFORMS
221
+ aarch64-linux
222
+ aarch64-linux-gnu
223
+ aarch64-linux-musl
224
+ arm-linux-gnu
225
+ arm-linux-musl
226
+ arm64-darwin
227
+ ruby
228
+ x86_64-darwin
229
+ x86_64-linux
230
+ x86_64-linux-gnu
231
+ x86_64-linux-musl
232
+
233
+ DEPENDENCIES
234
+ pg (~> 1.5)
235
+ rails (~> 8.1.0)
236
+
237
+ CHECKSUMS
238
+ action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009
239
+ actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0
240
+ actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59
241
+ actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d
242
+ actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3
243
+ actiontext (8.1.3) sha256=d291019c00e1ea9e6463011fa214f6081a56d7b9a1d224e7d3f6384c1dafc7d2
244
+ actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d
245
+ activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3
246
+ activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219
247
+ activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab
248
+ activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d
249
+ activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e
250
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
251
+ bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
252
+ builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
253
+ concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
254
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
255
+ crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
256
+ date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
257
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
258
+ erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
259
+ erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
260
+ globalid (1.4.0) sha256=037f12fbf1d9d7a014d501c2d5c77356fd4ddd96d7a7991d6700bba96706f427
261
+ i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
262
+ io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
263
+ irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
264
+ json (2.20.0) sha256=9362bc6e55a952b056abf9167cf053358181c904cb70cd6eee0808ea830fc32b
265
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
266
+ loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
267
+ mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
268
+ marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f
269
+ mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
270
+ mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289
271
+ minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
272
+ net-imap (0.6.4.1) sha256=29f0360d75a7efd3539f16ac1957dea5c0a51ddeceb348db4553c3120914ea0d
273
+ net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
274
+ net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
275
+ net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
276
+ nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
277
+ nokogiri (1.19.4) sha256=50c951611c92bca05c51411aef45f1cbc50f2821c4802758c5c6d34696533ab5
278
+ nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
279
+ nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af
280
+ nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca
281
+ nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb
282
+ nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
283
+ nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
284
+ nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
285
+ nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
286
+ pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99
287
+ pg (1.6.3-aarch64-linux) sha256=0698ad563e02383c27510b76bf7d4cd2de19cd1d16a5013f375dd473e4be72ea
288
+ pg (1.6.3-aarch64-linux-musl) sha256=06a75f4ea04b05140146f2a10550b8e0d9f006a79cdaf8b5b130cde40e3ecc2c
289
+ pg (1.6.3-arm64-darwin) sha256=7240330b572e6355d7c75a7de535edb5dfcbd6295d9c7777df4d9dddfb8c0e5f
290
+ pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6
291
+ pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d
292
+ pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009
293
+ pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
294
+ prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
295
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
296
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
297
+ rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
298
+ rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
299
+ rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
300
+ rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
301
+ rails (8.1.3) sha256=6d017ba5348c98fc909753a8169b21d44de14d2a0b92d140d1a966834c3c9cd3
302
+ rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
303
+ rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
304
+ railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22
305
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
306
+ rbs (4.0.3) sha256=5a7bf70e2628549d9a1f44eae447b2cfe55968a9c60cfff52693a4bdcc020e14
307
+ rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
308
+ reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
309
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
310
+ thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
311
+ timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
312
+ tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
313
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
314
+ uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
315
+ useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
316
+ websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146
317
+ websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
318
+ zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12
319
+
320
+ BUNDLED WITH
321
+ 4.0.6
@@ -15,6 +15,7 @@ gem "sequel-activerecord_connection"
15
15
  gem "tilt"
16
16
  gem "bcrypt"
17
17
  gem "rqrcode"
18
+ gem "active_shrine"
18
19
  gem "tzinfo-data", platforms: [:windows, :jruby]
19
20
 
20
21
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.59.0)
4
+ plutonium (0.60.4)
5
5
  action_policy (~> 0.7.0)
6
6
  csv
7
7
  listen (~> 3.8)
@@ -15,6 +15,7 @@ gem "sequel-activerecord_connection"
15
15
  gem "tilt"
16
16
  gem "bcrypt"
17
17
  gem "rqrcode"
18
+ gem "active_shrine"
18
19
  gem "tzinfo-data", platforms: [:windows, :jruby]
19
20
 
20
21
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.59.0)
4
+ plutonium (0.60.4)
5
5
  action_policy (~> 0.7.0)
6
6
  csv
7
7
  listen (~> 3.8)
@@ -15,6 +15,7 @@ gem "sequel-activerecord_connection"
15
15
  gem "tilt"
16
16
  gem "bcrypt"
17
17
  gem "rqrcode"
18
+ gem "active_shrine"
18
19
  gem "tzinfo-data", platforms: [:windows, :jruby]
19
20
 
20
21
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.59.0)
4
+ plutonium (0.60.4)
5
5
  action_policy (~> 0.7.0)
6
6
  csv
7
7
  listen (~> 3.8)
@@ -74,6 +74,11 @@ GEM
74
74
  erubi (~> 1.11)
75
75
  rails-dom-testing (~> 2.2)
76
76
  rails-html-sanitizer (~> 1.6)
77
+ active_shrine (0.7.1)
78
+ activestorage
79
+ activesupport
80
+ railties
81
+ shrine
77
82
  activejob (8.1.2)
78
83
  activesupport (= 8.1.2)
79
84
  globalid (>= 0.3.6)
@@ -137,9 +142,13 @@ GEM
137
142
  thor (>= 0.14.6)
138
143
  concurrent-ruby (1.3.6)
139
144
  connection_pool (3.0.2)
145
+ content_disposition (1.0.0)
140
146
  crass (1.0.6)
141
147
  csv (3.3.5)
142
148
  date (3.5.1)
149
+ down (5.6.0)
150
+ addressable (~> 2.8)
151
+ base64 (~> 0.3)
143
152
  drb (2.2.3)
144
153
  erb (6.0.2)
145
154
  erubi (1.13.1)
@@ -369,6 +378,9 @@ GEM
369
378
  sequel-activerecord_connection (2.0.1)
370
379
  activerecord (>= 5.1)
371
380
  sequel (~> 5.38)
381
+ shrine (3.7.1)
382
+ content_disposition (~> 1.0)
383
+ down (~> 5.1)
372
384
  sin_lru_redux (2.5.2)
373
385
  sqlite3 (2.9.1-arm64-darwin)
374
386
  standard (1.54.0)
@@ -427,6 +439,7 @@ PLATFORMS
427
439
  arm64-darwin
428
440
 
429
441
  DEPENDENCIES
442
+ active_shrine
430
443
  appraisal
431
444
  bcrypt
432
445
  brakeman
@@ -54,9 +54,14 @@ module Invites
54
54
 
55
55
  <% if rodauth? -%>
56
56
  def create_user_for_signup(email, password)
57
+ # Normalize the login up front. Lookups (account_from_login) downcase the
58
+ # login, so on a case-sensitive database a verbatim mixed-case email would
59
+ # be created but never found again. Downcasing here keeps create and
60
+ # lookup in agreement across both branches.
61
+ email = email.downcase
57
62
  password_hash = rodauth.password_hash(password)
58
63
 
59
- if email.downcase == @invite.email.downcase
64
+ if email == @invite.email.downcase
60
65
  # Email matches invitation - create verified account directly
61
66
  <%= user_model %>.create!(
62
67
  email: email,
@@ -49,8 +49,17 @@ module Plutonium
49
49
  # Downgrades the remote-modal frame to nil when the definition has
50
50
  # `modal false`, so the link navigates as a full page instead of
51
51
  # targeting a frame that won't exist. Other frames pass through.
52
+ #
53
+ # The canonical :show action carries no explicit frame; instead it reads
54
+ # the definition's `show_in` so a resource can open its show page in a
55
+ # modal (`show_in :modal`) or full-page (`:page`, default) without the
56
+ # caller threading a frame. This is deliberately independent of
57
+ # `modal_mode` (which styles :new/:edit) — show is always centered.
52
58
  def turbo_frame(definition = nil)
53
59
  return nil if definition && targets_remote_modal? && definition.modal_mode == false
60
+ if definition && name == :show && @turbo_frame.nil?
61
+ return (definition.show_in == :modal) ? Plutonium::REMOTE_MODAL_FRAME : nil
62
+ end
54
63
  @turbo_frame
55
64
  end
56
65
 
@@ -7,7 +7,7 @@ module Plutonium
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- helper_method :current_user, :current_#{name}
10
+ helper_method :current_user
11
11
  helper_method :logout_url
12
12
  helper_method :profile_url
13
13
  end
@@ -23,7 +23,6 @@ module Plutonium
23
23
  def current_user
24
24
  rodauth.rails_account
25
25
  end
26
- alias_method :current_#{name}, :current_user
27
26
 
28
27
  def logout_url
29
28
  rodauth.logout_path
@@ -24,6 +24,9 @@ module Plutonium
24
24
  # @return [AssetConfiguration] asset configuration
25
25
  attr_reader :assets
26
26
 
27
+ # @return [Plutonium::Wizard::Configuration] wizard subsystem configuration
28
+ attr_reader :wizards
29
+
27
30
  # @return [Float] the current defaults version
28
31
  attr_reader :defaults_version
29
32
 
@@ -54,6 +57,7 @@ module Plutonium
54
57
  def initialize
55
58
  @defaults_version = nil
56
59
  @assets = AssetConfiguration.new
60
+ @wizards = Plutonium::Wizard::Configuration.new
57
61
 
58
62
  @development = parse_boolean_env("PLUTONIUM_DEV")
59
63
  @cache_discovery = !Rails.env.development?
@@ -127,7 +127,7 @@ module Plutonium
127
127
  #
128
128
  # @return [Hash] args to pass to `url_for`
129
129
  #
130
- def resource_url_args_for(*args, action: nil, parent: nil, association: nil, package: nil, interaction: nil, **kwargs)
130
+ def resource_url_args_for(*args, action: nil, parent: nil, association: nil, package: nil, interaction: nil, wizard: nil, **kwargs)
131
131
  element = args.first
132
132
 
133
133
  raise ArgumentError, "parent is required when using symbol association name" if element.is_a?(Symbol) && parent.nil?
@@ -138,6 +138,12 @@ module Plutonium
138
138
  kwargs[:interactive_action] = interaction
139
139
  end
140
140
 
141
+ if wizard
142
+ raise ArgumentError, "cannot pass both `wizard:` and `action:`" if action
143
+ action = wizard_action_type_for(element, step: kwargs[:step])
144
+ kwargs[:wizard_name] = wizard
145
+ end
146
+
141
147
  # For nested resources, use named route helpers to avoid Rails param recall ambiguity
142
148
  if parent.present?
143
149
  assoc_name = if element.is_a?(Symbol)
@@ -194,6 +200,19 @@ module Plutonium
194
200
  end
195
201
  end
196
202
 
203
+ # Determine the wizard action type for the given element. Records → member,
204
+ # classes/symbols/nil → collection (wizards have no bulk variant). With no
205
+ # `step:`, target the bare LAUNCH action (which resolves the run and redirects
206
+ # to its current step); with a `step:`, target the stepped show action.
207
+ def wizard_action_type_for(element, step: nil)
208
+ member = !(element.is_a?(Class) || element.is_a?(Symbol) || element.nil?)
209
+ if step.nil?
210
+ member ? :launch_wizard_record_action : :launch_wizard_resource_action
211
+ else
212
+ member ? :wizard_record_action : :wizard_resource_action
213
+ end
214
+ end
215
+
197
216
  def build_nested_resource_url_args(element, parent:, association_name:, route_config:, action: nil, **kwargs)
198
217
  prefix = Plutonium::Routing::NESTED_ROUTE_PREFIX
199
218
  is_singular = route_config[:route_type] == :resource
@@ -29,6 +29,7 @@ module Plutonium
29
29
  include ConfigAttr
30
30
  include InheritableConfigAttr
31
31
  include Actions
32
+ include Wizards
32
33
  include Sorting
33
34
  include Scoping
34
35
  include Search
@@ -120,6 +121,30 @@ module Plutonium
120
121
  modal_size size
121
122
  end
122
123
 
124
+ # show_in — how the :show page opens from a record link (table row,
125
+ # grid card). Unlike :new/:edit (which follow `modal_mode`), the show
126
+ # page is ALWAYS centered when shown in a modal, so this is just a
127
+ # modal/page switch — not a style.
128
+ #
129
+ # :page (default) — full-page navigation to the show route
130
+ # :modal — open the show page in a centered dialog
131
+ #
132
+ # The kanban board has its own `show_in` that overrides this per-board.
133
+ VALID_SHOW_IN = [:modal, :page].freeze
134
+
135
+ inheritable_config_attr :show_in
136
+ show_in :page
137
+
138
+ # Validated setter — raises on an unknown mode rather than silently
139
+ # falling back, matching the kanban board's `show_in`.
140
+ def self.show_in(value = :__not_set__)
141
+ return show_in_config if value == :__not_set__
142
+ unless VALID_SHOW_IN.include?(value)
143
+ raise ArgumentError, "show_in must be one of #{VALID_SHOW_IN.inspect}, got #{value.inspect}"
144
+ end
145
+ self.show_in_config = value
146
+ end
147
+
123
148
  def initialize
124
149
  super
125
150
  end
@@ -33,6 +33,59 @@ module Plutonium
33
33
  # filtering). Produced by #resolve_form_sections (a later task).
34
34
  ResolvedSection = Struct.new(:section, :fields)
35
35
 
36
+ # First-section-wins ownership, shared by every layout resolver (the resource
37
+ # form via {#resolve_form_sections}, and the wizard's {StepAdapter} /
38
+ # {FieldImporter}). Each field is claimed by the FIRST explicit section that
39
+ # lists it, restricted to the currently-permitted `fields`; a listed field
40
+ # outside that set is simply skipped (never renders, never an error).
41
+ #
42
+ # @param layout [Array<Section>]
43
+ # @param fields [Array<Symbol,String>] the permitted field set, in order
44
+ # @return [Array(Hash, Array<Symbol>)] `[owner_by_field, leftover_fields]`
45
+ def self.assign_ownership(layout, fields)
46
+ fields = fields.map(&:to_sym)
47
+ known = fields.to_set
48
+ owner = {}
49
+ layout.each do |section|
50
+ next if section.ungrouped?
51
+ section.fields.map(&:to_sym).each { |f| owner[f] ||= section.key if known.include?(f) }
52
+ end
53
+ leftovers = fields.reject { |f| owner.key?(f) }
54
+ [owner, leftovers]
55
+ end
56
+
57
+ # The canonical resolution: first-section-wins ownership, with unclaimed
58
+ # permitted fields falling into the (explicit, or a synthesized trailing)
59
+ # ungrouped bucket so nothing silently disappears. Every declared section is
60
+ # kept (empty ones are dropped at render). Shared by {#resolve_form_sections}
61
+ # and the wizard {StepAdapter}'s raw-layout path so the two never drift.
62
+ #
63
+ # @param layout [Array<Section>]
64
+ # @param fields [Array<Symbol,String>] the permitted field set, in order
65
+ # @return [Array<ResolvedSection>]
66
+ def self.resolve_sections(layout, fields)
67
+ owner, leftovers = assign_ownership(layout, fields)
68
+
69
+ resolved = layout.map do |section|
70
+ section_fields =
71
+ if section.ungrouped?
72
+ leftovers
73
+ else
74
+ section.fields.map(&:to_sym).select { |f| owner[f] == section.key }
75
+ end
76
+ ResolvedSection.new(section:, fields: section_fields)
77
+ end
78
+
79
+ unless layout.any?(&:ungrouped?)
80
+ resolved.push(ResolvedSection.new(
81
+ section: Section.new(key: UNGROUPED_KEY, fields: [].freeze, options: {}.freeze),
82
+ fields: leftovers
83
+ ))
84
+ end
85
+
86
+ resolved
87
+ end
88
+
36
89
  # Collects section/ungrouped calls from a form_layout block in order.
37
90
  class Builder
38
91
  attr_reader :sections
@@ -102,41 +155,7 @@ module Plutonium
102
155
  layout = defined_form_layout
103
156
  return nil unless layout
104
157
 
105
- resource_fields = resource_fields.map(&:to_sym)
106
- known = resource_fields.to_set
107
-
108
- # First-section-wins assignment: each field is claimed by the first
109
- # section that lists it. A field a section lists but that isn't in the
110
- # currently-permitted set (policy, per-action, entity scoping, nesting)
111
- # is simply skipped — it never renders and is never an error.
112
- owner = {}
113
- layout.each do |section|
114
- next if section.ungrouped?
115
- section.fields.each do |f|
116
- owner[f] ||= section.key if known.include?(f)
117
- end
118
- end
119
- leftovers = resource_fields.reject { |f| owner.key?(f) }
120
-
121
- resolved = layout.map do |section|
122
- fields =
123
- if section.ungrouped?
124
- leftovers
125
- else
126
- section.fields.select { |f| owner[f] == section.key }
127
- end
128
- ResolvedSection.new(section:, fields:)
129
- end
130
-
131
- unless layout.any?(&:ungrouped?)
132
- implicit = ResolvedSection.new(
133
- section: Section.new(key: UNGROUPED_KEY, fields: [].freeze, options: {}.freeze),
134
- fields: leftovers
135
- )
136
- resolved.push(implicit)
137
- end
138
-
139
- resolved
158
+ FormLayout.resolve_sections(layout, resource_fields)
140
159
  end
141
160
  end
142
161
  end