plutonium 0.50.0 → 0.51.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 +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +572 -0
- data/.claude/skills/plutonium-auth/SKILL.md +163 -300
- data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
- data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
- data/.claude/skills/plutonium-testing/SKILL.md +6 -5
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +27 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1009 -1214
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +52 -51
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +37 -27
- data/docs/getting-started/index.md +22 -29
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +94 -463
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +94 -296
- data/docs/guides/custom-actions.md +121 -441
- data/docs/guides/index.md +22 -42
- data/docs/guides/multi-tenancy.md +116 -187
- data/docs/guides/nested-resources.md +103 -431
- data/docs/guides/search-filtering.md +123 -240
- data/docs/guides/testing.md +5 -4
- data/docs/guides/theming.md +157 -407
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +106 -425
- data/docs/guides/user-profile.md +76 -243
- data/docs/index.md +1 -1
- data/docs/reference/app/generators.md +517 -0
- data/docs/reference/app/index.md +158 -0
- data/docs/reference/app/packages.md +146 -0
- data/docs/reference/app/portals.md +377 -0
- data/docs/reference/auth/accounts.md +230 -0
- data/docs/reference/auth/index.md +88 -0
- data/docs/reference/auth/profile.md +185 -0
- data/docs/reference/behavior/controllers.md +395 -0
- data/docs/reference/behavior/index.md +22 -0
- data/docs/reference/behavior/interactions.md +341 -0
- data/docs/reference/behavior/policies.md +417 -0
- data/docs/reference/index.md +56 -49
- data/docs/reference/resource/actions.md +423 -0
- data/docs/reference/resource/definition.md +508 -0
- data/docs/reference/resource/index.md +50 -0
- data/docs/reference/resource/model.md +348 -0
- data/docs/reference/resource/query.md +305 -0
- data/docs/reference/tenancy/entity-scoping.md +361 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +393 -0
- data/docs/reference/tenancy/nested-resources.md +267 -0
- data/docs/reference/testing/index.md +287 -0
- data/docs/reference/ui/assets.md +400 -0
- data/docs/reference/ui/components.md +165 -0
- data/docs/reference/ui/displays.md +104 -0
- data/docs/reference/ui/forms.md +284 -0
- data/docs/reference/ui/index.md +30 -0
- data/docs/reference/ui/layouts.md +106 -0
- data/docs/reference/ui/pages.md +189 -0
- data/docs/reference/ui/tables.md +117 -0
- data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
- data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
- data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -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/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +1 -0
- data/lib/plutonium/definition/base.rb +1 -1
- data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
- data/lib/plutonium/helpers/turbo_helper.rb +11 -0
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
- data/lib/plutonium/resource/controller.rb +1 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
- data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
- data/lib/plutonium/resource/policy.rb +7 -0
- data/lib/plutonium/routing/mapper_extensions.rb +15 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/form/base.rb +6 -2
- data/lib/plutonium/ui/form/components/json.rb +58 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
- data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/grid/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +0 -7
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +8 -0
- data/lib/tasks/release.rake +15 -1
- data/package.json +10 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +553 -543
- metadata +44 -33
- data/.claude/skills/plutonium-assets/SKILL.md +0 -512
- data/.claude/skills/plutonium-controller/SKILL.md +0 -396
- data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
- data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
- data/.claude/skills/plutonium-forms/SKILL.md +0 -465
- data/.claude/skills/plutonium-installation/SKILL.md +0 -331
- data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
- data/.claude/skills/plutonium-invites/SKILL.md +0 -408
- data/.claude/skills/plutonium-model/SKILL.md +0 -440
- data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
- data/.claude/skills/plutonium-package/SKILL.md +0 -198
- data/.claude/skills/plutonium-policy/SKILL.md +0 -456
- data/.claude/skills/plutonium-portal/SKILL.md +0 -410
- data/.claude/skills/plutonium-views/SKILL.md +0 -651
- data/docs/reference/assets/index.md +0 -496
- data/docs/reference/controller/index.md +0 -412
- data/docs/reference/definition/actions.md +0 -462
- data/docs/reference/definition/fields.md +0 -383
- data/docs/reference/definition/index.md +0 -326
- data/docs/reference/definition/query.md +0 -351
- data/docs/reference/generators/index.md +0 -648
- data/docs/reference/interaction/index.md +0 -449
- data/docs/reference/model/features.md +0 -248
- data/docs/reference/model/index.md +0 -218
- data/docs/reference/policy/index.md +0 -456
- data/docs/reference/portal/index.md +0 -379
- data/docs/reference/views/forms.md +0 -411
- data/docs/reference/views/index.md +0 -544
data/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radioactive-labs/plutonium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/js/core.js",
|
|
@@ -25,27 +25,27 @@
|
|
|
25
25
|
"@uppy/dashboard": "^4.1.3",
|
|
26
26
|
"@uppy/image-editor": "^3.2.1",
|
|
27
27
|
"@uppy/xhr-upload": "^4.2.3",
|
|
28
|
-
"dompurify": "^3.
|
|
28
|
+
"dompurify": "^3.4.3",
|
|
29
29
|
"lodash.debounce": "^4.0.8",
|
|
30
30
|
"marked": "^15.0.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@tailwindcss/forms": "^0.5.10",
|
|
34
|
-
"@tailwindcss/postcss": "^4.0
|
|
34
|
+
"@tailwindcss/postcss": "^4.3.0",
|
|
35
35
|
"@tailwindcss/typography": "^0.5.16",
|
|
36
36
|
"chokidar-cli": "^3.0.0",
|
|
37
37
|
"concurrently": "^8.2.2",
|
|
38
38
|
"cssnano": "^7.0.2",
|
|
39
|
-
"esbuild": "^0.
|
|
39
|
+
"esbuild": "^0.28.0",
|
|
40
40
|
"esbuild-plugin-manifest": "^1.0.3",
|
|
41
41
|
"flowbite-typography": "^1.0.5",
|
|
42
|
-
"mermaid": "^11.
|
|
43
|
-
"postcss": "^8.
|
|
44
|
-
"postcss-cli": "^11.0.
|
|
42
|
+
"mermaid": "^11.15.0",
|
|
43
|
+
"postcss": "^8.5.14",
|
|
44
|
+
"postcss-cli": "^11.0.1",
|
|
45
45
|
"postcss-hash": "^3.0.0",
|
|
46
|
-
"postcss-import": "^16.1.
|
|
47
|
-
"tailwindcss": "^4.0
|
|
48
|
-
"vitepress": "^1.4
|
|
46
|
+
"postcss-import": "^16.1.1",
|
|
47
|
+
"tailwindcss": "^4.3.0",
|
|
48
|
+
"vitepress": "^1.6.4",
|
|
49
49
|
"vitepress-plugin-mermaid": "^2.0.17"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
data/src/css/slim_select.css
CHANGED
|
@@ -151,6 +151,10 @@
|
|
|
151
151
|
@apply absolute flex h-auto flex-col w-auto max-h-72 border transition-all duration-200 opacity-0 z-[10000] overflow-hidden;
|
|
152
152
|
background-color: var(--pu-surface);
|
|
153
153
|
border-color: var(--pu-border);
|
|
154
|
+
/* Default text color for everything inside the panel — covers the
|
|
155
|
+
"No Results" text and any other slim-select chrome that doesn't
|
|
156
|
+
declare its own color rule. Specific rules below still win. */
|
|
157
|
+
color: var(--pu-text);
|
|
154
158
|
box-shadow: var(--pu-shadow-md);
|
|
155
159
|
transform: scaleY(0);
|
|
156
160
|
transform-origin: top;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
|
3
3
|
// Connects to data-controller="slim-select"
|
|
4
|
+
//
|
|
5
|
+
// Optional values (used by the typeahead-capable ResourceSelect):
|
|
6
|
+
// typeahead-url — backend endpoint that returns
|
|
7
|
+
// {results: [{value, label}, ...], has_more: bool}.
|
|
8
|
+
// When present, SlimSelect's built-in client-side
|
|
9
|
+
// filter is replaced by a debounced fetch through
|
|
10
|
+
// this URL.
|
|
4
11
|
export default class extends Controller {
|
|
12
|
+
static values = {
|
|
13
|
+
typeaheadUrl: String,
|
|
14
|
+
typeaheadDebounceMs: { type: Number, default: 200 }
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
connect() {
|
|
6
18
|
if (this.slimSelect) return;
|
|
7
19
|
|
|
@@ -47,9 +59,18 @@ export default class extends Controller {
|
|
|
47
59
|
settings.openPosition = "auto";
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
const events = {};
|
|
63
|
+
|
|
64
|
+
if (this.hasTypeaheadUrlValue && this.typeaheadUrlValue) {
|
|
65
|
+
// Replace SlimSelect's client-side filter with a server fetch.
|
|
66
|
+
// Returns the SlimSelect data array shape: {value, text}.
|
|
67
|
+
events.search = (search, currentData) => this.#typeaheadFetch(search, currentData);
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
this.slimSelect = new SlimSelect({
|
|
51
71
|
select: this.element,
|
|
52
72
|
settings: settings,
|
|
73
|
+
events: events,
|
|
53
74
|
});
|
|
54
75
|
|
|
55
76
|
// Add event listeners for better positioning
|
|
@@ -177,6 +198,46 @@ export default class extends Controller {
|
|
|
177
198
|
this.#cleanupSlimSelect();
|
|
178
199
|
}
|
|
179
200
|
|
|
201
|
+
// Server-driven search. SlimSelect calls events.search on each
|
|
202
|
+
// keystroke; we debounce so that rapid typing produces a single
|
|
203
|
+
// request, and abort any in-flight fetch when a newer one starts.
|
|
204
|
+
// Returns a Promise resolving to either a DataArray (rendered as
|
|
205
|
+
// options) or a string (rendered as the no-results label).
|
|
206
|
+
#typeaheadFetch(search, _currentData) {
|
|
207
|
+
if (this._typeaheadDebounce) clearTimeout(this._typeaheadDebounce);
|
|
208
|
+
if (this._typeaheadAbort) this._typeaheadAbort.abort();
|
|
209
|
+
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
this._typeaheadDebounce = setTimeout(() => {
|
|
212
|
+
this._typeaheadAbort = new AbortController();
|
|
213
|
+
this.#performTypeaheadFetch(search, this._typeaheadAbort.signal).then(resolve);
|
|
214
|
+
}, this.typeaheadDebounceMsValue);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async #performTypeaheadFetch(search, signal) {
|
|
219
|
+
const url = new URL(this.typeaheadUrlValue, window.location.origin);
|
|
220
|
+
url.searchParams.set("q", search || "");
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const res = await fetch(url.toString(), {
|
|
224
|
+
headers: { Accept: "application/json" },
|
|
225
|
+
signal: signal,
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) return "Search failed";
|
|
228
|
+
const json = await res.json();
|
|
229
|
+
const results = Array.isArray(json.results) ? json.results : [];
|
|
230
|
+
return results.map((row) => ({
|
|
231
|
+
value: String(row.value ?? ""),
|
|
232
|
+
text: String(row.label ?? ""),
|
|
233
|
+
}));
|
|
234
|
+
} catch (e) {
|
|
235
|
+
if (e.name === "AbortError") return [];
|
|
236
|
+
console.warn("[slim-select] typeahead error", e);
|
|
237
|
+
return "Search failed";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
180
241
|
#handleMorph() {
|
|
181
242
|
if (!this.element.isConnected) return;
|
|
182
243
|
|
|
@@ -6,3 +6,36 @@ Turbo.StreamActions.redirect = function () {
|
|
|
6
6
|
const url = this.getAttribute("url")
|
|
7
7
|
Turbo.visit(url)
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
// Closes the <dialog> rendered inside the targeted turbo-frame and
|
|
11
|
+
// empties the frame so the dialog can be re-opened later. Used by the
|
|
12
|
+
// stacked-modal flow: after a successful create inside the secondary
|
|
13
|
+
// modal, the server tells the browser to dismiss it.
|
|
14
|
+
Turbo.StreamActions.close_frame = function () {
|
|
15
|
+
const frameId = this.getAttribute("target")
|
|
16
|
+
if (!frameId) return
|
|
17
|
+
|
|
18
|
+
const frame = document.getElementById(frameId)
|
|
19
|
+
if (!frame) return
|
|
20
|
+
|
|
21
|
+
const dialog = frame.querySelector("dialog")
|
|
22
|
+
if (dialog && typeof dialog.close === "function") dialog.close()
|
|
23
|
+
|
|
24
|
+
// Clearing the frame's content keeps a future visit to the same URL
|
|
25
|
+
// re-fetching (turbo would otherwise treat the frame as cached).
|
|
26
|
+
frame.innerHTML = ""
|
|
27
|
+
frame.removeAttribute("src")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Reloads the targeted turbo-frame from its current src. Used after a
|
|
31
|
+
// secondary-modal action mutates data the primary modal depends on
|
|
32
|
+
// (e.g. a newly created association option) so the primary re-renders.
|
|
33
|
+
Turbo.StreamActions.reload_frame = function () {
|
|
34
|
+
const frameId = this.getAttribute("target")
|
|
35
|
+
if (!frameId) return
|
|
36
|
+
|
|
37
|
+
const frame = document.getElementById(frameId)
|
|
38
|
+
if (!frame || typeof frame.reload !== "function") return
|
|
39
|
+
|
|
40
|
+
frame.reload()
|
|
41
|
+
}
|