plutonium 0.52.0 → 0.53.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-resource/SKILL.md +6 -4
- data/.claude/skills/plutonium-tenancy/SKILL.md +9 -4
- data/.claude/skills/plutonium-ui/SKILL.md +29 -5
- data/CHANGELOG.md +16 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +257 -11
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +39 -39
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_resource_header.html.erb +2 -1
- data/docs/.vitepress/config.ts +1 -0
- data/docs/guides/authentication.md +1 -1
- data/docs/guides/custom-actions.md +2 -1
- data/docs/guides/customizing-ui.md +6 -5
- data/docs/guides/multi-tenancy.md +6 -6
- data/docs/guides/theming.md +1 -1
- data/docs/public/images/components/avatar.png +0 -0
- data/docs/reference/auth/accounts.md +1 -1
- data/docs/reference/behavior/policies.md +1 -1
- data/docs/reference/configuration.md +61 -0
- data/docs/reference/resource/actions.md +2 -1
- data/docs/reference/resource/definition.md +4 -3
- data/docs/reference/tenancy/entity-scoping.md +12 -13
- data/docs/reference/ui/components.md +53 -0
- data/docs/reference/ui/forms.md +1 -1
- data/docs/reference/ui/pages.md +6 -5
- data/docs/superpowers/specs/2026-05-29-avatar-component-design.md +153 -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/lite/solid_errors/solid_errors_generator.rb +7 -3
- data/lib/plutonium/action/base.rb +43 -63
- data/lib/plutonium/configuration.rb +7 -0
- data/lib/plutonium/definition/actions.rb +10 -11
- data/lib/plutonium/definition/base.rb +29 -0
- data/lib/plutonium/helpers/assets_helper.rb +0 -30
- data/lib/plutonium/helpers/content_helper.rb +0 -44
- data/lib/plutonium/helpers/display_helper.rb +0 -62
- data/lib/plutonium/helpers/turbo_helper.rb +0 -4
- data/lib/plutonium/helpers.rb +0 -2
- data/lib/plutonium/resource/definition.rb +0 -42
- data/lib/plutonium/ui/action_button.rb +4 -3
- data/lib/plutonium/ui/avatar.rb +182 -0
- data/lib/plutonium/ui/component/kit.rb +2 -0
- data/lib/plutonium/ui/form/base.rb +16 -2
- data/lib/plutonium/ui/form/components/secure_association.rb +3 -2
- data/lib/plutonium/ui/form/resource.rb +58 -0
- data/lib/plutonium/ui/form/theme.rb +7 -3
- data/lib/plutonium/ui/grid/card.rb +10 -26
- data/lib/plutonium/ui/modal/base.rb +36 -1
- data/lib/plutonium/ui/modal/centered.rb +24 -6
- data/lib/plutonium/ui/modal/slideover.rb +26 -11
- data/lib/plutonium/ui/nav_user.rb +3 -23
- data/lib/plutonium/ui/page/edit.rb +6 -3
- data/lib/plutonium/ui/page/interactive_action.rb +5 -3
- data/lib/plutonium/ui/page/new.rb +6 -3
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/css/components.css +38 -1
- data/src/css/slim_select.css +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +165 -0
- data/src/js/controllers/register_controllers.js +2 -0
- data/src/js/controllers/remote_modal_controller.js +53 -19
- data/src/js/turbo/index.js +1 -0
- data/src/js/turbo/turbo_confirm.js +128 -0
- metadata +10 -6
- data/lib/plutonium/helpers/attachment_helper.rb +0 -73
- data/lib/plutonium/helpers/table_helper.rb +0 -35
- /data/lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/{password_changed.text.erb → change_password_notify.text.erb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1575a79af57aa89f49041cbdb1e31364cea08fb3d541398e095772470a3da6b8
|
|
4
|
+
data.tar.gz: a3d4dfebbec5836c9557c903393f7deebcfb17b94ca8d61ccb5a1dd1ffbd7aee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c6954af1d7e64be809c28e06140b5660f58e94ec254fe2960c4d0b28b615f515df84d107528899d791ab3cc5bf38ac18cf623aa4c00d3a683bf7b963b96bc47
|
|
7
|
+
data.tar.gz: a5f6a8ea0ad4e9fafc6559aac4b488c131a6c796c58506a49a49e8088dd3408cd04641ab4ffef357b66a373f88bd6c99476eb2d1cd7cb79eaaf2c993c6322cf5
|
|
@@ -725,9 +725,10 @@ class PostDefinition < ResourceDefinition
|
|
|
725
725
|
# nil (default) = auto (hidden for singular, shown for plural)
|
|
726
726
|
submit_and_continue false
|
|
727
727
|
|
|
728
|
-
# How :new / :edit render
|
|
728
|
+
# How :new / :edit + interactive actions render
|
|
729
729
|
# :slideover (default), :centered, or false (full pages)
|
|
730
|
-
|
|
730
|
+
# size: :sm / :md (default) / :lg / :xl / :auto / :full
|
|
731
|
+
modal :centered, size: :lg
|
|
731
732
|
|
|
732
733
|
# Titles
|
|
733
734
|
index_page_title "All Posts"
|
|
@@ -757,7 +758,7 @@ class PostDefinition < ResourceDefinition
|
|
|
757
758
|
end
|
|
758
759
|
```
|
|
759
760
|
|
|
760
|
-
`modal:`
|
|
761
|
+
`modal:` is the default for framework `:new` / `:edit` *and* every interactive action on this definition. Per-action `modal:` / `size:` overrides win.
|
|
761
762
|
|
|
762
763
|
## Metadata Panel (show page)
|
|
763
764
|
|
|
@@ -983,7 +984,8 @@ action :name,
|
|
|
983
984
|
confirmation: "Are you sure?",
|
|
984
985
|
turbo_frame: "_top",
|
|
985
986
|
route_options: {action: :foo},
|
|
986
|
-
modal: :slideover
|
|
987
|
+
modal: :slideover, # :slideover / :centered — overrides definition's modal mode
|
|
988
|
+
size: :lg # :sm / :md / :lg / :xl / :auto / :full — overrides definition's modal size
|
|
987
989
|
```
|
|
988
990
|
|
|
989
991
|
`Action#with(...)` — actions are frozen value objects; clone with overrides:
|
|
@@ -15,7 +15,7 @@ Cross-references back to [[plutonium-resource]] (models, definitions) and [[plut
|
|
|
15
15
|
|
|
16
16
|
## 🚨 Critical (read first)
|
|
17
17
|
|
|
18
|
-
- **Never bypass `default_relation_scope`.** Overriding `relation_scope` with `where(organization: ...)` or manual joins to the entity triggers `verify_default_relation_scope_applied!`. Make sure `default_relation_scope(relation)`
|
|
18
|
+
- **Never bypass `default_relation_scope`.** Overriding `relation_scope` with `where(organization: ...)` or manual joins to the entity triggers `verify_default_relation_scope_applied!`. Make sure the chain ends up calling `default_relation_scope(relation)` — explicitly, or via `super(relation)` (the framework base calls it).
|
|
19
19
|
- **Always declare an association path from model to entity.** Direct `belongs_to`, `has_one :through`, or a custom `associated_with_<entity>` scope. If `associated_with` can't resolve, Plutonium raises. Fix the **model**, not the policy.
|
|
20
20
|
- **Parent scoping beats entity scoping.** When a parent is present (nested resource), `default_relation_scope` scopes via the parent, NOT via `entity_scope`. Don't double-scope.
|
|
21
21
|
- **One level of nesting only.** Grandparent → parent → child nested routes are NOT supported. Use top-level routes for deeper relationships.
|
|
@@ -170,7 +170,12 @@ relation_scope { |r| r.joins(:project).where(projects: {organization_id: current
|
|
|
170
170
|
relation_scope { |r| r.where(published: true) }
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
**`default_relation_scope(relation)` must end up being called somewhere in the chain** — runtime verification just checks it was hit, not that you wrote it in this class. Both work:
|
|
174
|
+
|
|
175
|
+
- `default_relation_scope(relation).where(...)` — explicit, always safe
|
|
176
|
+
- `super(relation).where(...)` — `Plutonium::Resource::Policy`'s `relation_scope` block calls `default_relation_scope`, so chaining through `super` picks it up
|
|
177
|
+
|
|
178
|
+
Pick the one that reads better for the situation.
|
|
174
179
|
|
|
175
180
|
### Intentionally skipping
|
|
176
181
|
|
|
@@ -199,7 +204,7 @@ module AdminPortal
|
|
|
199
204
|
end
|
|
200
205
|
```
|
|
201
206
|
|
|
202
|
-
Routes become
|
|
207
|
+
Routes become `/<mount>/:organization_scoped/posts` (resolving to `/<mount>/42/posts` at request time — the entity id is the first path segment after the mount). Portal extracts `params[:organization_scoped]` and loads the entity automatically. The `_scoped` suffix on the param name avoids colliding with `params[:organization]` from a `belongs_to :organization` on child models.
|
|
203
208
|
|
|
204
209
|
### Custom strategy (subdomain, session, etc.)
|
|
205
210
|
|
|
@@ -235,7 +240,7 @@ entity_scope
|
|
|
235
240
|
|
|
236
241
|
- **Multiple associations to the same entity class.** E.g. `Match belongs_to :home_team, :away_team` both pointing at `Team`. Plutonium raises — override `scoped_entity_association` on the controller to pick one (`def scoped_entity_association = :home_team`).
|
|
237
242
|
- **`param_key` differs from association name.** Fine — Plutonium matches by **class**, not param key. `scope_to_entity Competition::Team, param_key: :team` works with `belongs_to :competition_team`.
|
|
238
|
-
- **Default `param_key` includes `_scoped` suffix.** `scope_to_entity Organization`
|
|
243
|
+
- **Default `param_key` includes `_scoped` suffix.** `scope_to_entity Organization` reads `params[:organization_scoped]` (not `params[:organization]`) so it doesn't collide with `params[:organization]` from a `belongs_to :organization` on child models. The URL itself is unchanged — the entity id is just the first path segment after the mount (`/<mount>/42/posts`). Pass `param_key:` only if you want a different param name in your controllers.
|
|
239
244
|
- **Forgetting compound uniqueness.** `validates :code, uniqueness: true` leaks across tenants. Use `uniqueness: {scope: :organization_id}`.
|
|
240
245
|
- **"Temporary" `where` bypass for debugging.** Use `skip_default_relation_scope!` explicitly. Never leave a `where` bypass in code.
|
|
241
246
|
|
|
@@ -390,6 +390,7 @@ Inside any `Plutonium::UI::Component::Base` (or any page/form/display):
|
|
|
390
390
|
PageHeader(title: "Dashboard", description: "...", actions: [...])
|
|
391
391
|
Panel(class: "mt-4") { p { "Content" } }
|
|
392
392
|
Block { TabList(items: tabs) }
|
|
393
|
+
Avatar(user) # profile image: src → Navii fallback → icon
|
|
393
394
|
EmptyCard("No items found")
|
|
394
395
|
ActionButton(action, url: "/posts/new")
|
|
395
396
|
DynaFrameHost(src: "/some/path", loading: :lazy)
|
|
@@ -401,6 +402,28 @@ TablePagination(pagy)
|
|
|
401
402
|
Breadcrumbs()
|
|
402
403
|
```
|
|
403
404
|
|
|
405
|
+
## Avatar
|
|
406
|
+
|
|
407
|
+
`Avatar(subject = nil, src: nil, size: :md, alt: nil, **attrs)` — profile image with a deterministic [Navii](https://navii.dev) fallback. Registered in the kit.
|
|
408
|
+
|
|
409
|
+
```ruby
|
|
410
|
+
Avatar(user) # Navii fallback seeded from the record
|
|
411
|
+
Avatar(user, src: :avatar) # user.avatar if present, else Navii fallback
|
|
412
|
+
Avatar(user, src: user.avatar) # pass the attachment/uploader/URL directly
|
|
413
|
+
Avatar("acme-team") # String subject = deterministic seed
|
|
414
|
+
Avatar("https://.../p.png") # URL-shaped subject is shown as the image
|
|
415
|
+
Avatar(src: avatar_url) # bare image, no subject/fallback
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
- **subject** (positional): record → PII-free hashed seed + default `alt` (display name); String → seed. A URL-shaped String (`http(s)://…` or `/…`) is routed to `src` (shown as the image), not used as a seed.
|
|
419
|
+
- **src**: a Symbol is sent to the subject (`:avatar` → `subject.avatar`, a **contract** — raises if absent); otherwise an ActiveStorage attachment, active_shrine/Shrine uploader, or URL string. ActiveStorage resolves via `helpers.url_for`; everything else via its own `#url`.
|
|
420
|
+
- **size**: `:xs 24 / :sm 32 / :md 40 / :lg 48 / :xl 64`, or a raw Integer.
|
|
421
|
+
- **Privacy**: the value sent to Navii is **always** a SHA256 hash — no ids, emails, or seed strings leave the app. Deterministic per subject.
|
|
422
|
+
- **Resolution order**: resolved `src` → Navii (from subject) → generic user icon.
|
|
423
|
+
- **Config**: `config.navii_host_url` (default `https://api.navii.dev`); the component appends `/avatar/:seed`.
|
|
424
|
+
|
|
425
|
+
🚨 Ejected shells: `Avatar` only shows a Navii avatar when `NavUser` is passed `record:`. The gem's `_resource_header.html.erb` passes `record: (current_user if current_user.respond_to?(:id))`; portals that **ejected** the header before this must re-eject (`rails g pu:eject:shell --dest=<portal>`) or add the `record:` line, otherwise they keep the icon fallback. Pass a record only — a String `current_user` (e.g. a guest) would otherwise be seeded as a literal identity.
|
|
426
|
+
|
|
404
427
|
## Custom Phlex components
|
|
405
428
|
|
|
406
429
|
```ruby
|
|
@@ -450,17 +473,18 @@ All pages inherit this. Modals and frame navigation work without special handlin
|
|
|
450
473
|
|
|
451
474
|
# Part 5 — Modals, Slideovers, Tabs
|
|
452
475
|
|
|
453
|
-
## Modal/slideover for `:new` / `:edit`
|
|
476
|
+
## Modal/slideover for `:new` / `:edit` + interactive actions
|
|
454
477
|
|
|
455
478
|
```ruby
|
|
456
479
|
class PostDefinition < ResourceDefinition
|
|
457
|
-
modal :slideover
|
|
458
|
-
# modal :centered
|
|
459
|
-
# modal
|
|
480
|
+
modal :slideover # default — slide-in panel from the right
|
|
481
|
+
# modal :centered # centered dialog
|
|
482
|
+
# modal :centered, size: :lg # centered, wider container
|
|
483
|
+
# modal false # full standalone page
|
|
460
484
|
end
|
|
461
485
|
```
|
|
462
486
|
|
|
463
|
-
|
|
487
|
+
Drives both framework `:new` / `:edit` and every interactive action on the definition. `size:` accepts `:sm`, `:md` (default), `:lg`, `:xl`, `:auto` (hugs content), or `:full`. Per-action `modal:` / `size:` overrides win. See [[plutonium-resource]] › Action Options.
|
|
464
488
|
|
|
465
489
|
## Tabs on the show page
|
|
466
490
|
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [0.53.0] - 2026-05-31
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- *(ui)* Add Avatar component with Navii fallback (#59)
|
|
6
|
+
|
|
7
|
+
### 🐛 Bug Fixes
|
|
8
|
+
|
|
9
|
+
- *(docs)* Correct broken cross-page anchors in guides
|
|
10
|
+
- *(docs)* Retract incorrect "never super" guidance in relation_scope
|
|
11
|
+
- *(docs)* Correct scoped-URL shape in multi-tenancy docs
|
|
12
|
+
- *(rodauth)* Match change_password_notify mailer template name
|
|
13
|
+
|
|
14
|
+
### 🚜 Refactor
|
|
15
|
+
|
|
16
|
+
- *(helpers)* Remove dead view helpers superseded by Phlex components
|
|
1
17
|
## [0.52.0] - 2026-05-21
|
|
2
18
|
|
|
3
19
|
### 🐛 Bug Fixes
|