plutonium 0.60.5 → 0.62.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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +19 -1
- data/.claude/skills/plutonium-app/SKILL.md +41 -0
- data/.claude/skills/plutonium-auth/SKILL.md +40 -0
- data/.claude/skills/plutonium-behavior/SKILL.md +47 -1
- data/.claude/skills/plutonium-kanban/SKILL.md +378 -0
- data/.claude/skills/plutonium-resource/SKILL.md +40 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +43 -0
- data/.claude/skills/plutonium-testing/SKILL.md +38 -0
- data/.claude/skills/plutonium-ui/SKILL.md +51 -0
- data/.claude/skills/plutonium-wizard/SKILL.md +469 -0
- data/.cliff.toml +6 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +576 -439
- data/CLAUDE.md +15 -7
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +1173 -194
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +53 -53
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/layouts/basic.html.erb +7 -0
- data/app/views/plutonium/_flash_toasts.html.erb +2 -46
- data/app/views/plutonium/_toast.html.erb +52 -0
- data/app/views/resource/_kanban_move_action_form.html.erb +1 -0
- data/app/views/resource/_resource_kanban.html.erb +1 -0
- data/app/views/resource/kanban_move_form.html.erb +1 -0
- data/config/brakeman.ignore +2 -2
- data/db/migrate/wizard/20260615000001_create_plutonium_wizard_sessions.rb +57 -0
- data/docs/.vitepress/config.ts +45 -1
- data/docs/.vitepress/sync-skills.mjs +45 -0
- data/docs/ai.md +99 -0
- data/docs/guides/index.md +2 -0
- data/docs/guides/kanban.md +557 -0
- data/docs/guides/wizards.md +447 -0
- data/docs/public/images/guides/kanban-after-move.png +0 -0
- data/docs/public/images/guides/kanban-board-light.png +0 -0
- data/docs/public/images/guides/kanban-board.png +0 -0
- data/docs/public/images/guides/kanban-show-centered-modal.png +0 -0
- data/docs/public/images/guides/kanban-wip-toast.png +0 -0
- data/docs/public/images/guides/wizards-chooser.png +0 -0
- data/docs/public/images/guides/wizards-completed.png +0 -0
- data/docs/public/images/guides/wizards-index-action.png +0 -0
- data/docs/public/images/guides/wizards-repeater.png +0 -0
- data/docs/public/images/guides/wizards-review.png +0 -0
- data/docs/public/images/guides/wizards-step.png +0 -0
- data/docs/reference/behavior/policies.md +1 -1
- data/docs/reference/index.md +14 -0
- data/docs/reference/kanban/authorization.md +82 -0
- data/docs/reference/kanban/dsl.md +334 -0
- data/docs/reference/kanban/index.md +40 -0
- data/docs/reference/kanban/positioning.md +162 -0
- data/docs/reference/resource/definition.md +26 -1
- data/docs/reference/resource/model.md +26 -0
- data/docs/reference/ui/forms.md +77 -0
- data/docs/reference/ui/pages.md +2 -0
- data/docs/reference/wizard/anchoring-resume.md +194 -0
- data/docs/reference/wizard/dsl.md +337 -0
- data/docs/reference/wizard/index.md +33 -0
- data/docs/reference/wizard/one-time.md +129 -0
- data/docs/reference/wizard/registration-launch.md +177 -0
- data/docs/reference/wizard/storage-config.md +151 -0
- data/docs/superpowers/plans/2026-06-14-form-sectioning.md +2 -2
- data/docs/superpowers/plans/2026-06-15-wizard-dsl.md +1619 -0
- data/docs/superpowers/plans/2026-06-15-wizard-dsl.md.tasks.json +68 -0
- data/docs/superpowers/plans/2026-06-26-kanban-dsl.md +1128 -0
- data/docs/superpowers/plans/2026-06-26-kanban-dsl.md.tasks.json +24 -0
- data/docs/superpowers/plans/2026-07-02-kanban-drop-interactions.md +714 -0
- data/docs/superpowers/plans/2026-07-02-kanban-drop-interactions.md.tasks.json +68 -0
- data/docs/superpowers/specs/2026-06-15-wizard-dsl-design.md +836 -0
- data/docs/superpowers/specs/2026-06-15-wizard-dsl-examples.rb +245 -0
- data/docs/superpowers/specs/2026-06-17-wizard-relaunch-prompt-design.md +86 -0
- data/docs/superpowers/specs/2026-06-18-wizard-attachments-design.md +101 -0
- data/docs/superpowers/specs/2026-06-18-wizard-hosting-design.md +220 -0
- data/docs/superpowers/specs/2026-06-26-kanban-dsl-design.md +388 -0
- data/docs/superpowers/specs/2026-07-03-kanban-auth-simplification.md +159 -0
- data/gemfiles/postgres.gemfile +8 -0
- data/gemfiles/postgres.gemfile.lock +321 -0
- data/gemfiles/rails_7.gemfile +1 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile +1 -0
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile +1 -0
- data/gemfiles/rails_8.1.gemfile.lock +14 -1
- data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +5 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +6 -1
- data/lib/plutonium/action/base.rb +17 -0
- data/lib/plutonium/auth/rodauth.rb +1 -2
- data/lib/plutonium/configuration.rb +16 -0
- data/lib/plutonium/core/controller.rb +20 -1
- data/lib/plutonium/definition/base.rb +25 -0
- data/lib/plutonium/definition/form_layout.rb +54 -35
- data/lib/plutonium/definition/index_views.rb +70 -1
- data/lib/plutonium/definition/wizards.rb +209 -0
- data/lib/plutonium/invites/concerns/invite_token.rb +9 -0
- data/lib/plutonium/invites/concerns/invite_user.rb +9 -0
- data/lib/plutonium/invites/controller.rb +4 -1
- data/lib/plutonium/kanban/action.rb +7 -0
- data/lib/plutonium/kanban/board.rb +40 -0
- data/lib/plutonium/kanban/broadcaster.rb +54 -0
- data/lib/plutonium/kanban/column.rb +122 -0
- data/lib/plutonium/kanban/context.rb +15 -0
- data/lib/plutonium/kanban/dsl.rb +71 -0
- data/lib/plutonium/kanban/grouping.rb +51 -0
- data/lib/plutonium/kanban/positioning.rb +75 -0
- data/lib/plutonium/kanban.rb +11 -0
- data/lib/plutonium/migrations.rb +40 -0
- data/lib/plutonium/models/has_cents.rb +30 -2
- data/lib/plutonium/positioning.rb +146 -0
- data/lib/plutonium/railtie.rb +33 -0
- data/lib/plutonium/resource/controller.rb +24 -1
- data/lib/plutonium/resource/controllers/crud_actions.rb +9 -1
- data/lib/plutonium/resource/controllers/kanban_actions.rb +851 -0
- data/lib/plutonium/resource/controllers/wizard_actions.rb +165 -0
- data/lib/plutonium/resource/policy.rb +14 -0
- data/lib/plutonium/routing/mapper_extensions.rb +45 -0
- data/lib/plutonium/routing/wizard_registration.rb +289 -0
- data/lib/plutonium/ui/display/components/currency.rb +41 -9
- data/lib/plutonium/ui/display/options/inferred_types.rb +2 -5
- data/lib/plutonium/ui/display/resource.rb +17 -12
- data/lib/plutonium/ui/form/base.rb +25 -5
- data/lib/plutonium/ui/form/components/currency.rb +64 -0
- data/lib/plutonium/ui/form/components/intl_tel_input.rb +27 -1
- data/lib/plutonium/ui/form/components/password.rb +126 -0
- data/lib/plutonium/ui/form/components/uppy.rb +26 -5
- data/lib/plutonium/ui/form/kanban_move.rb +46 -0
- data/lib/plutonium/ui/form/options/inferred_types.rb +26 -0
- data/lib/plutonium/ui/form/resource.rb +13 -1
- data/lib/plutonium/ui/form/theme.rb +7 -0
- data/lib/plutonium/ui/form/wizard.rb +63 -0
- data/lib/plutonium/ui/grid/card.rb +56 -18
- data/lib/plutonium/ui/kanban/card.rb +67 -0
- data/lib/plutonium/ui/kanban/color_dot.rb +36 -0
- data/lib/plutonium/ui/kanban/column.rb +411 -0
- data/lib/plutonium/ui/kanban/resource.rb +319 -0
- data/lib/plutonium/ui/layout/base.rb +1 -1
- data/lib/plutonium/ui/layout/resource_layout.rb +7 -1
- data/lib/plutonium/ui/modal/base.rb +30 -3
- data/lib/plutonium/ui/modal/centered.rb +5 -2
- data/lib/plutonium/ui/options/has_cents_field.rb +21 -0
- data/lib/plutonium/ui/page/index.rb +2 -1
- data/lib/plutonium/ui/page/interactive_action.rb +12 -2
- data/lib/plutonium/ui/page/kanban_move.rb +20 -0
- data/lib/plutonium/ui/page/show.rb +29 -1
- data/lib/plutonium/ui/page/wizard.rb +371 -0
- data/lib/plutonium/ui/page/wizard_chooser.rb +97 -0
- data/lib/plutonium/ui/page/wizard_completed.rb +86 -0
- data/lib/plutonium/ui/table/base.rb +1 -1
- data/lib/plutonium/ui/table/components/view_switcher.rb +2 -1
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/ui/wizard/review.rb +196 -0
- data/lib/plutonium/ui/wizard/stepper.rb +122 -0
- data/lib/plutonium/ui/wizard/summary_display.rb +92 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/attachment_data.rb +42 -0
- data/lib/plutonium/wizard/attachments.rb +226 -0
- data/lib/plutonium/wizard/base.rb +216 -0
- data/lib/plutonium/wizard/base_controller.rb +31 -0
- data/lib/plutonium/wizard/configuration.rb +42 -0
- data/lib/plutonium/wizard/controller.rb +162 -0
- data/lib/plutonium/wizard/data.rb +134 -0
- data/lib/plutonium/wizard/driving.rb +639 -0
- data/lib/plutonium/wizard/dsl.rb +336 -0
- data/lib/plutonium/wizard/errors.rb +27 -0
- data/lib/plutonium/wizard/field_capture.rb +157 -0
- data/lib/plutonium/wizard/field_importer.rb +208 -0
- data/lib/plutonium/wizard/gate.rb +171 -0
- data/lib/plutonium/wizard/instance_key.rb +97 -0
- data/lib/plutonium/wizard/lazy_persisted.rb +77 -0
- data/lib/plutonium/wizard/resume.rb +250 -0
- data/lib/plutonium/wizard/review_step.rb +48 -0
- data/lib/plutonium/wizard/route_resolution.rb +40 -0
- data/lib/plutonium/wizard/runner.rb +684 -0
- data/lib/plutonium/wizard/session.rb +53 -0
- data/lib/plutonium/wizard/state.rb +35 -0
- data/lib/plutonium/wizard/step.rb +61 -0
- data/lib/plutonium/wizard/step_adapter.rb +103 -0
- data/lib/plutonium/wizard/store/active_record.rb +174 -0
- data/lib/plutonium/wizard/store/base.rb +42 -0
- data/lib/plutonium/wizard/store/memory.rb +44 -0
- data/lib/plutonium/wizard/sweep_job.rb +76 -0
- data/lib/plutonium/wizard.rb +86 -0
- data/lib/plutonium.rb +5 -0
- data/lib/rodauth/features/case_insensitive_login.rb +1 -1
- data/lib/tasks/release.rake +144 -191
- data/package.json +7 -5
- data/src/css/components.css +137 -0
- data/src/js/controllers/attachment_input_controller.js +15 -1
- data/src/js/controllers/currency_input_controller.js +39 -0
- data/src/js/controllers/dirty_form_guard_controller.js +155 -27
- data/src/js/controllers/intl_tel_input_controller.js +4 -0
- data/src/js/controllers/kanban_controller.js +717 -0
- data/src/js/controllers/password_sentinel_controller.js +39 -0
- data/src/js/controllers/register_controllers.js +8 -0
- data/src/js/controllers/remote_modal_controller.js +10 -0
- data/src/js/controllers/row_click_controller.js +14 -1
- data/src/js/controllers/wizard_controller.js +54 -0
- data/src/js/turbo/turbo_confirm.js +1 -1
- data/yarn.lock +945 -286
- metadata +113 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<%= render Plutonium::UI::Layout::BasicLayout.new do |layout| %>
|
|
2
|
+
<%# Shell-less wizards have no topbar, so lift the content off the viewport edge %>
|
|
3
|
+
<%# and cap its width — keeping it comfortably centered like an onboarding screen. %>
|
|
4
|
+
<div class="mx-auto w-full max-w-3xl pt-8 sm:pt-14">
|
|
5
|
+
<%= yield %>
|
|
6
|
+
</div>
|
|
7
|
+
<% end %>
|
|
@@ -1,48 +1,4 @@
|
|
|
1
1
|
<% flash.each do |type, msg| %>
|
|
2
|
-
<%
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
variant = {
|
|
6
|
-
success: "success",
|
|
7
|
-
warning: "warning",
|
|
8
|
-
alert: "danger", danger: "danger", error: "danger"
|
|
9
|
-
}.fetch(type.to_sym, "info")
|
|
10
|
-
%>
|
|
11
|
-
|
|
12
|
-
<div data-controller="resource-dismiss"
|
|
13
|
-
data-resource-dismiss-after-value="6000"
|
|
14
|
-
class="pu-toast pu-toast-<%= variant %>"
|
|
15
|
-
role="alert">
|
|
16
|
-
|
|
17
|
-
<div class="pu-toast-icon">
|
|
18
|
-
<% case type.to_sym %>
|
|
19
|
-
<% when :success %>
|
|
20
|
-
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
21
|
-
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
|
|
22
|
-
</svg>
|
|
23
|
-
<% when :warning %>
|
|
24
|
-
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
25
|
-
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"/>
|
|
26
|
-
</svg>
|
|
27
|
-
<% when :alert, :danger, :error %>
|
|
28
|
-
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
29
|
-
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"/>
|
|
30
|
-
</svg>
|
|
31
|
-
<% else %>
|
|
32
|
-
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
|
33
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"/>
|
|
34
|
-
</svg>
|
|
35
|
-
<% end %>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="pu-toast-message"><%= msg %></div>
|
|
38
|
-
<button type="button"
|
|
39
|
-
class="pu-toast-close"
|
|
40
|
-
data-action="click->resource-dismiss#dismiss"
|
|
41
|
-
aria-label="Close">
|
|
42
|
-
<span class="sr-only">Close</span>
|
|
43
|
-
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
44
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
45
|
-
</svg>
|
|
46
|
-
</button>
|
|
47
|
-
</div>
|
|
2
|
+
<% next unless msg.present? %>
|
|
3
|
+
<%= render "plutonium/toast", type: type, msg: msg %>
|
|
48
4
|
<% end %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
Renders a single dismissable toast. Locals:
|
|
3
|
+
type — flash-style key (:success, :warning, :alert/:danger/:error, …)
|
|
4
|
+
msg — the message string
|
|
5
|
+
Shared by _flash_toasts (one per flash entry) and any turbo_stream that
|
|
6
|
+
wants to append a single toast without re-rendering the whole flash set
|
|
7
|
+
(e.g. the kanban move-rejection feedback).
|
|
8
|
+
%>
|
|
9
|
+
<%
|
|
10
|
+
variant = {
|
|
11
|
+
success: "success",
|
|
12
|
+
warning: "warning",
|
|
13
|
+
alert: "danger", danger: "danger", error: "danger"
|
|
14
|
+
}.fetch(type.to_sym, "info")
|
|
15
|
+
%>
|
|
16
|
+
|
|
17
|
+
<div data-controller="resource-dismiss"
|
|
18
|
+
data-resource-dismiss-after-value="6000"
|
|
19
|
+
class="pu-toast pu-toast-<%= variant %>"
|
|
20
|
+
role="alert">
|
|
21
|
+
|
|
22
|
+
<div class="pu-toast-icon">
|
|
23
|
+
<% case type.to_sym %>
|
|
24
|
+
<% when :success %>
|
|
25
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
26
|
+
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
|
|
27
|
+
</svg>
|
|
28
|
+
<% when :warning %>
|
|
29
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
30
|
+
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"/>
|
|
31
|
+
</svg>
|
|
32
|
+
<% when :alert, :danger, :error %>
|
|
33
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
34
|
+
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"/>
|
|
35
|
+
</svg>
|
|
36
|
+
<% else %>
|
|
37
|
+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
|
38
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"/>
|
|
39
|
+
</svg>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="pu-toast-message"><%= msg %></div>
|
|
43
|
+
<button type="button"
|
|
44
|
+
class="pu-toast-close"
|
|
45
|
+
data-action="click->resource-dismiss#dismiss"
|
|
46
|
+
aria-label="Close">
|
|
47
|
+
<span class="sr-only">Close</span>
|
|
48
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
49
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render Plutonium::UI::Form::KanbanMove.new(@interaction) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render build_kanban_board_shell %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render Plutonium::UI::Page::KanbanMove.new %>
|
data/config/brakeman.ignore
CHANGED
|
@@ -144,11 +144,11 @@
|
|
|
144
144
|
{
|
|
145
145
|
"warning_type": "Dangerous Eval",
|
|
146
146
|
"warning_code": 13,
|
|
147
|
-
"fingerprint": "
|
|
147
|
+
"fingerprint": "bdacd028907815ba0db0cf99e069d1ea7e94a6e527b7746efd97886c52b2f928",
|
|
148
148
|
"check_name": "Evaluation",
|
|
149
149
|
"message": "Dynamic string evaluated as code",
|
|
150
150
|
"file": "lib/plutonium/models/has_cents.rb",
|
|
151
|
-
"line":
|
|
151
|
+
"line": 162,
|
|
152
152
|
"link": "https://brakemanscanner.org/docs/warning_types/dangerous_eval/",
|
|
153
153
|
"code": "class_eval(\" # Getter method for the decimal representation of the cents value.\\n #\\n # @return [BigDecimal, nil] The decimal value or nil if cents_name is not present.\\n def #{(:\"#{cents_name.to_sym}_#{suffix}\" or cents_name.to_sym.to_s.gsub(/_cents$/, \"\").to_sym)}\\n #{cents_name.to_sym}.to_d / #{rate} if #{cents_name.to_sym}.present?\\n end\\n\\n # Setter method for the decimal representation of the cents value.\\n #\\n # @param value [Numeric, nil] The decimal value to be set.\\n def #{(:\"#{cents_name.to_sym}_#{suffix}\" or cents_name.to_sym.to_s.gsub(/_cents$/, \"\").to_sym)}=(value)\\n self.#{cents_name.to_sym} = begin\\n (BigDecimal(value.to_s) * #{rate}).to_i if value.present?\\n rescue ArgumentError\\n nil\\n end\\n end\\n\\n # Mark decimal field as invalid if cents field is not valid\\n after_validation do\\n next unless errors[#{cents_name.to_sym.inspect}].present?\\n\\n errors.add(#{(:\"#{cents_name.to_sym}_#{suffix}\" or cents_name.to_sym.to_s.gsub(/_cents$/, \"\").to_sym).inspect}, :invalid)\\n end\\n\", \"lib/plutonium/models/has_cents.rb\", 135)",
|
|
154
154
|
"render_path": null,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreatePlutoniumWizardSessions < ActiveRecord::Migration[7.2]
|
|
4
|
+
def change
|
|
5
|
+
create_table :plutonium_wizard_sessions do |t|
|
|
6
|
+
t.string :wizard, null: false
|
|
7
|
+
t.string :status, null: false, default: "in_progress" # in_progress | completing | completed
|
|
8
|
+
t.string :current_step
|
|
9
|
+
|
|
10
|
+
# Identity — a deterministic digest of (wizard, scope, anchor, principal).
|
|
11
|
+
# A single unique column is required because nullable polymorphic columns
|
|
12
|
+
# can't enforce the singleton rule (NULL != NULL in unique indexes).
|
|
13
|
+
t.string :instance_key, null: false
|
|
14
|
+
|
|
15
|
+
# Polymorphic refs — for querying/listing, NOT identity. *_id is string-typed
|
|
16
|
+
# to accommodate bigint or uuid host primary keys.
|
|
17
|
+
t.string :scope_type
|
|
18
|
+
t.string :scope_id
|
|
19
|
+
t.string :owner_type
|
|
20
|
+
t.string :owner_id
|
|
21
|
+
t.string :anchor_type
|
|
22
|
+
t.string :anchor_id
|
|
23
|
+
t.string :token
|
|
24
|
+
|
|
25
|
+
# The portal (engine) this run was launched in, e.g. "OrgPortal::Engine" (the
|
|
26
|
+
# main app's class name for a public mount). Records the run's portal context
|
|
27
|
+
# so the "continue where you left off" listing only ever shows — and links —
|
|
28
|
+
# runs that belong to the portal being viewed (two portals can share an entity
|
|
29
|
+
# scope, so scope alone can't identify the portal).
|
|
30
|
+
t.string :engine
|
|
31
|
+
|
|
32
|
+
# Optimistic-merge version. The runner reads a row at one version and writes
|
|
33
|
+
# it back a request later; a concurrent advance bumps the version in between.
|
|
34
|
+
# The store compares the version the state was read at against the row's
|
|
35
|
+
# current version under a row lock and MERGES (rather than clobbers) when they
|
|
36
|
+
# differ, so two concurrent writers on the same run never lose each other's
|
|
37
|
+
# staged data.
|
|
38
|
+
t.integer :lock_version, null: false, default: 0
|
|
39
|
+
|
|
40
|
+
t.public_send(:jsonb, :data, null: false, default: {})
|
|
41
|
+
t.public_send(:jsonb, :tracked_records, null: false, default: {})
|
|
42
|
+
# Steps the user has actually visited+validated (§6.3 completeness). A
|
|
43
|
+
# zero-validation step is only "complete" once it's been visited.
|
|
44
|
+
t.public_send(:jsonb, :visited, null: false, default: [])
|
|
45
|
+
|
|
46
|
+
t.datetime :expires_at
|
|
47
|
+
t.datetime :completed_at
|
|
48
|
+
t.timestamps
|
|
49
|
+
|
|
50
|
+
t.index :instance_key, unique: true
|
|
51
|
+
t.index [:status, :expires_at]
|
|
52
|
+
t.index [:owner_type, :owner_id, :engine, :status]
|
|
53
|
+
t.index [:scope_type, :scope_id, :status]
|
|
54
|
+
t.index [:wizard, :anchor_type, :anchor_id, :status]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/docs/.vitepress/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineConfig } from "vitepress"
|
|
2
2
|
import { withMermaid } from "vitepress-plugin-mermaid";
|
|
3
|
+
import llmstxt from "vitepress-plugin-llms";
|
|
3
4
|
|
|
4
5
|
const base = "/plutonium-core/"
|
|
5
6
|
|
|
@@ -22,6 +23,24 @@ export default defineConfig(withMermaid({
|
|
|
22
23
|
],
|
|
23
24
|
ignoreDeadLinks: 'localhostLinks',
|
|
24
25
|
srcExclude: ['superpowers/**'],
|
|
26
|
+
vite: {
|
|
27
|
+
plugins: [
|
|
28
|
+
// Generates llms.txt, llms-full.txt, and a raw .md twin for every page.
|
|
29
|
+
llmstxt({
|
|
30
|
+
// Site base (/plutonium-core/) is appended automatically — domain must not include it.
|
|
31
|
+
domain: "https://radioactive-labs.github.io",
|
|
32
|
+
// public/ is served verbatim (skills live there); superpowers/ is internal.
|
|
33
|
+
// Section landing pages are Vue components with no markdown content.
|
|
34
|
+
ignoreFiles: [
|
|
35
|
+
"superpowers/**",
|
|
36
|
+
"public/**",
|
|
37
|
+
"getting-started/index.md",
|
|
38
|
+
"guides/index.md",
|
|
39
|
+
"reference/index.md",
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
},
|
|
25
44
|
themeConfig: {
|
|
26
45
|
// https://vitepress.dev/reference/default-theme-config
|
|
27
46
|
logo: "/plutonium.png",
|
|
@@ -32,7 +51,8 @@ export default defineConfig(withMermaid({
|
|
|
32
51
|
{ text: "Home", link: "/" },
|
|
33
52
|
{ text: "Getting Started", link: "/getting-started/" },
|
|
34
53
|
{ text: "Guides", link: "/guides/" },
|
|
35
|
-
{ text: "Reference", link: "/reference/" }
|
|
54
|
+
{ text: "Reference", link: "/reference/" },
|
|
55
|
+
{ text: "For AI Agents", link: "/ai" }
|
|
36
56
|
],
|
|
37
57
|
sidebar: {
|
|
38
58
|
'/getting-started/': [
|
|
@@ -88,6 +108,8 @@ export default defineConfig(withMermaid({
|
|
|
88
108
|
{ text: "Multi-tenancy", link: "/guides/multi-tenancy" },
|
|
89
109
|
{ text: "Search & Filtering", link: "/guides/search-filtering" },
|
|
90
110
|
{ text: "User Invites", link: "/guides/user-invites" },
|
|
111
|
+
{ text: "Wizards", link: "/guides/wizards" },
|
|
112
|
+
{ text: "Kanban Boards", link: "/guides/kanban" },
|
|
91
113
|
]
|
|
92
114
|
},
|
|
93
115
|
{
|
|
@@ -177,6 +199,28 @@ export default defineConfig(withMermaid({
|
|
|
177
199
|
{ text: "Invites", link: "/reference/tenancy/invites" },
|
|
178
200
|
]
|
|
179
201
|
},
|
|
202
|
+
{
|
|
203
|
+
text: "Wizard",
|
|
204
|
+
collapsed: false,
|
|
205
|
+
items: [
|
|
206
|
+
{ text: "Overview", link: "/reference/wizard/" },
|
|
207
|
+
{ text: "DSL", link: "/reference/wizard/dsl" },
|
|
208
|
+
{ text: "Anchoring & resume", link: "/reference/wizard/anchoring-resume" },
|
|
209
|
+
{ text: "Storage & config", link: "/reference/wizard/storage-config" },
|
|
210
|
+
{ text: "Registration & launch", link: "/reference/wizard/registration-launch" },
|
|
211
|
+
{ text: "One-time", link: "/reference/wizard/one-time" },
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
text: "Kanban",
|
|
216
|
+
collapsed: false,
|
|
217
|
+
items: [
|
|
218
|
+
{ text: "Overview", link: "/reference/kanban/" },
|
|
219
|
+
{ text: "DSL", link: "/reference/kanban/dsl" },
|
|
220
|
+
{ text: "Positioning", link: "/reference/kanban/positioning" },
|
|
221
|
+
{ text: "Authorization", link: "/reference/kanban/authorization" },
|
|
222
|
+
]
|
|
223
|
+
},
|
|
180
224
|
{
|
|
181
225
|
text: "Testing",
|
|
182
226
|
collapsed: false,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Syncs .claude/skills/*/SKILL.md into docs/public/skills/ so the docs site
|
|
2
|
+
// serves them as raw, crawlable markdown at /skills/<name>.md.
|
|
3
|
+
// Runs before `docs:dev` and `docs:build` (see package.json). Output is gitignored.
|
|
4
|
+
import { readdirSync, readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from "node:fs"
|
|
5
|
+
import { join, dirname } from "node:path"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
|
|
8
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..", "..")
|
|
9
|
+
const skillsDir = join(root, ".claude", "skills")
|
|
10
|
+
const outDir = join(root, "docs", "public", "skills")
|
|
11
|
+
|
|
12
|
+
rmSync(outDir, { recursive: true, force: true })
|
|
13
|
+
mkdirSync(outDir, { recursive: true })
|
|
14
|
+
|
|
15
|
+
const skills = []
|
|
16
|
+
|
|
17
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
18
|
+
if (!entry.isDirectory()) continue
|
|
19
|
+
const source = join(skillsDir, entry.name, "SKILL.md")
|
|
20
|
+
if (!existsSync(source)) continue // skip dirs that aren't skills (no SKILL.md)
|
|
21
|
+
let content = readFileSync(source, "utf8")
|
|
22
|
+
|
|
23
|
+
const description = content.match(/^description:\s*(.+)$/m)?.[1] ?? ""
|
|
24
|
+
|
|
25
|
+
// Wiki-style [[skill-name]] cross-links become relative markdown links so
|
|
26
|
+
// crawlers can follow them between the published files.
|
|
27
|
+
content = content.replace(/\[\[([\w-]+)\]\]/g, "[$1]($1.md)")
|
|
28
|
+
|
|
29
|
+
writeFileSync(join(outDir, `${entry.name}.md`), content)
|
|
30
|
+
skills.push({ name: entry.name, description })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
skills.sort((a, b) => (a.name === "plutonium" ? -1 : b.name === "plutonium" ? 1 : a.name.localeCompare(b.name)))
|
|
34
|
+
|
|
35
|
+
const index = `# Plutonium Skills
|
|
36
|
+
|
|
37
|
+
Task-focused guides for AI agents working with the [Plutonium](https://radioactive-labs.github.io/plutonium-core/) Rails RAD framework. Each file is self-contained markdown. Start with \`plutonium.md\` — it routes to the others.
|
|
38
|
+
|
|
39
|
+
These are the same skills the gem installs into projects via \`rails g pu:skills:sync\` (Claude Code loads them automatically from \`.claude/skills/\`). Any agent can fetch them directly from the URLs below.
|
|
40
|
+
|
|
41
|
+
${skills.map((s) => `- [${s.name}](${s.name}.md) — ${s.description}`).join("\n")}
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
writeFileSync(join(outDir, "index.md"), index)
|
|
45
|
+
console.log(`Synced ${skills.length} skills to docs/public/skills/`)
|
data/docs/ai.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Plutonium for AI Agents
|
|
3
|
+
description: Machine-readable entry point for AI coding agents building Rails apps with Plutonium — install commands, conventions, generator catalog, and skill files.
|
|
4
|
+
sidebar: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Plutonium for AI Agents
|
|
8
|
+
|
|
9
|
+
You are reading the agent entry point for **Plutonium**, a Rails RAD framework (Ruby gem) that provides resource-oriented architecture: automatic CRUD, authorization policies, declarative UI definitions, authentication, and multi-tenancy. If a user asked you to build something with Plutonium, this page gives you the golden path and where to read next.
|
|
10
|
+
|
|
11
|
+
## Machine-readable resources
|
|
12
|
+
|
|
13
|
+
| Resource | URL |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Docs index (llms.txt) | `https://radioactive-labs.github.io/plutonium-core/llms.txt` |
|
|
16
|
+
| Full docs, one file | `https://radioactive-labs.github.io/plutonium-core/llms-full.txt` |
|
|
17
|
+
| Agent skills index | `https://radioactive-labs.github.io/plutonium-core/skills/index.md` |
|
|
18
|
+
| Router skill (start here) | `https://radioactive-labs.github.io/plutonium-core/skills/plutonium.md` |
|
|
19
|
+
|
|
20
|
+
Every docs page also has a raw markdown twin: append `.md` to its URL (e.g. `/guides/multi-tenancy.md`).
|
|
21
|
+
|
|
22
|
+
**Prefer the skills over the raw docs.** They are task-focused, deduplicated, and encode the mistakes agents actually make. The router skill maps "about to do X" to the right skill file. If you are Claude Code, run `rails g pu:skills:sync` after installing the gem to install them into `.claude/skills/` so they load automatically.
|
|
23
|
+
|
|
24
|
+
## Rules that prevent expensive mistakes
|
|
25
|
+
|
|
26
|
+
1. **Plutonium is generator-driven.** Nearly every file has a `pu:*` generator. Generate, then edit — never hand-write models, definitions, policies, or packages from scratch. Hand-written files drift from conventions and break future generator runs.
|
|
27
|
+
2. **Inspect before you act.** Check `Gemfile` for `plutonium`, `ls config/packages.rb`, and `ls packages/` before installing or scaffolding anything.
|
|
28
|
+
3. **New app → `plutonium.rb` template. Existing app → `base.rb` template.** Never run `plutonium.rb` on an existing app; it re-runs full bootstrap and clobbers git history.
|
|
29
|
+
4. **Pass flags to avoid interactive prompts**: `--dest=main_app` (or `--dest=<package>`), `--force` when re-running meta-generators, `--auth=<account>` for portals, `--skip-bundle`, `--quiet`.
|
|
30
|
+
5. **Quote field arguments with special characters**: `'title:string?'`, `'price:decimal{10,2}'`.
|
|
31
|
+
6. **Multi-tenancy is structural, not filtered in policies.** The portal declares `scope_to_entity`; the model provides the association path that `associated_with` resolves. Never `where(organization: ...)` in a policy — read the tenancy skill first.
|
|
32
|
+
|
|
33
|
+
## Golden path: new application
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
rails new myapp -a propshaft -j esbuild -c tailwind \
|
|
37
|
+
-m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
|
|
38
|
+
cd myapp
|
|
39
|
+
rails db:prepare
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then for each resource:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
rails g pu:res:scaffold Post 'title:string' 'body:text?' user:references --dest=main_app
|
|
46
|
+
rails db:prepare
|
|
47
|
+
rails g pu:res:conn Post --dest=<portal_package>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Golden path: existing application
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bin/rails app:template \
|
|
54
|
+
LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or manually: add `gem "plutonium"` to the Gemfile, `bundle install`, then `rails g pu:core:install`.
|
|
58
|
+
|
|
59
|
+
## Architecture in one table
|
|
60
|
+
|
|
61
|
+
A **resource** is four cooperating layers. Plutonium auto-fills defaults from the model; you only declare overrides.
|
|
62
|
+
|
|
63
|
+
| Layer | File | Purpose |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Model | `app/models/post.rb` | Data, validations, associations |
|
|
66
|
+
| Definition | `app/definitions/post_definition.rb` | UI — fields, filters, actions |
|
|
67
|
+
| Policy | `app/policies/post_policy.rb` | Authorization |
|
|
68
|
+
| Controller | `app/controllers/posts_controller.rb` | Rarely edited — use hooks |
|
|
69
|
+
| Interaction (optional) | `app/interactions/publish_post_interaction.rb` | Business logic for custom actions |
|
|
70
|
+
|
|
71
|
+
Resources live in the main app or in **feature packages**; users access them through **portal packages** (Rails engines with their own auth and optional tenant scoping).
|
|
72
|
+
|
|
73
|
+
## Generator catalog
|
|
74
|
+
|
|
75
|
+
Discover all generators with `rails g --help | grep pu:`. The most used:
|
|
76
|
+
|
|
77
|
+
| Generator | Purpose |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `pu:core:install` | Initial Plutonium setup |
|
|
80
|
+
| `pu:res:scaffold NAME field:type ... --dest=` | New resource (model, migration, policy, definition) |
|
|
81
|
+
| `pu:res:conn RESOURCE --dest=PORTAL` | Connect a resource to a portal |
|
|
82
|
+
| `pu:pkg:package NAME` | Feature package |
|
|
83
|
+
| `pu:pkg:portal NAME --auth=...` | Portal package |
|
|
84
|
+
| `pu:rodauth:install` / `pu:rodauth:account NAME` | Authentication |
|
|
85
|
+
| `pu:saas:setup --user ... --entity ...` | Full SaaS bootstrap: user + tenant + membership + portal |
|
|
86
|
+
| `pu:test:install` / `pu:test:scaffold NAME` | Testing scaffolds |
|
|
87
|
+
| `pu:skills:sync` | Install the agent skill files into the project |
|
|
88
|
+
|
|
89
|
+
## Verify your work
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
rails runner "puts Plutonium::VERSION" # gem installed and loaded
|
|
93
|
+
rails db:prepare # migrations applied
|
|
94
|
+
bin/dev # boot; visit the portal route
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## For humans reading this
|
|
98
|
+
|
|
99
|
+
This page exists so AI coding agents can bootstrap correctly on the first try. Point your agent at it, or just say: *"Read https://radioactive-labs.github.io/plutonium-core/ai.md before starting."* The [Getting Started guide](/getting-started/) covers the same ground for people.
|
data/docs/guides/index.md
CHANGED
|
@@ -25,6 +25,8 @@ aside: false
|
|
|
25
25
|
{ name: 'Nested resources', link: '/plutonium-core/guides/nested-resources' },
|
|
26
26
|
{ name: 'Multi-tenancy', link: '/plutonium-core/guides/multi-tenancy' },
|
|
27
27
|
{ name: 'Search & filtering', link: '/plutonium-core/guides/search-filtering' },
|
|
28
|
+
{ name: 'Wizards', desc: 'Multi-step flows — onboarding, checkout, branching create.', link: '/plutonium-core/guides/wizards' },
|
|
29
|
+
{ name: 'Kanban boards', desc: 'Drag-and-drop board view — columns, moves, positioning, WIP limits.', link: '/plutonium-core/guides/kanban' },
|
|
28
30
|
]},
|
|
29
31
|
{ group: 'Customization', items: [
|
|
30
32
|
{ name: 'Customizing the UI', desc: 'A map of the override surface — pages, forms, displays, tables, components, layouts.', link: '/plutonium-core/guides/customizing-ui' },
|