plutonium 0.49.0 → 0.49.1
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-invites/SKILL.md +41 -0
- data/CHANGELOG.md +15 -0
- data/app/assets/plutonium.js +35 -0
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +24 -24
- data/app/assets/plutonium.min.js.map +3 -3
- data/app/views/plutonium/_flash.html.erb +1 -1
- data/docs/guides/user-invites.md +64 -0
- data/docs/superpowers/plans/2026-05-06-multi-invite-model-support.md +1487 -0
- data/docs/superpowers/plans/2026-05-06-multi-invite-model-support.md.tasks.json +15 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/invites/install_generator.rb +136 -35
- data/lib/generators/pu/invites/templates/app/interactions/invite_user_interaction.rb.tt +8 -2
- data/lib/generators/pu/invites/templates/app/interactions/user_invite_user_interaction.rb.tt +7 -1
- data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +4 -4
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +9 -4
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/definitions/invites/user_invite_definition.rb.tt +1 -1
- data/lib/generators/pu/invites/templates/packages/invites/app/mailers/invites/user_invite_mailer.rb.tt +8 -8
- data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +13 -4
- data/lib/generators/pu/invites/templates/packages/invites/app/policies/invites/user_invite_policy.rb.tt +3 -3
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/landing.html.erb.tt +1 -1
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/show.html.erb.tt +1 -1
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invite_mailer/invitation.html.erb.tt +4 -4
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invite_mailer/invitation.text.erb.tt +4 -4
- data/lib/generators/pu/invites/templates/packages/invites/app/views/layouts/invites/invitation.html.erb.tt +5 -1
- data/lib/generators/pu/saas/welcome/templates/app/views/layouts/welcome.html.erb.tt +5 -1
- data/lib/plutonium/invites/concerns/invite_token.rb +11 -3
- data/lib/plutonium/invites/concerns/invite_user.rb +13 -4
- data/lib/plutonium/invites/controller.rb +14 -1
- data/lib/plutonium/invites/pending_invite_check.rb +37 -28
- data/lib/plutonium/resource/controllers/interactive_actions.rb +13 -9
- data/lib/plutonium/resource/policy.rb +23 -8
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/js/controllers/flatpickr_controller.js +23 -0
- data/src/js/controllers/sidebar_controller.js +28 -1
- metadata +5 -3
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
<meta name="csrf-param" content="authenticity_token" />
|
|
8
8
|
<meta name="csrf-token" content="<%%= form_authenticity_token %>" />
|
|
9
9
|
<%%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
|
10
|
-
|
|
10
|
+
<%% if defined?(Importmap) %>
|
|
11
|
+
<%%= javascript_importmap_tags %>
|
|
12
|
+
<%% else %>
|
|
13
|
+
<%%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
|
|
14
|
+
<%% end %>
|
|
11
15
|
</head>
|
|
12
16
|
<body class="antialiased min-h-screen bg-[var(--pu-body)]">
|
|
13
17
|
<main class="p-4 min-h-screen flex flex-col items-center justify-center gap-2 px-6 py-8 mx-auto lg:py-0">
|
|
@@ -125,9 +125,9 @@ module Plutonium
|
|
|
125
125
|
|
|
126
126
|
transaction do
|
|
127
127
|
update!(
|
|
128
|
-
state
|
|
129
|
-
accepted_at
|
|
130
|
-
|
|
128
|
+
:state => :accepted,
|
|
129
|
+
:accepted_at => Time.current,
|
|
130
|
+
user_attribute => user
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
create_membership_for(user)
|
|
@@ -135,6 +135,14 @@ module Plutonium
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
+
# Override to specify the user association name on the invite model.
|
|
139
|
+
# Defaults to :user. Override when the invite model's `belongs_to`
|
|
140
|
+
# uses a different name (e.g., :spender_account, :staff_user).
|
|
141
|
+
# @return [Symbol]
|
|
142
|
+
def user_attribute
|
|
143
|
+
:user
|
|
144
|
+
end
|
|
145
|
+
|
|
138
146
|
# Override this method to specify the mailer class.
|
|
139
147
|
#
|
|
140
148
|
# @return [Class] the mailer class for sending invitation emails
|
|
@@ -37,12 +37,12 @@ module Plutonium
|
|
|
37
37
|
|
|
38
38
|
def execute
|
|
39
39
|
attrs = {
|
|
40
|
-
entity: entity,
|
|
41
40
|
email: email,
|
|
42
41
|
role: role,
|
|
43
42
|
invited_by: current_user,
|
|
44
43
|
**additional_invite_attributes
|
|
45
44
|
}
|
|
45
|
+
attrs[invite_entity_attribute] = entity
|
|
46
46
|
attrs[:invitable] = invitable if invitable.present?
|
|
47
47
|
|
|
48
48
|
invite_class.create!(attrs)
|
|
@@ -109,6 +109,15 @@ module Plutonium
|
|
|
109
109
|
entity.class.name.underscore.to_sym
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# Override to specify the entity association name on the invite model.
|
|
113
|
+
# Defaults to :entity, matching the convention documented on InviteToken.
|
|
114
|
+
# When the invite model uses a concrete `belongs_to :<entity_name>`
|
|
115
|
+
# instead, override this to return that association name.
|
|
116
|
+
# @return [Symbol]
|
|
117
|
+
def invite_entity_attribute
|
|
118
|
+
:entity
|
|
119
|
+
end
|
|
120
|
+
|
|
112
121
|
def role_is_present
|
|
113
122
|
errors.add(:role, :blank) if role.blank?
|
|
114
123
|
end
|
|
@@ -130,9 +139,9 @@ module Plutonium
|
|
|
130
139
|
return unless email.present? && entity.present?
|
|
131
140
|
|
|
132
141
|
pending = invite_class.find_by(
|
|
133
|
-
|
|
134
|
-
email
|
|
135
|
-
state
|
|
142
|
+
invite_entity_attribute => entity,
|
|
143
|
+
:email => email,
|
|
144
|
+
:state => :pending
|
|
136
145
|
)
|
|
137
146
|
errors.add(:email, "already has a pending invitation") if pending
|
|
138
147
|
end
|
|
@@ -35,6 +35,7 @@ module Plutonium
|
|
|
35
35
|
extend ActiveSupport::Concern
|
|
36
36
|
|
|
37
37
|
included do
|
|
38
|
+
append_view_path File.expand_path("app/views", Plutonium.root)
|
|
38
39
|
helper_method :current_user if respond_to?(:helper_method)
|
|
39
40
|
end
|
|
40
41
|
|
|
@@ -72,7 +73,7 @@ module Plutonium
|
|
|
72
73
|
return unless (@invite = load_and_validate_invite(params[:token]))
|
|
73
74
|
|
|
74
75
|
unless current_user
|
|
75
|
-
redirect_to
|
|
76
|
+
redirect_to invitation_path_for(params[:token]),
|
|
76
77
|
alert: "Please sign in to accept this invitation"
|
|
77
78
|
return
|
|
78
79
|
end
|
|
@@ -174,6 +175,18 @@ module Plutonium
|
|
|
174
175
|
raise NotImplementedError, "#{self.class}#invite_class must return the invite model class"
|
|
175
176
|
end
|
|
176
177
|
|
|
178
|
+
# Override to customize the invitation URL helper.
|
|
179
|
+
# Default uses Rails' `invitation_path(token:)` helper, which is what
|
|
180
|
+
# `pu:invites:install` generates for single-flow apps. Multi-flow apps
|
|
181
|
+
# whose generator scoped the route as `<prefix>_invitation_path` should
|
|
182
|
+
# override this.
|
|
183
|
+
#
|
|
184
|
+
# @param token [String] the invitation token
|
|
185
|
+
# @return [String] the URL path
|
|
186
|
+
def invitation_path_for(token)
|
|
187
|
+
invitation_path(token: token)
|
|
188
|
+
end
|
|
189
|
+
|
|
177
190
|
# Override to specify the user model class.
|
|
178
191
|
#
|
|
179
192
|
# @return [Class] the user model class
|
|
@@ -8,39 +8,34 @@ module Plutonium
|
|
|
8
8
|
# (e.g., WelcomeController, DashboardController) to check for
|
|
9
9
|
# pending invitations stored in cookies.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# Hosts may override either `invite_classes` (preferred — returns
|
|
12
|
+
# an Array of invite classes to check, in priority order) or
|
|
13
|
+
# `invite_class` (single class, kept for backward compatibility).
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# # Normal post-login flow...
|
|
19
|
-
# redirect_to dashboard_path
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
# private
|
|
23
|
-
#
|
|
24
|
-
# def invite_class
|
|
25
|
-
# Invites::UserInvite
|
|
26
|
-
# end
|
|
15
|
+
# @example Single invite class
|
|
16
|
+
# def invite_class
|
|
17
|
+
# ::Invites::UserInvite
|
|
27
18
|
# end
|
|
28
19
|
#
|
|
20
|
+
# @example Multiple invite classes
|
|
21
|
+
# def invite_classes
|
|
22
|
+
# [::Invites::FunderInvite, ::Invites::SpenderInvite]
|
|
23
|
+
# end
|
|
29
24
|
module PendingInviteCheck
|
|
30
25
|
extend ActiveSupport::Concern
|
|
31
26
|
|
|
27
|
+
included do
|
|
28
|
+
append_view_path File.expand_path("app/views", Plutonium.root)
|
|
29
|
+
end
|
|
30
|
+
|
|
32
31
|
private
|
|
33
32
|
|
|
34
33
|
# Check for a pending invitation and redirect if found.
|
|
35
|
-
#
|
|
36
|
-
# @return [Boolean] true if redirected, false otherwise
|
|
37
34
|
def redirect_to_pending_invite!
|
|
38
35
|
token = cookies.encrypted[:pending_invitation]
|
|
39
36
|
return false unless token
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if invite
|
|
38
|
+
if find_pending_invite(token)
|
|
44
39
|
redirect_to invitation_path(token: token)
|
|
45
40
|
true
|
|
46
41
|
else
|
|
@@ -49,14 +44,12 @@ module Plutonium
|
|
|
49
44
|
end
|
|
50
45
|
end
|
|
51
46
|
|
|
52
|
-
# Returns the pending invite if one exists.
|
|
53
|
-
#
|
|
54
|
-
# @return [Object, nil] the pending invite or nil
|
|
47
|
+
# Returns the pending invite if one exists across any invite_classes.
|
|
55
48
|
def pending_invite
|
|
56
49
|
token = cookies.encrypted[:pending_invitation]
|
|
57
50
|
return nil unless token
|
|
58
51
|
|
|
59
|
-
invite =
|
|
52
|
+
invite = find_pending_invite(token)
|
|
60
53
|
unless invite
|
|
61
54
|
cookies.delete(:pending_invitation)
|
|
62
55
|
return nil
|
|
@@ -65,11 +58,27 @@ module Plutonium
|
|
|
65
58
|
invite
|
|
66
59
|
end
|
|
67
60
|
|
|
68
|
-
# Override to specify
|
|
69
|
-
#
|
|
70
|
-
# @return [Class]
|
|
61
|
+
# Override to specify multiple invite model classes (preferred).
|
|
62
|
+
# Defaults to `[invite_class]` for backward compatibility.
|
|
63
|
+
# @return [Array<Class>]
|
|
64
|
+
def invite_classes
|
|
65
|
+
[invite_class]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Override to specify a single invite model class. Maintained for
|
|
69
|
+
# backward compatibility; prefer `invite_classes` for multi-flow apps.
|
|
70
|
+
# @return [Class]
|
|
71
71
|
def invite_class
|
|
72
|
-
raise NotImplementedError,
|
|
72
|
+
raise NotImplementedError,
|
|
73
|
+
"#{self.class}#invite_class or #invite_classes must return the invite model class(es)"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_pending_invite(token)
|
|
77
|
+
invite_classes.each do |klass|
|
|
78
|
+
invite = klass.find_for_acceptance(token)
|
|
79
|
+
return invite if invite
|
|
80
|
+
end
|
|
81
|
+
nil
|
|
73
82
|
end
|
|
74
83
|
end
|
|
75
84
|
end
|
|
@@ -29,7 +29,7 @@ module Plutonium
|
|
|
29
29
|
# GET /resources/1/record_actions/:interactive_action
|
|
30
30
|
def interactive_record_action
|
|
31
31
|
build_interactive_record_action_interaction
|
|
32
|
-
render :interactive_record_action,
|
|
32
|
+
render :interactive_record_action, formats: [:html], **modal_render_options
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# POST /resources/1/record_actions/:interactive_action
|
|
@@ -62,7 +62,7 @@ module Plutonium
|
|
|
62
62
|
end
|
|
63
63
|
else
|
|
64
64
|
format.any(:html, :turbo_stream) do
|
|
65
|
-
render :interactive_record_action,
|
|
65
|
+
render :interactive_record_action, formats: [:html], content_type: "text/html", **modal_render_options, status: :unprocessable_content
|
|
66
66
|
end
|
|
67
67
|
format.any do
|
|
68
68
|
@errors = @interaction.errors
|
|
@@ -77,7 +77,7 @@ module Plutonium
|
|
|
77
77
|
def interactive_resource_action
|
|
78
78
|
skip_verify_current_authorized_scope!
|
|
79
79
|
build_interactive_resource_action_interaction
|
|
80
|
-
render :interactive_resource_action,
|
|
80
|
+
render :interactive_resource_action, formats: [:html], **modal_render_options
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# POST /resources/resource_actions/:interactive_action
|
|
@@ -111,7 +111,7 @@ module Plutonium
|
|
|
111
111
|
end
|
|
112
112
|
else
|
|
113
113
|
format.any(:html, :turbo_stream) do
|
|
114
|
-
render :interactive_resource_action,
|
|
114
|
+
render :interactive_resource_action, formats: [:html], content_type: "text/html", **modal_render_options, status: :unprocessable_content
|
|
115
115
|
end
|
|
116
116
|
format.any do
|
|
117
117
|
@errors = @interaction.errors
|
|
@@ -125,7 +125,7 @@ module Plutonium
|
|
|
125
125
|
# GET /resources/bulk_actions/:interactive_action?ids[]=1&ids[]=2
|
|
126
126
|
def interactive_bulk_action
|
|
127
127
|
build_interactive_bulk_action_interaction
|
|
128
|
-
render :interactive_bulk_action,
|
|
128
|
+
render :interactive_bulk_action, formats: [:html], **modal_render_options
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
# POST /resources/bulk_actions/:interactive_action?ids[]=1&ids[]=2
|
|
@@ -158,7 +158,7 @@ module Plutonium
|
|
|
158
158
|
end
|
|
159
159
|
else
|
|
160
160
|
format.any(:html, :turbo_stream) do
|
|
161
|
-
render :interactive_bulk_action,
|
|
161
|
+
render :interactive_bulk_action, formats: [:html], content_type: "text/html", **modal_render_options, status: :unprocessable_content
|
|
162
162
|
end
|
|
163
163
|
format.any do
|
|
164
164
|
@errors = @interaction.errors
|
|
@@ -171,9 +171,13 @@ module Plutonium
|
|
|
171
171
|
|
|
172
172
|
private
|
|
173
173
|
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
# Render options for modal-aware actions. Returns `{ layout: false }` for
|
|
175
|
+
# turbo-frame requests so the bare frame is rendered, and an empty hash
|
|
176
|
+
# for top-level requests so the controller's default layout proc applies.
|
|
177
|
+
# (Passing `layout: nil` explicitly is treated as "no layout" by Rails,
|
|
178
|
+
# which is why we omit the key entirely on the default path.)
|
|
179
|
+
def modal_render_options
|
|
180
|
+
helpers.current_turbo_frame.present? ? {layout: false} : {}
|
|
177
181
|
end
|
|
178
182
|
|
|
179
183
|
def current_interactive_action
|
|
@@ -67,16 +67,31 @@ module Plutonium
|
|
|
67
67
|
raise ArgumentError, "parent and parent_association must both be provided together"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# Parent association scoping (nested routes)
|
|
71
|
-
#
|
|
72
|
-
#
|
|
70
|
+
# Parent association scoping (nested routes).
|
|
71
|
+
#
|
|
72
|
+
# The parent context is set on the policy for the whole request, so it
|
|
73
|
+
# leaks into sibling lookups too — e.g. a SecureAssociation field on
|
|
74
|
+
# the child's form authorizes an unrelated resource scope while
|
|
75
|
+
# parent/parent_association are still set. Only apply parent scoping
|
|
76
|
+
# when the relation actually corresponds to the parent's named
|
|
77
|
+
# association; otherwise fall through to entity scoping so we don't
|
|
78
|
+
# produce an incoherent (and silently empty) result.
|
|
73
79
|
assoc_reflection = parent.class.reflect_on_association(parent_association)
|
|
74
|
-
if assoc_reflection.
|
|
75
|
-
#
|
|
76
|
-
parent
|
|
80
|
+
if assoc_reflection && relation.klass <= assoc_reflection.klass
|
|
81
|
+
# The parent was already entity-scoped during authorization, so
|
|
82
|
+
# children accessed through the parent don't need additional
|
|
83
|
+
# entity scoping.
|
|
84
|
+
if assoc_reflection.collection?
|
|
85
|
+
# has_many: merge with the association's scope
|
|
86
|
+
parent.public_send(parent_association).merge(relation)
|
|
87
|
+
else
|
|
88
|
+
# has_one: scope by foreign key
|
|
89
|
+
relation.where(assoc_reflection.foreign_key => parent.id)
|
|
90
|
+
end
|
|
91
|
+
elsif entity_scope
|
|
92
|
+
relation.associated_with(entity_scope)
|
|
77
93
|
else
|
|
78
|
-
|
|
79
|
-
relation.where(assoc_reflection.foreign_key => parent.id)
|
|
94
|
+
relation
|
|
80
95
|
end
|
|
81
96
|
elsif entity_scope
|
|
82
97
|
# Entity scoping (multi-tenancy)
|
|
@@ -35,7 +35,7 @@ module Plutonium
|
|
|
35
35
|
def render_content(&)
|
|
36
36
|
div(
|
|
37
37
|
id: "sidebar-navigation-content",
|
|
38
|
-
data: {turbo_permanent: true},
|
|
38
|
+
data: {turbo_permanent: true, sidebar_target: "scroll"},
|
|
39
39
|
class: "overflow-y-auto py-5 px-3 h-full bg-[var(--pu-surface)] border-r border-[var(--pu-border)]",
|
|
40
40
|
&
|
|
41
41
|
)
|
data/lib/plutonium/version.rb
CHANGED
data/package.json
CHANGED
|
@@ -54,7 +54,30 @@ export default class extends Controller {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (this.modal) {
|
|
57
|
+
// Inside a <dialog> opened via showModal(), the dialog establishes its
|
|
58
|
+
// own containing block in the top layer. flatpickr's default positioning
|
|
59
|
+
// computes document coordinates but the calendar (appended to the
|
|
60
|
+
// dialog) interprets them relative to the dialog's box, placing the
|
|
61
|
+
// calendar far from the input. Append to the modal and reposition
|
|
62
|
+
// manually relative to the modal's bounding rect.
|
|
57
63
|
options.appendTo = this.modal;
|
|
64
|
+
options.position = (instance) => {
|
|
65
|
+
const input = instance.altInput || instance.input;
|
|
66
|
+
const inputRect = input.getBoundingClientRect();
|
|
67
|
+
const modalRect = this.modal.getBoundingClientRect();
|
|
68
|
+
const cal = instance.calendarContainer;
|
|
69
|
+
const calHeight = cal.offsetHeight;
|
|
70
|
+
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
71
|
+
const showAbove = spaceBelow < calHeight && inputRect.top > calHeight;
|
|
72
|
+
const top = showAbove
|
|
73
|
+
? inputRect.top - modalRect.top - calHeight - 2
|
|
74
|
+
: inputRect.bottom - modalRect.top + 2;
|
|
75
|
+
cal.style.top = `${top}px`;
|
|
76
|
+
cal.style.left = `${inputRect.left - modalRect.left}px`;
|
|
77
|
+
cal.style.right = "auto";
|
|
78
|
+
cal.classList.toggle("arrowTop", !showAbove);
|
|
79
|
+
cal.classList.toggle("arrowBottom", showAbove);
|
|
80
|
+
};
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
return options;
|
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Persists across controller reconnects so the value saved on
|
|
4
|
+
// turbo:before-render is still available on turbo:render, even though
|
|
5
|
+
// the <aside> hosting this controller is replaced during navigation.
|
|
6
|
+
let savedScrollTop = 0;
|
|
7
|
+
|
|
8
|
+
export default class extends Controller {
|
|
9
|
+
static targets = ["scroll"];
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
this.beforeRender = this.beforeRender.bind(this);
|
|
13
|
+
this.afterRender = this.afterRender.bind(this);
|
|
14
|
+
document.addEventListener("turbo:before-render", this.beforeRender);
|
|
15
|
+
document.addEventListener("turbo:render", this.afterRender);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
disconnect() {
|
|
19
|
+
document.removeEventListener("turbo:before-render", this.beforeRender);
|
|
20
|
+
document.removeEventListener("turbo:render", this.afterRender);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeRender() {
|
|
24
|
+
if (this.hasScrollTarget) savedScrollTop = this.scrollTarget.scrollTop;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterRender() {
|
|
28
|
+
if (this.hasScrollTarget) this.scrollTarget.scrollTop = savedScrollTop;
|
|
29
|
+
}
|
|
30
|
+
}
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plutonium
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.49.
|
|
4
|
+
version: 0.49.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Froelich
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-05-
|
|
10
|
+
date: 2026-05-06 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: zeitwerk
|
|
@@ -610,6 +610,8 @@ files:
|
|
|
610
610
|
- docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md
|
|
611
611
|
- docs/superpowers/plans/2026-04-14-plutonium-testing.md
|
|
612
612
|
- docs/superpowers/plans/2026-04-14-plutonium-testing.md.tasks.json
|
|
613
|
+
- docs/superpowers/plans/2026-05-06-multi-invite-model-support.md
|
|
614
|
+
- docs/superpowers/plans/2026-05-06-multi-invite-model-support.md.tasks.json
|
|
613
615
|
- docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md
|
|
614
616
|
- docs/superpowers/specs/2026-04-14-plutonium-testing-design.md
|
|
615
617
|
- esbuild.config.js
|
|
@@ -1142,7 +1144,7 @@ metadata:
|
|
|
1142
1144
|
homepage_uri: https://radioactive-labs.github.io/plutonium-core/
|
|
1143
1145
|
source_code_uri: https://github.com/radioactive-labs/plutonium-core
|
|
1144
1146
|
post_install_message: |
|
|
1145
|
-
⚠️ Plutonium 0.49.
|
|
1147
|
+
⚠️ Plutonium 0.49.1 — breaking change
|
|
1146
1148
|
|
|
1147
1149
|
Entity-scoped URL helpers and path params have been renamed from
|
|
1148
1150
|
`<entity>_scope_*` to `<entity>_scoped_*`.
|