plutonium 0.64.0 → 0.65.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 (278) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +4 -1
  3. data/.claude/skills/plutonium-app/SKILL.md +14 -0
  4. data/.claude/skills/plutonium-dashboard/SKILL.md +161 -0
  5. data/.claude/skills/plutonium-resource/SKILL.md +94 -14
  6. data/.claude/skills/plutonium-ui/SKILL.md +22 -1
  7. data/CHANGELOG.md +59 -0
  8. data/CLAUDE.md +282 -210
  9. data/SECURITY.md +1 -1
  10. data/app/assets/plutonium-charts.js +20604 -0
  11. data/app/assets/plutonium-charts.js.map +7 -0
  12. data/app/assets/plutonium-charts.min.js +41 -0
  13. data/app/assets/plutonium-charts.min.js.map +7 -0
  14. data/app/assets/plutonium.css +1 -1
  15. data/app/assets/plutonium.js +794 -629
  16. data/app/assets/plutonium.js.map +4 -4
  17. data/app/assets/plutonium.min.js +48 -48
  18. data/app/assets/plutonium.min.js.map +4 -4
  19. data/app/views/plutonium/_flash_alerts.html.erb +2 -2
  20. data/app/views/plutonium/_resource_header.html.erb +2 -2
  21. data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
  22. data/app/views/plutonium/_toast.html.erb +2 -2
  23. data/app/views/rodauth/_login_form.html.erb +1 -1
  24. data/app/views/rodauth/_password_visibility.html.erb +1 -1
  25. data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  26. data/app/views/rodauth/change_login.html.erb +2 -2
  27. data/app/views/rodauth/create_account.html.erb +4 -4
  28. data/app/views/rodauth/reset_password_request.html.erb +1 -1
  29. data/app/views/rodauth/verify_account_resend.html.erb +1 -1
  30. data/app/views/rodauth/webauthn_remove.html.erb +1 -1
  31. data/config/brakeman.ignore +4 -4
  32. data/config/initializers/rabl.rb +0 -40
  33. data/config/locales/en/api_client.yml +22 -0
  34. data/config/locales/en/async.yml +34 -0
  35. data/config/locales/en/dashboard.yml +25 -0
  36. data/config/locales/en/invites.yml +37 -0
  37. data/config/locales/en/js.yml +40 -0
  38. data/config/locales/en/kanban.yml +28 -0
  39. data/config/locales/en/plutonium.yml +26 -0
  40. data/config/locales/en/positioning.yml +14 -0
  41. data/config/locales/en/profile.yml +29 -0
  42. data/config/locales/en/resource.yml +60 -0
  43. data/config/locales/en/ui.yml +93 -0
  44. data/config/locales/en/wizard.yml +46 -0
  45. data/docs/.vitepress/config.ts +34 -1
  46. data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
  47. data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
  48. data/docs/.vitepress/theme/custom.css +34 -377
  49. data/docs/blog/introducing-plutonium-dashboards.md +175 -0
  50. data/docs/blog/introducing-plutonium-i18n.md +91 -0
  51. data/docs/blog/introducing-plutonium.md +4 -5
  52. data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
  53. data/docs/guides/dashboards.md +300 -0
  54. data/docs/guides/index.md +1 -0
  55. data/docs/guides/kanban.md +2 -2
  56. data/docs/public/images/blog/dashboards-card-error.png +0 -0
  57. data/docs/public/images/blog/dashboards-loading.png +0 -0
  58. data/docs/public/images/blog/dashboards-overview.png +0 -0
  59. data/docs/public/images/blog/dashboards-sidebar.png +0 -0
  60. data/docs/public/images/blog/i18n-es-admin.png +0 -0
  61. data/docs/public/images/guides/dashboard-card-error.png +0 -0
  62. data/docs/public/images/guides/dashboard-loading.png +0 -0
  63. data/docs/public/images/guides/dashboard-overview.png +0 -0
  64. data/docs/public/images/guides/dashboard-sidebar.png +0 -0
  65. data/docs/reference/app/generators.md +2 -2
  66. data/docs/reference/app/portals.md +2 -0
  67. data/docs/reference/configuration.md +84 -27
  68. data/docs/reference/dashboard/dsl.md +123 -0
  69. data/docs/reference/dashboard/index.md +35 -0
  70. data/docs/reference/dashboard/registration.md +76 -0
  71. data/docs/reference/i18n.md +239 -0
  72. data/docs/reference/index.md +10 -1
  73. data/docs/reference/kanban/dsl.md +1 -1
  74. data/docs/reference/kanban/positioning.md +2 -2
  75. data/docs/reference/resource/actions.md +1 -1
  76. data/docs/reference/resource/definition.md +96 -18
  77. data/docs/reference/resource/export.md +5 -1
  78. data/docs/reference/resource/index.md +2 -0
  79. data/docs/reference/{positioning.md → resource/positioning.md} +2 -1
  80. data/docs/reference/resource/query.md +2 -0
  81. data/docs/reference/ui/assets.md +8 -0
  82. data/docs/reference/ui/displays.md +5 -8
  83. data/docs/reference/ui/index.md +1 -1
  84. data/docs/reference/wizard/dsl.md +2 -2
  85. data/esbuild.config.js +5 -2
  86. data/gemfiles/rails_7.gemfile.lock +2 -2
  87. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  88. data/lib/active_model/validations/array_validator.rb +1 -1
  89. data/lib/active_model/validations/attached_validator.rb +1 -1
  90. data/lib/active_model/validations/url_validator.rb +2 -2
  91. data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
  92. data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
  93. data/lib/generators/pu/core/update/update_generator.rb +3 -2
  94. data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
  95. data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
  96. data/lib/generators/pu/docker/install/install_generator.rb +58 -0
  97. data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
  98. data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
  99. data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
  100. data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
  101. data/lib/generators/pu/invites/invitable_generator.rb +18 -1
  102. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
  103. data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
  104. data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
  105. data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
  106. data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
  107. data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
  108. data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
  109. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
  110. data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
  111. data/lib/plutonium/action/base.rb +27 -3
  112. data/lib/plutonium/action/interactive.rb +17 -4
  113. data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
  114. data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
  115. data/lib/plutonium/auth/sequel_adapter.rb +1 -1
  116. data/lib/plutonium/configuration.rb +5 -0
  117. data/lib/plutonium/core/controller.rb +25 -1
  118. data/lib/plutonium/core/controllers/authorizable.rb +1 -1
  119. data/lib/plutonium/dashboard/base.rb +114 -0
  120. data/lib/plutonium/dashboard/card.rb +200 -0
  121. data/lib/plutonium/dashboard/controller.rb +101 -0
  122. data/lib/plutonium/dashboard/dsl.rb +104 -0
  123. data/lib/plutonium/dashboard/register.rb +40 -0
  124. data/lib/plutonium/dashboard/route_resolution.rb +30 -0
  125. data/lib/plutonium/dashboard.rb +42 -0
  126. data/lib/plutonium/definition/actions.rb +6 -1
  127. data/lib/plutonium/definition/base.rb +41 -3
  128. data/lib/plutonium/definition/index_views.rb +1 -1
  129. data/lib/plutonium/definition/positioning.rb +1 -1
  130. data/lib/plutonium/definition/presentable.rb +2 -2
  131. data/lib/plutonium/helpers/assets_helper.rb +15 -2
  132. data/lib/plutonium/helpers/display_helper.rb +10 -2
  133. data/lib/plutonium/interaction/async/context.rb +6 -8
  134. data/lib/plutonium/interaction/async/executor.rb +6 -7
  135. data/lib/plutonium/interaction/async/run.rb +2 -2
  136. data/lib/plutonium/interaction/async/run_definition.rb +1 -1
  137. data/lib/plutonium/interaction/base.rb +2 -0
  138. data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
  139. data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
  140. data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
  141. data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
  142. data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
  143. data/lib/plutonium/invites/controller.rb +11 -11
  144. data/lib/plutonium/kanban/column.rb +13 -3
  145. data/lib/plutonium/kanban/dsl.rb +7 -4
  146. data/lib/plutonium/models/has_cents.rb +1 -1
  147. data/lib/plutonium/profile/security_section.rb +10 -18
  148. data/lib/plutonium/query/filter.rb +10 -1
  149. data/lib/plutonium/query/filters/association.rb +27 -2
  150. data/lib/plutonium/query/filters/boolean.rb +4 -4
  151. data/lib/plutonium/query/filters/date.rb +4 -17
  152. data/lib/plutonium/query/filters/date_range.rb +2 -2
  153. data/lib/plutonium/query/filters/select.rb +10 -1
  154. data/lib/plutonium/query/filters/text.rb +9 -12
  155. data/lib/plutonium/railtie.rb +17 -0
  156. data/lib/plutonium/resource/controller.rb +10 -5
  157. data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
  158. data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
  159. data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
  160. data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
  161. data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
  162. data/lib/plutonium/resource/query_object.rb +30 -3
  163. data/lib/plutonium/routing/dashboard_registration.rb +137 -0
  164. data/lib/plutonium/routing/resource_registration.rb +5 -0
  165. data/lib/plutonium/routing/route_set_extensions.rb +2 -1
  166. data/lib/plutonium/translation.rb +227 -0
  167. data/lib/plutonium/ui/actions_dropdown.rb +2 -2
  168. data/lib/plutonium/ui/breadcrumbs.rb +3 -3
  169. data/lib/plutonium/ui/color_mode_selector.rb +1 -1
  170. data/lib/plutonium/ui/component/behaviour.rb +46 -0
  171. data/lib/plutonium/ui/component/methods.rb +4 -0
  172. data/lib/plutonium/ui/dashboard/board.rb +80 -0
  173. data/lib/plutonium/ui/dashboard/card.rb +127 -0
  174. data/lib/plutonium/ui/dashboard/chart.rb +43 -0
  175. data/lib/plutonium/ui/dashboard/custom.rb +32 -0
  176. data/lib/plutonium/ui/dashboard/frame.rb +25 -0
  177. data/lib/plutonium/ui/dashboard/metric.rb +137 -0
  178. data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
  179. data/lib/plutonium/ui/display/components/attachment.rb +1 -1
  180. data/lib/plutonium/ui/display/components/badge.rb +7 -1
  181. data/lib/plutonium/ui/display/components/boolean.rb +2 -2
  182. data/lib/plutonium/ui/display/resource.rb +23 -16
  183. data/lib/plutonium/ui/export_button.rb +3 -3
  184. data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
  185. data/lib/plutonium/ui/form/components/password.rb +2 -2
  186. data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
  187. data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
  188. data/lib/plutonium/ui/form/components/uppy.rb +11 -3
  189. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
  190. data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
  191. data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
  192. data/lib/plutonium/ui/form/query.rb +8 -8
  193. data/lib/plutonium/ui/form/resource.rb +54 -26
  194. data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
  195. data/lib/plutonium/ui/grid/card.rb +3 -3
  196. data/lib/plutonium/ui/grid/resource.rb +3 -3
  197. data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
  198. data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
  199. data/lib/plutonium/ui/kanban/column.rb +6 -6
  200. data/lib/plutonium/ui/layout/base.rb +19 -1
  201. data/lib/plutonium/ui/layout/header.rb +1 -1
  202. data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
  203. data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
  204. data/lib/plutonium/ui/layout/sidebar.rb +1 -1
  205. data/lib/plutonium/ui/layout/topbar.rb +2 -2
  206. data/lib/plutonium/ui/modal/base.rb +4 -4
  207. data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
  208. data/lib/plutonium/ui/nav_user.rb +2 -2
  209. data/lib/plutonium/ui/page/base.rb +10 -2
  210. data/lib/plutonium/ui/page/dashboard.rb +54 -0
  211. data/lib/plutonium/ui/page/edit.rb +3 -3
  212. data/lib/plutonium/ui/page/interactive_action.rb +2 -2
  213. data/lib/plutonium/ui/page/new.rb +3 -3
  214. data/lib/plutonium/ui/page/show.rb +2 -2
  215. data/lib/plutonium/ui/page/wizard.rb +22 -16
  216. data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
  217. data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
  218. data/lib/plutonium/ui/skeleton_table.rb +1 -1
  219. data/lib/plutonium/ui/table/base.rb +2 -2
  220. data/lib/plutonium/ui/table/components/attachment.rb +1 -1
  221. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
  222. data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
  223. data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
  224. data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
  225. data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
  226. data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
  227. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
  228. data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
  229. data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
  230. data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
  231. data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
  232. data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
  233. data/lib/plutonium/ui/table/resource.rb +53 -17
  234. data/lib/plutonium/ui/wizard/review.rb +7 -7
  235. data/lib/plutonium/ui/wizard/stepper.rb +1 -1
  236. data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
  237. data/lib/plutonium/version.rb +1 -1
  238. data/lib/plutonium/wizard/base.rb +2 -0
  239. data/lib/plutonium/wizard/driving.rb +8 -2
  240. data/lib/plutonium/wizard/dsl.rb +4 -2
  241. data/lib/plutonium/wizard/field_importer.rb +1 -8
  242. data/lib/plutonium/wizard/review_step.rb +8 -1
  243. data/lib/plutonium/wizard/step.rb +14 -3
  244. data/package.json +4 -2
  245. data/plutonium.gemspec +4 -1
  246. data/postcss-gem-import.cjs +34 -0
  247. data/postcss-gem-import.js +3 -28
  248. data/src/css/components.css +91 -0
  249. data/src/css/slim_select.css +0 -38
  250. data/src/css/tokens.css +24 -0
  251. data/src/js/controllers/attachment_input_controller.js +15 -6
  252. data/src/js/controllers/chart_controller.js +162 -0
  253. data/src/js/controllers/clipboard_controller.js +3 -2
  254. data/src/js/controllers/dirty_form_guard_controller.js +4 -3
  255. data/src/js/controllers/easymde_controller.js +3 -0
  256. data/src/js/controllers/filter_panel_controller.js +15 -6
  257. data/src/js/controllers/flatpickr_controller.js +8 -0
  258. data/src/js/controllers/frame_refresh_controller.js +59 -0
  259. data/src/js/controllers/intl_tel_input_controller.js +5 -0
  260. data/src/js/controllers/register_controllers.js +4 -0
  261. data/src/js/controllers/slim_select_controller.js +79 -176
  262. data/src/js/core.js +9 -1
  263. data/src/js/i18n.js +69 -0
  264. data/src/js/plutonium-charts.js +15 -0
  265. data/src/js/turbo/turbo_confirm.js +5 -3
  266. metadata +64 -16
  267. data/.yarnrc.yml +0 -8
  268. data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
  269. data/lib/generators/pu/field/input/input_generator.rb +0 -32
  270. data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
  271. data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
  272. data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
  273. data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
  274. data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
  275. data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
  276. data/public/plutonium-assets/application.js +0 -31419
  277. data/public/plutonium-assets/plutonium.ico +0 -0
  278. data/yarn.lock +0 -6858
