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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5016bbce0d2ef39825a923b820797c304a848396853f334d94757f22a90a99e9
|
|
4
|
+
data.tar.gz: aa95cd0b1d994c093dd0e51587d5c95c3b0a527b0ca5a8f771be1a6f21fd9daa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6aeedc9109ad0ac05e2343981de71497ba08be96017e5af800bde70eb5035a96a027477ec2280ad78a1e9449c530e2c67290663efc28a2cc41f97c492056e1e9
|
|
7
|
+
data.tar.gz: 5b697b2d753c9f80443210c2169f2abe42642f0d36bb1f0aa909965da6317cc1e4c90406560d9bf0156457e5a01b9a009b38250c0aed134b09328d50cb47484f
|
|
@@ -46,6 +46,7 @@ rails generate pu:invites:install
|
|
|
46
46
|
|--------|---------|-------------|
|
|
47
47
|
| `--entity-model=NAME` | Entity | Entity model name for scoping |
|
|
48
48
|
| `--user-model=NAME` | User | User model name |
|
|
49
|
+
| `--invite-model=NAME` | `<EntityModel><UserModel>Invite` | Invite class name. Omit for single-flow apps; set per-invocation when running the generator more than once. |
|
|
49
50
|
| `--membership-model=NAME` | EntityUser | Membership join model |
|
|
50
51
|
| `--roles=ROLES` | member,admin | Comma-separated roles |
|
|
51
52
|
| `--rodauth=NAME` | user | Rodauth configuration for signup |
|
|
@@ -113,6 +114,46 @@ get "invitations/:token/signup", to: "invites/user_invitations#signup"
|
|
|
113
114
|
post "invitations/:token/signup", to: "invites/user_invitations#signup"
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
## Multiple invite flows in one app
|
|
118
|
+
|
|
119
|
+
A single app can run several independent invite flows side-by-side — for example, one for inviting customers to organizations and another for inviting funders to projects. Run `pu:invites:install` once per flow.
|
|
120
|
+
|
|
121
|
+
**Default derivation rule.** When `--invite_model` is omitted, the generator derives the class name as `<EntityModel><UserModel>Invite`. So with the defaults (`--entity_model=Organization --user_model=User`) the generated class is `Invites::OrganizationUserInvite` — there is no literal `UserInvite` default. Single-flow apps don't need to pass `--invite_model` at all.
|
|
122
|
+
|
|
123
|
+
Multi-flow apps typically vary `--entity_model` / `--user_model` per invocation; the derived names diverge automatically, so `--invite_model` is only needed when you want a custom class name.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
rails g pu:invites:install \
|
|
127
|
+
--entity_model=FunderOrganization \
|
|
128
|
+
--user_model=SpenderAccount \
|
|
129
|
+
--invite_model=FunderInvite
|
|
130
|
+
|
|
131
|
+
rails g pu:invites:install \
|
|
132
|
+
--entity_model=Project \
|
|
133
|
+
--user_model=Member \
|
|
134
|
+
--invite_model=ProjectInvite
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Each invocation creates an independent flow: model `Invites::FunderInvite` on `funder_invites`, controller `Invites::FunderInvitationsController` on `/funder_invitations/:token`, helper `funder_invitation_path`, etc. The shared `Invites::WelcomeController` accumulates each new class into its `invite_classes` array, so `pending_invite` checks all flows in priority order (first-match wins).
|
|
138
|
+
|
|
139
|
+
Override hooks at the model level:
|
|
140
|
+
- `def user_attribute; :spender_account; end` — when `belongs_to :spender_account` instead of `:user`.
|
|
141
|
+
- `def invite_entity_attribute; :funder_organization; end` — when `belongs_to :funder_organization` instead of `:entity`.
|
|
142
|
+
|
|
143
|
+
Override hooks at the controller level (auto-generated by the install generator, shown here so you understand what it emits and can tweak it):
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
# packages/invites/app/controllers/invites/welcome_controller.rb
|
|
147
|
+
def invite_classes
|
|
148
|
+
[::Invites::FunderInvite, ::Invites::ProjectInvite]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# packages/invites/app/controllers/invites/funder_invitations_controller.rb
|
|
152
|
+
def invitation_path_for(token)
|
|
153
|
+
funder_invitation_path(token: token)
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
116
157
|
## Connecting Invitables
|
|
117
158
|
|
|
118
159
|
Invitables are models that trigger invitations and get notified when they're accepted. Common examples:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [0.49.1] - 2026-05-06
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- *(invites)* Support multiple invite models per app
|
|
6
|
+
|
|
7
|
+
### 🐛 Bug Fixes
|
|
8
|
+
|
|
9
|
+
- *(policy)* Scope parent association only to matching relations
|
|
10
|
+
- *(ui)* Preserve sidebar scroll across Turbo navigations
|
|
11
|
+
- *(invites)* Add invite_entity_attribute hook for non-:entity invite models
|
|
12
|
+
- *(interactive_actions)* Restore default layout on direct loads
|
|
13
|
+
- *(invites)* Unblock acceptance + non-importmap apps
|
|
14
|
+
- *(interactive_actions)* Force text/html on failure response
|
|
15
|
+
- *(flatpickr)* Position calendar correctly inside modal dialogs
|
|
1
16
|
## [0.49.0] - 2026-05-04
|
|
2
17
|
|
|
3
18
|
### 🚀 Features
|
data/app/assets/plutonium.js
CHANGED
|
@@ -16884,6 +16884,21 @@ ${text2}</tr>
|
|
|
16884
16884
|
}
|
|
16885
16885
|
if (this.modal) {
|
|
16886
16886
|
options2.appendTo = this.modal;
|
|
16887
|
+
options2.position = (instance) => {
|
|
16888
|
+
const input = instance.altInput || instance.input;
|
|
16889
|
+
const inputRect = input.getBoundingClientRect();
|
|
16890
|
+
const modalRect = this.modal.getBoundingClientRect();
|
|
16891
|
+
const cal = instance.calendarContainer;
|
|
16892
|
+
const calHeight = cal.offsetHeight;
|
|
16893
|
+
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
16894
|
+
const showAbove = spaceBelow < calHeight && inputRect.top > calHeight;
|
|
16895
|
+
const top2 = showAbove ? inputRect.top - modalRect.top - calHeight - 2 : inputRect.bottom - modalRect.top + 2;
|
|
16896
|
+
cal.style.top = `${top2}px`;
|
|
16897
|
+
cal.style.left = `${inputRect.left - modalRect.left}px`;
|
|
16898
|
+
cal.style.right = "auto";
|
|
16899
|
+
cal.classList.toggle("arrowTop", !showAbove);
|
|
16900
|
+
cal.classList.toggle("arrowBottom", showAbove);
|
|
16901
|
+
};
|
|
16887
16902
|
}
|
|
16888
16903
|
return options2;
|
|
16889
16904
|
}
|
|
@@ -27674,7 +27689,27 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
|
|
|
27674
27689
|
};
|
|
27675
27690
|
|
|
27676
27691
|
// src/js/controllers/sidebar_controller.js
|
|
27692
|
+
var savedScrollTop = 0;
|
|
27677
27693
|
var sidebar_controller_default = class extends Controller {
|
|
27694
|
+
static targets = ["scroll"];
|
|
27695
|
+
connect() {
|
|
27696
|
+
this.beforeRender = this.beforeRender.bind(this);
|
|
27697
|
+
this.afterRender = this.afterRender.bind(this);
|
|
27698
|
+
document.addEventListener("turbo:before-render", this.beforeRender);
|
|
27699
|
+
document.addEventListener("turbo:render", this.afterRender);
|
|
27700
|
+
}
|
|
27701
|
+
disconnect() {
|
|
27702
|
+
document.removeEventListener("turbo:before-render", this.beforeRender);
|
|
27703
|
+
document.removeEventListener("turbo:render", this.afterRender);
|
|
27704
|
+
}
|
|
27705
|
+
beforeRender() {
|
|
27706
|
+
if (this.hasScrollTarget)
|
|
27707
|
+
savedScrollTop = this.scrollTarget.scrollTop;
|
|
27708
|
+
}
|
|
27709
|
+
afterRender() {
|
|
27710
|
+
if (this.hasScrollTarget)
|
|
27711
|
+
this.scrollTarget.scrollTop = savedScrollTop;
|
|
27712
|
+
}
|
|
27678
27713
|
};
|
|
27679
27714
|
|
|
27680
27715
|
// src/js/controllers/password_visibility_controller.js
|