@@ -0,0 +1,93 @@
1
+ # Fixed strings rendered by the components under lib/plutonium/ui.
2
+ en:
3
+ plutonium:
4
+ ui:
5
+ loading: "Loading..."
6
+ details: "Details"
7
+ home: "Home"
8
+ actions_dropdown:
9
+ actions: "Actions"
10
+ open_menu: "Open actions menu"
11
+ breadcrumbs:
12
+ label: "Breadcrumb"
13
+ home: "Home"
14
+ show_collapsed: "Show collapsed breadcrumbs"
15
+ color_mode:
16
+ toggle: "Toggle color mode"
17
+ empty_state:
18
+ none_available: "No %{resources} available"
19
+ export:
20
+ export: "Export"
21
+ export_all: "Export all"
22
+ more_options: "More export options"
23
+ frame_navigator:
24
+ back: "Back"
25
+ refresh: "Refresh"
26
+ maximize: "Maximize"
27
+ grid:
28
+ open: "Open"
29
+ open_record: "Open %{name}"
30
+ layout:
31
+ sidebar_navigation: "Sidebar Navigation"
32
+ toggle_sidebar: "Toggle sidebar"
33
+ toggle_sidebar_pin: "Toggle sidebar pin"
34
+ modal:
35
+ dialog: "Dialog"
36
+ open_full_page: "Open full page"
37
+ open_full_page_new_tab: "Open full page in a new tab"
38
+ close: "Close dialog"
39
+ nav_user:
40
+ open_menu: "Open user menu"
41
+ avatar_alt: "avatar"
42
+ nav_grid_menu:
43
+ view: "View %{label}"
44
+ page:
45
+ new_title: "New %{resource}"
46
+ edit_title: "Edit %{resource}"
47
+ table:
48
+ column_options: "Column options"
49
+ clear_sort: "Clear sort"
50
+ filters: "Filters"
51
+ filter: "Filter"
52
+ clear: "Clear"
53
+ clear_all: "Clear all"
54
+ apply: "Apply"
55
+ apply_filters: "Apply Filters"
56
+ close_filters: "Close filters"
57
+ search_placeholder: "Search..."
58
+ all: "All"
59
+ scope: "Scope"
60
+ view: "View"
61
+ views:
62
+ table: "Table"
63
+ grid: "Grid"
64
+ kanban: "Board"
65
+ remove_filter: "Remove %{filter} filter"
66
+ result_count:
67
+ one: "%{count} result"
68
+ other: "%{count} results"
69
+ selected_count: "%{count} selected"
70
+ clear_selection: "Clear selection"
71
+ no_bulk_actions: "No bulk actions available"
72
+ more_actions: "More actions"
73
+ form:
74
+ discard_title: "Discard changes?"
75
+ discard_description: "You have unsaved changes. Closing this form now will lose them."
76
+ keep_editing: "Keep editing"
77
+ discard_changes: "Discard changes"
78
+ create_and_add_another: "Create and add another"
79
+ update_and_continue_editing: "Update and continue editing"
80
+ repeater:
81
+ add: "Add %{name}"
82
+ remove: "Remove"
83
+ removed: "Removed"
84
+ restore: "Restore"
85
+ key_value_store:
86
+ key_placeholder: "Key"
87
+ value_placeholder: "Value"
88
+ add_pair: "+ Add Pair"
89
+ password:
90
+ keep_current_hint: "Leave blank to keep the current value."
91
+ reenter_hint: "Re-enter the new value to save it."
92
+ attachment:
93
+ delete: "Delete"
@@ -0,0 +1,46 @@
1
+ # Plutonium wizard strings: the step page, the review body, the stepper, the
2
+ # resume-or-new chooser, the one-time "already completed" page and the driving
3
+ # concern's flash / error text. Loaded at the lowest precedence, so a package,
4
+ # portal or the host application can override any key.
5
+ en:
6
+ plutonium:
7
+ wizard:
8
+ cancel: "Cancel"
9
+ authorization_denied: "Wizard authorization denied"
10
+ run_not_found: "wizard run not found"
11
+ page:
12
+ title: "%{wizard} — %{step}"
13
+ fallback_title: "Wizard"
14
+ step_position: "Step %{position} of %{total}"
15
+ review_prompt: "Check everything over before you finish."
16
+ nav:
17
+ back: "Back"
18
+ finish: "Finish"
19
+ next: "Next"
20
+ save_and_continue: "Save & continue"
21
+ save_and_review: "Save & review"
22
+ unsaved_back: "You have unsaved changes on this step. Go back and lose them?"
23
+ unsaved_cancel: "You have unsaved changes. Cancel the wizard and lose them?"
24
+ stepper:
25
+ progress: "Progress"
26
+ review:
27
+ label: "Review"
28
+ outstanding: "Some steps still need attention before you can finish:"
29
+ fix_step: "Fix %{step}"
30
+ edit: "Edit"
31
+ none: "None"
32
+ row_label: "%{label}: "
33
+ ready_title: "You're all set"
34
+ ready_message: "Everything looks good. Click Finish to complete."
35
+ chooser:
36
+ prompt: "Pick up where you left off, or start a new one."
37
+ in_progress: "In progress"
38
+ updated_ago: "Updated %{time} ago"
39
+ a_while: "a while"
40
+ resume: "Resume"
41
+ cancel_draft: "Cancel this %{wizard} draft"
42
+ discard_confirm: "Discard this in-progress %{wizard}? This can't be undone."
43
+ start_new: "Start new"
44
+ completed:
45
+ message: "You've already completed this — there's nothing more to do here."
46
+ continue: "Continue"
@@ -45,6 +45,28 @@ export default defineConfig(withMermaid({
45
45
  : []),
46
46
  ],
47
47
  ignoreDeadLinks: 'localhostLinks',
48
+ // A blog post shared as a link is its own thing, not the homepage. Give each
49
+ // one an og card built from the frontmatter it already declares; every other
50
+ // page keeps the site-wide card from `head` above.
51
+ //
52
+ // `mergeHead` keys on the FIRST attribute of each meta tag, so `property` and
53
+ // `name` have to lead here exactly as they do in `head`, or these append
54
+ // alongside the site tags instead of replacing them.
55
+ transformHead({ pageData }) {
56
+ const { relativePath, frontmatter } = pageData
57
+ // Same date test the loader and the feed use to tell a post from the index.
58
+ if (!relativePath.startsWith("blog/") || !frontmatter.date) return []
59
+
60
+ const url = `${hostname}/${relativePath.replace(/\.md$/, "")}`
61
+ return [
62
+ ["meta", { property: "og:type", content: "article" }],
63
+ ["meta", { property: "og:title", content: frontmatter.title }],
64
+ ["meta", { property: "og:description", content: frontmatter.description }],
65
+ ["meta", { property: "og:url", content: url }],
66
+ ["meta", { name: "twitter:title", content: frontmatter.title }],
67
+ ["meta", { name: "twitter:description", content: frontmatter.description }],
68
+ ] as HeadConfig[]
69
+ },
48
70
  srcExclude: ['superpowers/**'],
49
71
  vite: {
50
72
  plugins: [
@@ -136,6 +158,7 @@ export default defineConfig(withMermaid({
136
158
  { text: "User Invites", link: "/guides/user-invites" },
137
159
  { text: "Wizards", link: "/guides/wizards" },
138
160
  { text: "Kanban Boards", link: "/guides/kanban" },
161
+ { text: "Dashboards", link: "/guides/dashboards" },
139
162
  ]
140
163
  },
141
164
  {
@@ -159,6 +182,7 @@ export default defineConfig(withMermaid({
159
182
  items: [
160
183
  { text: "Overview", link: "/reference/" },
161
184
  { text: "Configuration", link: "/reference/configuration" },
185
+ { text: "Internationalization", link: "/reference/i18n" },
162
186
  ]
163
187
  },
164
188
  {
@@ -181,7 +205,7 @@ export default defineConfig(withMermaid({
181
205
  { text: "Definition", link: "/reference/resource/definition" },
182
206
  { text: "Query", link: "/reference/resource/query" },
183
207
  { text: "Actions", link: "/reference/resource/actions" },
184
- { text: "Positioning & drag-to-reorder", link: "/reference/positioning" },
208
+ { text: "Positioning & drag-to-reorder", link: "/reference/resource/positioning" },
185
209
  { text: "CSV Export", link: "/reference/resource/export" },
186
210
  ]
187
211
  },
@@ -251,6 +275,15 @@ export default defineConfig(withMermaid({
251
275
  { text: "Authorization", link: "/reference/kanban/authorization" },
252
276
  ]
253
277
  },
278
+ {
279
+ text: "Dashboard",
280
+ collapsed: false,
281
+ items: [
282
+ { text: "Overview", link: "/reference/dashboard/" },
283
+ { text: "DSL", link: "/reference/dashboard/dsl" },
284
+ { text: "Registration", link: "/reference/dashboard/registration" },
285
+ ]
286
+ },
254
287
  {
255
288
  text: "Testing",
256
289
  collapsed: false,
@@ -11,7 +11,7 @@
11
11
  </p>
12
12
  <p class="home-hero-pillars">
13
13
  <!-- keep the bolds on one source line: Vue drops whitespace-only nodes containing newlines -->
14
- <b>CRUD.</b> <b>Auth.</b> <b>Authorization.</b> <b>Multi-tenancy.</b> <b>Wizards.</b> <b>Kanban.</b> <b>Admin portals.</b> <b>Search, filters, bulk actions.</b>
14
+ <b>CRUD.</b> <b>Auth.</b> <b>Authorization.</b> <b>Multi-tenancy.</b> <b>Wizards.</b> <b>Kanban.</b> <b>Dashboards.</b> <b>Admin portals.</b> <b>Search, filters, bulk actions.</b>
15
15
  All generated. All customizable. All Rails.
16
16
  </p>
17
17
  <div class="home-hero-ctas">
@@ -38,6 +38,12 @@ const cats = [
38
38
  { name: "Interactions", desc: "Business logic with auto-generated UI",
39
39
  link: "/plutonium-core/guides/custom-actions" },
40
40
  ]},
41
+ { name: "Operations", items: [
42
+ { name: "Dashboards", desc: "Metric, chart and custom cards, each lazy-loaded",
43
+ link: "/plutonium-core/guides/dashboards" },
44
+ { name: "Async runs", desc: "Bulk work with a live progress page",
45
+ link: "/plutonium-core/reference/behavior/async-interactions" },
46
+ ]},
41
47
  { name: "App structure", items: [
42
48
  { name: "Portals", desc: "Themed, mountable engines",
43
49
  link: "/plutonium-core/reference/app/portals" },
@@ -54,6 +60,14 @@ const cats = [
54
60
  { name: "Invites & memberships", desc: "Token lifecycle, mailers, onboarding",
55
61
  link: "/plutonium-core/guides/user-invites" },
56
62
  ]},
63
+ { name: "Localization", items: [
64
+ { name: "Internationalization", desc: "Every UI string from a locale file",
65
+ link: "/plutonium-core/reference/i18n" },
66
+ { name: "Locale switching", desc: "Per-portal, the standard Rails way",
67
+ link: "/plutonium-core/reference/i18n#switching-locale" },
68
+ { name: "Translated pagination", desc: "Pagy locales, ~35 languages",
69
+ link: "/plutonium-core/reference/i18n#where-the-strings-live" },
70
+ ]},
57
71
  ]
58
72
  </script>
59
73
 
@@ -36,383 +36,6 @@ Brand Colors:
36
36
  --vp-c-accent-soft: rgba(64, 224, 227, 0.16);
37
37
  }
38
38
 
39
- /* ============================================
40
- Landing Page Styles
41
- ============================================ */
42
-
43
- .landing-content {
44
- max-width: 1152px;
45
- margin: 0 auto;
46
- padding: 0 24px;
47
- }
48
-
49
- .landing-content section {
50
- padding: 80px 0;
51
- }
52
-
53
- .landing-content h2 {
54
- font-size: 2.25rem;
55
- font-weight: 700;
56
- text-align: center;
57
- margin-bottom: 48px;
58
- background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-accent-1));
59
- -webkit-background-clip: text;
60
- -webkit-text-fill-color: transparent;
61
- background-clip: text;
62
- }
63
-
64
- /* Before/After Comparison */
65
- .before-after {
66
- border-bottom: 1px solid var(--vp-c-divider);
67
- }
68
-
69
- .comparison {
70
- display: flex;
71
- align-items: center;
72
- justify-content: center;
73
- gap: 32px;
74
- flex-wrap: wrap;
75
- }
76
-
77
- .before, .after {
78
- flex: 1;
79
- min-width: 300px;
80
- max-width: 450px;
81
- padding: 24px;
82
- border-radius: 12px;
83
- background: var(--vp-c-bg-soft);
84
- }
85
-
86
- .before h3, .after h3 {
87
- font-size: 1.1rem;
88
- font-weight: 600;
89
- margin-bottom: 16px;
90
- color: var(--vp-c-text-1);
91
- }
92
-
93
- .file-tree {
94
- font-family: var(--vp-font-family-mono);
95
- font-size: 0.85rem;
96
- line-height: 1.8;
97
- }
98
-
99
- .file {
100
- color: var(--vp-c-text-2);
101
- padding: 2px 0;
102
- }
103
-
104
- .file.dim {
105
- color: var(--vp-c-text-3);
106
- font-style: italic;
107
- }
108
-
109
- .stats {
110
- display: flex;
111
- gap: 16px;
112
- margin-top: 16px;
113
- padding-top: 16px;
114
- border-top: 1px solid var(--vp-c-divider);
115
- }
116
-
117
- .stats span {
118
- font-size: 0.85rem;
119
- color: var(--vp-c-text-3);
120
- padding: 4px 12px;
121
- background: var(--vp-c-bg-mute);
122
- border-radius: 20px;
123
- }
124
-
125
- .stats.success span {
126
- color: var(--vp-c-brand-1);
127
- background: var(--vp-c-brand-soft);
128
- font-weight: 500;
129
- }
130
-
131
- .arrow {
132
- font-size: 2rem;
133
- color: var(--vp-c-brand-1);
134
- font-weight: bold;
135
- }
136
-
137
- @media (max-width: 768px) {
138
- .arrow {
139
- transform: rotate(90deg);
140
- }
141
- }
142
-
143
- /* Feature Rows */
144
- .features-detailed {
145
- border-bottom: 1px solid var(--vp-c-divider);
146
- }
147
-
148
- .feature-row {
149
- display: flex;
150
- align-items: center;
151
- gap: 48px;
152
- margin-bottom: 64px;
153
- }
154
-
155
- .feature-row:last-child {
156
- margin-bottom: 0;
157
- }
158
-
159
- .feature-row.reverse {
160
- flex-direction: row-reverse;
161
- }
162
-
163
- .feature-text {
164
- flex: 1;
165
- min-width: 280px;
166
- }
167
-
168
- .feature-text h3 {
169
- font-size: 1.5rem;
170
- font-weight: 600;
171
- margin-bottom: 12px;
172
- color: var(--vp-c-text-1);
173
- }
174
-
175
- .feature-text p {
176
- font-size: 1.05rem;
177
- color: var(--vp-c-text-2);
178
- line-height: 1.7;
179
- }
180
-
181
- .feature-text code {
182
- font-size: 0.9rem;
183
- padding: 2px 6px;
184
- background: var(--vp-c-bg-soft);
185
- border-radius: 4px;
186
- }
187
-
188
- .feature-code {
189
- flex: 1;
190
- min-width: 320px;
191
- }
192
-
193
- .feature-code div[class*="language-"] {
194
- margin: 0 !important;
195
- border-radius: 12px;
196
- }
197
-
198
- @media (max-width: 900px) {
199
- .feature-row,
200
- .feature-row.reverse {
201
- flex-direction: column;
202
- }
203
-
204
- .feature-text {
205
- text-align: center;
206
- }
207
-
208
- .feature-code {
209
- width: 100%;
210
- }
211
- }
212
-
213
- /* Feature Grid */
214
- .feature-grid {
215
- display: grid;
216
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
217
- gap: 24px;
218
- border-bottom: 1px solid var(--vp-c-divider);
219
- }
220
-
221
- .grid-item {
222
- padding: 32px 24px;
223
- background: var(--vp-c-bg-soft);
224
- border-radius: 12px;
225
- text-align: center;
226
- transition: transform 0.2s, box-shadow 0.2s;
227
- }
228
-
229
- .grid-item:hover {
230
- transform: translateY(-4px);
231
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
232
- }
233
-
234
- .grid-item .icon {
235
- font-size: 2.5rem;
236
- margin-bottom: 16px;
237
- }
238
-
239
- .grid-item h3 {
240
- font-size: 1.15rem;
241
- font-weight: 600;
242
- margin-bottom: 8px;
243
- color: var(--vp-c-text-1);
244
- }
245
-
246
- .grid-item p {
247
- font-size: 0.95rem;
248
- color: var(--vp-c-text-2);
249
- line-height: 1.6;
250
- }
251
-
252
- /* CTA Section */
253
- .cta-section {
254
- text-align: center;
255
- padding-top: 80px !important;
256
- padding-bottom: 120px !important;
257
- }
258
-
259
- .cta-section h2 {
260
- margin-bottom: 16px;
261
- }
262
-
263
- .cta-section > p {
264
- font-size: 1.15rem;
265
- color: var(--vp-c-text-2);
266
- margin-bottom: 32px;
267
- }
268
-
269
- .cta-buttons {
270
- display: flex;
271
- justify-content: center;
272
- gap: 16px;
273
- flex-wrap: wrap;
274
- }
275
-
276
- .cta-primary,
277
- .cta-secondary {
278
- display: inline-block;
279
- padding: 14px 32px;
280
- font-size: 1rem;
281
- font-weight: 600;
282
- border-radius: 8px;
283
- text-decoration: none;
284
- transition: all 0.2s;
285
- }
286
-
287
- .cta-primary {
288
- background: linear-gradient(135deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
289
- color: #ffffff !important;
290
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
291
- }
292
-
293
- .cta-primary:hover {
294
- transform: translateY(-2px);
295
- box-shadow: 0 8px 20px rgba(255, 77, 77, 0.3);
296
- }
297
-
298
- .cta-secondary {
299
- background: var(--vp-c-bg-soft);
300
- color: var(--vp-c-text-1);
301
- border: 1px solid var(--vp-c-divider);
302
- }
303
-
304
- .cta-secondary:hover {
305
- background: var(--vp-c-bg-mute);
306
- border-color: var(--vp-c-brand-1);
307
- }
308
-
309
- /* After section - code block styling */
310
- .after div[class*="language-"] {
311
- margin: 0 0 16px 0 !important;
312
- border-radius: 8px;
313
- }
314
-
315
- /* AI Section */
316
- .ai-section {
317
- border-bottom: 1px solid var(--vp-c-divider);
318
- background: linear-gradient(180deg, var(--vp-c-bg) 0%, var(--vp-c-bg-soft) 100%);
319
- margin: 0 -24px;
320
- padding-left: 24px;
321
- padding-right: 24px;
322
- display: flex;
323
- flex-direction: column;
324
- align-items: center;
325
- }
326
-
327
- .ai-intro {
328
- text-align: center;
329
- font-size: 1.2rem;
330
- color: var(--vp-c-text-2);
331
- max-width: 700px;
332
- margin: -24px auto 48px;
333
- line-height: 1.7;
334
- }
335
-
336
- .ai-features {
337
- display: grid;
338
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
339
- gap: 32px;
340
- margin-bottom: 48px;
341
- width: 100%;
342
- max-width: 1100px;
343
- }
344
-
345
- .ai-feature {
346
- text-align: center;
347
- padding: 24px;
348
- }
349
-
350
- .ai-icon {
351
- font-size: 3rem;
352
- margin-bottom: 16px;
353
- }
354
-
355
- .ai-feature h3 {
356
- font-size: 1.25rem;
357
- font-weight: 600;
358
- margin-bottom: 12px;
359
- color: var(--vp-c-text-1);
360
- }
361
-
362
- .ai-feature p {
363
- font-size: 1rem;
364
- color: var(--vp-c-text-2);
365
- line-height: 1.6;
366
- }
367
-
368
- .ai-example {
369
- max-width: 700px;
370
- margin: 0 auto;
371
- background: var(--vp-c-bg);
372
- border-radius: 12px;
373
- overflow: hidden;
374
- border: 1px solid var(--vp-c-divider);
375
- }
376
-
377
- .ai-prompt,
378
- .ai-result {
379
- padding: 24px;
380
- }
381
-
382
- .ai-prompt {
383
- border-bottom: 1px solid var(--vp-c-divider);
384
- }
385
-
386
- .prompt-label,
387
- .result-label {
388
- display: block;
389
- font-size: 0.8rem;
390
- font-weight: 600;
391
- text-transform: uppercase;
392
- letter-spacing: 0.05em;
393
- margin-bottom: 8px;
394
- }
395
-
396
- .prompt-label {
397
- color: var(--vp-c-brand-1);
398
- }
399
-
400
- .result-label {
401
- color: var(--vp-c-accent-1);
402
- }
403
-
404
- .ai-prompt p,
405
- .ai-result p {
406
- font-size: 1.05rem;
407
- color: var(--vp-c-text-1);
408
- margin: 0;
409
- line-height: 1.6;
410
- }
411
-
412
- .ai-prompt p {
413
- font-style: italic;
414
- }
415
-
416
39
  /* Fix VitePress doc h2 text cutoff at bottom */
417
40
  .vp-doc h2 {
418
41
  line-height: 1.5;
@@ -562,3 +185,37 @@ img.pu-zoomable { cursor: zoom-in; }
562
185
  }
563
186
  .pu-zoom-close:hover { background: var(--vp-c-bg-mute); transform: scale(1.05); }
564
187
  .pu-zoom-close:focus-visible { outline: 2px solid var(--vp-c-brand-1); outline-offset: 2px; }
188
+
189
+ /* ===== Nav bar fits before it unfolds ===== */
190
+
191
+ /* VitePress swaps the hamburger for the full nav menu at exactly 768px. This
192
+ site's nav is seven items wide (~673px), which together with the logo, search
193
+ and appearance toggle needs about 1000px — so from 768 to 999 the bar did not
194
+ fit and the PAGE scrolled horizontally, squeezing the content column and
195
+ everything in it. Keep the hamburger until the menu actually fits.
196
+
197
+ These declarations reproduce the state VitePress itself renders below 768px,
198
+ so the hamburger opens the same nav screen it always did.
199
+
200
+ `!important` is load-bearing: the theme's own rule is `.VPNavBarMenu[data-v-…]`,
201
+ a scoped selector that outranks a plain class whatever the source order. The
202
+ `data-v` hash is regenerated per VitePress build, so matching its specificity
203
+ by copying it would break on the next upgrade. */
204
+ @media (min-width: 768px) and (max-width: 999px) {
205
+ .VPNavBarMenu { display: none !important; }
206
+ .VPNavBarHamburger { display: flex !important; }
207
+ .VPNavBarExtra { display: none !important; }
208
+ .VPNavScreen { display: block !important; }
209
+ }
210
+
211
+ /* ===== Tables scroll instead of crushing ===== */
212
+
213
+ /* `.vp-doc table` is `display: block; overflow-x: auto`, so a table CAN scroll —
214
+ but only once its content is wider than the container. A table whose cells are
215
+ all prose has no unbreakable token to force that, so instead of scrolling it
216
+ wraps down to whatever it is given: on a phone that meant columns of 59-89px,
217
+ a few characters per line. Giving each cell a floor makes the table exceed the
218
+ container, which is what turns the existing overflow-x into an actual scroll. */
219
+ @media (max-width: 640px) {
220
+ .vp-doc :is(th, td) { min-width: 7rem; }
221
+ }