plutonium 0.50.0 → 0.52.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 +574 -0
- data/.claude/skills/plutonium-auth/SKILL.md +167 -302
- 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 +674 -0
- data/.claude/skills/plutonium-testing/SKILL.md +9 -6
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +44 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1010 -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 +38 -29
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -57
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +98 -462
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +93 -298
- data/docs/guides/custom-actions.md +126 -441
- data/docs/guides/customizing-ui.md +258 -0
- data/docs/guides/index.md +49 -52
- data/docs/guides/multi-tenancy.md +123 -186
- data/docs/guides/nested-resources.md +137 -396
- data/docs/guides/search-filtering.md +127 -238
- data/docs/guides/testing.md +10 -5
- data/docs/guides/theming.md +168 -405
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +112 -425
- data/docs/guides/user-profile.md +82 -241
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- 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 +229 -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 +67 -48
- 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 +368 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +400 -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 +121 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -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/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -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/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +45 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- 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 +30 -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 +23 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- 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 +5 -0
- data/lib/plutonium/ui/form/base.rb +23 -3
- 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 +103 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/form/theme.rb +1 -1
- 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/edit.rb +1 -1
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/page/new.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -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 +13 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +661 -544
- metadata +86 -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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section sl-section">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<div class="pu-eyebrow">{{ eyebrow }}</div>
|
|
5
|
+
<h1 class="sl-h1">{{ title }}</h1>
|
|
6
|
+
<p class="sl-lede">{{ lede }}</p>
|
|
7
|
+
|
|
8
|
+
<div class="sl-grid">
|
|
9
|
+
<div class="sl-rail">
|
|
10
|
+
<template v-if="mode === 'numbered'">
|
|
11
|
+
<a
|
|
12
|
+
v-for="(step, i) in rail"
|
|
13
|
+
:key="i"
|
|
14
|
+
:href="step.link"
|
|
15
|
+
:class="['sl-step', { 'sl-step--link': step.link }]"
|
|
16
|
+
>
|
|
17
|
+
<span class="sl-num">{{ i + 1 }}</span>
|
|
18
|
+
<span class="sl-step-body">
|
|
19
|
+
<span class="sl-step-name">{{ step.name }}</span>
|
|
20
|
+
<span v-if="step.desc" class="sl-step-desc">{{ step.desc }}</span>
|
|
21
|
+
</span>
|
|
22
|
+
</a>
|
|
23
|
+
</template>
|
|
24
|
+
<template v-else>
|
|
25
|
+
<div v-for="grp in rail" :key="grp.group" class="sl-group">
|
|
26
|
+
<div class="sl-group-name">{{ grp.group }}</div>
|
|
27
|
+
<a
|
|
28
|
+
v-for="item in grp.items"
|
|
29
|
+
:key="item.name"
|
|
30
|
+
:href="item.link"
|
|
31
|
+
class="sl-step sl-step--link sl-step--cat"
|
|
32
|
+
>
|
|
33
|
+
<span class="sl-step-body">
|
|
34
|
+
<span class="sl-step-name">{{ item.name }}</span>
|
|
35
|
+
<span v-if="item.desc" class="sl-step-desc">{{ item.desc }}</span>
|
|
36
|
+
</span>
|
|
37
|
+
<IconArrowRight class="sl-step-arrow" :size="16" :stroke-width="2" />
|
|
38
|
+
</a>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<aside class="sl-aside">
|
|
44
|
+
<div v-for="block in sidebar" :key="block.heading" class="sl-aside-block">
|
|
45
|
+
<h4 class="sl-aside-heading">{{ block.heading }}</h4>
|
|
46
|
+
<ul>
|
|
47
|
+
<li v-for="item in block.items" :key="item.label">
|
|
48
|
+
<a :href="item.href">{{ item.label }}</a>
|
|
49
|
+
<span v-if="item.note" class="sl-aside-note"> — {{ item.note }}</span>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
</aside>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup>
|
|
60
|
+
import { IconArrowRight } from "@tabler/icons-vue"
|
|
61
|
+
|
|
62
|
+
defineProps({
|
|
63
|
+
eyebrow: { type: String, required: true },
|
|
64
|
+
title: { type: String, required: true },
|
|
65
|
+
lede: { type: String, required: true },
|
|
66
|
+
rail: { type: Array, required: true },
|
|
67
|
+
mode: { type: String, default: "numbered", validator: v => ["numbered", "categorized"].includes(v) },
|
|
68
|
+
sidebar: { type: Array, default: () => [] },
|
|
69
|
+
})
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<style scoped>
|
|
73
|
+
.sl-section { padding: 64px 24px 96px; }
|
|
74
|
+
.sl-h1 { font-size: 36px; letter-spacing: -0.025em; margin: 0 0 12px; color: var(--pu-text); }
|
|
75
|
+
.sl-lede { font-size: 16px; color: var(--pu-text-muted); max-width: 640px; margin: 0 0 40px; line-height: 1.55; }
|
|
76
|
+
.sl-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 48px; }
|
|
77
|
+
.sl-rail { border-left: 2px solid var(--pu-accent); padding-left: 24px; }
|
|
78
|
+
.sl-group + .sl-group { margin-top: 22px; }
|
|
79
|
+
.sl-group-name {
|
|
80
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
|
|
81
|
+
color: var(--pu-accent); font-weight: 600; margin-bottom: 8px;
|
|
82
|
+
}
|
|
83
|
+
.sl-step {
|
|
84
|
+
display: flex; gap: 12px; align-items: flex-start;
|
|
85
|
+
padding: 12px 0; border-bottom: 1px solid var(--pu-border-soft);
|
|
86
|
+
color: var(--pu-text); text-decoration: none;
|
|
87
|
+
}
|
|
88
|
+
.sl-step:last-child { border-bottom: none; }
|
|
89
|
+
.sl-step--cat { justify-content: space-between; align-items: center; }
|
|
90
|
+
.sl-step--link:hover .sl-step-name { color: var(--pu-accent); }
|
|
91
|
+
.sl-step--link:hover .sl-step-arrow { transform: translateX(2px); color: var(--pu-accent); }
|
|
92
|
+
.sl-num {
|
|
93
|
+
flex-shrink: 0; width: 24px; height: 24px; line-height: 24px; text-align: center;
|
|
94
|
+
background: var(--pu-accent); color: #fff; border-radius: 50%;
|
|
95
|
+
font-size: 11px; font-weight: 600;
|
|
96
|
+
}
|
|
97
|
+
.sl-step-body { display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
|
98
|
+
.sl-step-name { font-weight: 600; font-size: 14px; }
|
|
99
|
+
.sl-step-desc { font-size: 12.5px; color: var(--pu-text-muted); }
|
|
100
|
+
.sl-step-arrow { color: var(--pu-text-faint); transition: transform 0.15s ease, color 0.15s ease; flex-shrink: 0; }
|
|
101
|
+
|
|
102
|
+
.sl-aside-block + .sl-aside-block { margin-top: 28px; }
|
|
103
|
+
.sl-aside-heading {
|
|
104
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
|
|
105
|
+
color: var(--pu-text-faint); margin: 0 0 10px; font-weight: 600;
|
|
106
|
+
}
|
|
107
|
+
.sl-aside ul { list-style: none; padding: 0; margin: 0; font-size: 14px; line-height: 1.85; }
|
|
108
|
+
.sl-aside a { color: var(--pu-accent); text-decoration: none; font-weight: 500; }
|
|
109
|
+
.sl-aside a:hover { text-decoration: underline; }
|
|
110
|
+
.sl-aside-note { color: var(--pu-text-muted); font-weight: 400; }
|
|
111
|
+
|
|
112
|
+
@media (max-width: 900px) {
|
|
113
|
+
.sl-grid { grid-template-columns: 1fr; gap: 32px; }
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -418,3 +418,147 @@ Brand Colors:
|
|
|
418
418
|
line-height: 1.5;
|
|
419
419
|
padding-bottom: 4px;
|
|
420
420
|
}
|
|
421
|
+
|
|
422
|
+
/* ===== Public-pages design tokens (2026-05) ===== */
|
|
423
|
+
:root {
|
|
424
|
+
--pu-bg-dark: #0d1117;
|
|
425
|
+
--pu-bg-dark-2: #161b22;
|
|
426
|
+
--pu-bg-light: #ffffff;
|
|
427
|
+
--pu-bg-band: #fafafa;
|
|
428
|
+
--pu-border: #e0e0e0;
|
|
429
|
+
--pu-border-soft: #ececec;
|
|
430
|
+
--pu-text: #1a1a1a;
|
|
431
|
+
--pu-text-muted: #666666;
|
|
432
|
+
--pu-text-faint: #888888;
|
|
433
|
+
--pu-accent: #d33;
|
|
434
|
+
--pu-accent-soft: #fff7f7;
|
|
435
|
+
--pu-success-bg: #ecf9f1;
|
|
436
|
+
--pu-success-fg: #0a7c3f;
|
|
437
|
+
--pu-warn-bg: #fdf3e6;
|
|
438
|
+
--pu-warn-fg: #a86b00;
|
|
439
|
+
--pu-term-prompt: #7ee787;
|
|
440
|
+
--pu-term-cursor: #58a6ff;
|
|
441
|
+
--pu-term-text: #e6edf3;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.dark {
|
|
445
|
+
--pu-bg-light: #0d1117;
|
|
446
|
+
--pu-bg-band: #161b22;
|
|
447
|
+
--pu-text: #e6edf3;
|
|
448
|
+
--pu-text-muted: #9da7b1;
|
|
449
|
+
--pu-text-faint: #6e7681;
|
|
450
|
+
--pu-border: #30363d;
|
|
451
|
+
--pu-border-soft: #21262d;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Eyebrow */
|
|
455
|
+
.pu-eyebrow {
|
|
456
|
+
font-size: 11px;
|
|
457
|
+
text-transform: uppercase;
|
|
458
|
+
letter-spacing: 0.1em;
|
|
459
|
+
color: var(--pu-accent);
|
|
460
|
+
font-weight: 600;
|
|
461
|
+
margin-bottom: 8px;
|
|
462
|
+
}
|
|
463
|
+
.pu-eyebrow--muted { color: var(--pu-text-faint); }
|
|
464
|
+
|
|
465
|
+
/* Buttons */
|
|
466
|
+
.pu-btn {
|
|
467
|
+
display: inline-block;
|
|
468
|
+
padding: 10px 18px;
|
|
469
|
+
border-radius: 6px;
|
|
470
|
+
font-size: 14px;
|
|
471
|
+
font-weight: 500;
|
|
472
|
+
text-decoration: none;
|
|
473
|
+
transition: opacity 0.15s ease;
|
|
474
|
+
}
|
|
475
|
+
.pu-btn:hover { opacity: 0.85; }
|
|
476
|
+
.pu-btn:focus-visible {
|
|
477
|
+
outline: 2px solid var(--pu-accent);
|
|
478
|
+
outline-offset: 2px;
|
|
479
|
+
}
|
|
480
|
+
.pu-btn-primary { background: var(--pu-accent); color: #ffffff; }
|
|
481
|
+
.pu-btn-ghost {
|
|
482
|
+
border: 1px solid currentColor;
|
|
483
|
+
color: var(--pu-text);
|
|
484
|
+
opacity: 0.85;
|
|
485
|
+
}
|
|
486
|
+
.pu-btn-ghost.on-dark { color: #e6edf3; border-color: rgba(255,255,255,0.25); }
|
|
487
|
+
|
|
488
|
+
/* Terminal block */
|
|
489
|
+
.pu-term {
|
|
490
|
+
background: var(--pu-bg-dark);
|
|
491
|
+
color: var(--pu-term-text);
|
|
492
|
+
border-radius: 8px;
|
|
493
|
+
padding: 16px 18px;
|
|
494
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
495
|
+
font-size: 13px;
|
|
496
|
+
line-height: 1.7;
|
|
497
|
+
overflow-x: auto;
|
|
498
|
+
}
|
|
499
|
+
.pu-term--inline { padding: 12px 14px; font-size: 12.5px; }
|
|
500
|
+
.pu-term .prompt { color: var(--pu-term-prompt); }
|
|
501
|
+
.pu-term .dim { opacity: 0.55; }
|
|
502
|
+
.pu-term-cursor {
|
|
503
|
+
background: var(--pu-term-cursor);
|
|
504
|
+
display: inline-block;
|
|
505
|
+
width: 7px;
|
|
506
|
+
height: 13px;
|
|
507
|
+
vertical-align: text-bottom;
|
|
508
|
+
animation: pu-blink 1s steps(2) infinite;
|
|
509
|
+
}
|
|
510
|
+
@keyframes pu-blink { 50% { opacity: 0; } }
|
|
511
|
+
@media (prefers-reduced-motion: reduce) {
|
|
512
|
+
.pu-term-cursor { animation: none; }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Section frames */
|
|
516
|
+
.pu-section {
|
|
517
|
+
padding: 64px 24px;
|
|
518
|
+
}
|
|
519
|
+
.pu-section--dark {
|
|
520
|
+
background: var(--pu-bg-dark);
|
|
521
|
+
color: var(--pu-term-text);
|
|
522
|
+
}
|
|
523
|
+
.pu-section--band {
|
|
524
|
+
background: var(--pu-bg-band);
|
|
525
|
+
}
|
|
526
|
+
.pu-section .pu-section-inner {
|
|
527
|
+
max-width: 1100px;
|
|
528
|
+
margin: 0 auto;
|
|
529
|
+
}
|
|
530
|
+
.pu-section-title {
|
|
531
|
+
font-size: 28px;
|
|
532
|
+
letter-spacing: -0.02em;
|
|
533
|
+
margin: 0 0 24px;
|
|
534
|
+
color: var(--pu-text);
|
|
535
|
+
}
|
|
536
|
+
.pu-section--dark .pu-section-title { color: var(--pu-term-text); }
|
|
537
|
+
|
|
538
|
+
.vp-doc img:not(a img),
|
|
539
|
+
img.pu-zoomable { cursor: zoom-in; }
|
|
540
|
+
.medium-zoom-overlay { z-index: 100; }
|
|
541
|
+
.medium-zoom-image--opened { z-index: 101; }
|
|
542
|
+
|
|
543
|
+
.pu-zoom-close {
|
|
544
|
+
position: fixed;
|
|
545
|
+
top: 16px;
|
|
546
|
+
right: 16px;
|
|
547
|
+
z-index: 102;
|
|
548
|
+
width: 40px;
|
|
549
|
+
height: 40px;
|
|
550
|
+
border: 1px solid var(--vp-c-divider);
|
|
551
|
+
background: var(--vp-c-bg-soft);
|
|
552
|
+
color: var(--vp-c-text-1);
|
|
553
|
+
border-radius: 50%;
|
|
554
|
+
font-size: 24px;
|
|
555
|
+
line-height: 1;
|
|
556
|
+
cursor: pointer;
|
|
557
|
+
display: inline-flex;
|
|
558
|
+
align-items: center;
|
|
559
|
+
justify-content: center;
|
|
560
|
+
padding: 0;
|
|
561
|
+
transition: background 0.15s ease, transform 0.15s ease;
|
|
562
|
+
}
|
|
563
|
+
.pu-zoom-close:hover { background: var(--vp-c-bg-mute); transform: scale(1.05); }
|
|
564
|
+
.pu-zoom-close:focus-visible { outline: 2px solid var(--vp-c-brand-1); outline-offset: 2px; }
|
|
@@ -1,4 +1,61 @@
|
|
|
1
1
|
import DefaultTheme from "vitepress/theme"
|
|
2
|
+
import { onMounted, watch, nextTick } from "vue"
|
|
3
|
+
import { useRoute } from "vitepress"
|
|
4
|
+
import mediumZoom from "medium-zoom"
|
|
2
5
|
import "./custom.css"
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
import HomeHero from "./components/HomeHero.vue"
|
|
8
|
+
import HomeStopWriting from "./components/HomeStopWriting.vue"
|
|
9
|
+
import HomePillars from "./components/HomePillars.vue"
|
|
10
|
+
import HomeWalkthrough from "./components/HomeWalkthrough.vue"
|
|
11
|
+
import HomeAudienceSplit from "./components/HomeAudienceSplit.vue"
|
|
12
|
+
import HomeInTheBox from "./components/HomeInTheBox.vue"
|
|
13
|
+
import HomeCta from "./components/HomeCta.vue"
|
|
14
|
+
import SectionLanding from "./components/SectionLanding.vue"
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
extends: DefaultTheme,
|
|
18
|
+
enhanceApp({ app }) {
|
|
19
|
+
app.component("HomeHero", HomeHero)
|
|
20
|
+
app.component("HomeStopWriting", HomeStopWriting)
|
|
21
|
+
app.component("HomePillars", HomePillars)
|
|
22
|
+
app.component("HomeWalkthrough", HomeWalkthrough)
|
|
23
|
+
app.component("HomeAudienceSplit", HomeAudienceSplit)
|
|
24
|
+
app.component("HomeInTheBox", HomeInTheBox)
|
|
25
|
+
app.component("HomeCta", HomeCta)
|
|
26
|
+
app.component("SectionLanding", SectionLanding)
|
|
27
|
+
},
|
|
28
|
+
setup() {
|
|
29
|
+
const route = useRoute()
|
|
30
|
+
|
|
31
|
+
let closeBtn: HTMLButtonElement | null = null
|
|
32
|
+
|
|
33
|
+
const attachCloseButton = (z: ReturnType<typeof mediumZoom>) => {
|
|
34
|
+
z.on("opened", () => {
|
|
35
|
+
const overlay = document.querySelector<HTMLElement>(".medium-zoom-overlay")
|
|
36
|
+
if (!overlay) return
|
|
37
|
+
closeBtn = document.createElement("button")
|
|
38
|
+
closeBtn.type = "button"
|
|
39
|
+
closeBtn.setAttribute("aria-label", "Close")
|
|
40
|
+
closeBtn.className = "pu-zoom-close"
|
|
41
|
+
closeBtn.innerHTML = "×"
|
|
42
|
+
closeBtn.addEventListener("click", () => z.close())
|
|
43
|
+
document.body.appendChild(closeBtn)
|
|
44
|
+
})
|
|
45
|
+
z.on("close", () => {
|
|
46
|
+
closeBtn?.remove()
|
|
47
|
+
closeBtn = null
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const zoom = () => {
|
|
52
|
+
const z = mediumZoom(".vp-doc img:not(a img), img.pu-zoomable", {
|
|
53
|
+
background: "var(--vp-c-bg)",
|
|
54
|
+
margin: 16,
|
|
55
|
+
})
|
|
56
|
+
attachCloseButton(z)
|
|
57
|
+
}
|
|
58
|
+
onMounted(() => nextTick(zoom))
|
|
59
|
+
watch(() => route.path, () => nextTick(zoom))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,57 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
### Existing Application
|
|
36
|
-
|
|
37
|
-
Adding Plutonium to an existing Rails app requires a few more steps but is fully supported.
|
|
38
|
-
|
|
39
|
-
[Continue to Installation →](./installation#existing-application)
|
|
40
|
-
|
|
41
|
-
### Tutorial
|
|
42
|
-
|
|
43
|
-
Want to learn by building? Follow our step-by-step tutorial to create a complete blog application.
|
|
44
|
-
|
|
45
|
-
[Start the Tutorial →](./tutorial/)
|
|
46
|
-
|
|
47
|
-
## Next Steps
|
|
48
|
-
|
|
49
|
-
After installation, you'll typically:
|
|
50
|
-
|
|
51
|
-
1. **Create a Feature Package** - Organize your business logic
|
|
52
|
-
2. **Generate Resources** - Create your models and scaffolds
|
|
53
|
-
3. **Create a Portal** - Set up the web interface
|
|
54
|
-
4. **Connect Resources** - Make resources accessible through the portal
|
|
55
|
-
5. **Customize** - Override defaults as needed
|
|
56
|
-
|
|
57
|
-
Each of these steps is covered in detail in the [Tutorial](./tutorial/).
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
sidebar: false
|
|
4
|
+
aside: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<SectionLanding
|
|
8
|
+
eyebrow="Getting Started"
|
|
9
|
+
title="Learn Plutonium by building."
|
|
10
|
+
lede="Walk the path top to bottom, or skip to the part you need."
|
|
11
|
+
mode="numbered"
|
|
12
|
+
:rail="[
|
|
13
|
+
{ name: 'Project setup', desc: 'Bootstrap a Rails app with the Plutonium template.', link: '/plutonium-core/getting-started/tutorial/01-setup' },
|
|
14
|
+
{ name: 'First resource', desc: 'Model, definition, scaffold, connect to a portal.', link: '/plutonium-core/getting-started/tutorial/02-first-resource' },
|
|
15
|
+
{ name: 'Authentication', desc: 'Add Rodauth with login + signup.', link: '/plutonium-core/getting-started/tutorial/03-authentication' },
|
|
16
|
+
{ name: 'Authorization', desc: 'ActionPolicy-scoped resource access.', link: '/plutonium-core/getting-started/tutorial/04-authorization' },
|
|
17
|
+
{ name: 'Custom actions', desc: 'Add a domain-specific action to a resource.', link: '/plutonium-core/getting-started/tutorial/05-custom-actions' },
|
|
18
|
+
{ name: 'Nested resources', desc: 'Posts → Comments, scoped through routing.', link: '/plutonium-core/getting-started/tutorial/06-nested-resources' },
|
|
19
|
+
{ name: 'Author portal', desc: 'A second portal with its own auth and pages.', link: '/plutonium-core/getting-started/tutorial/07-author-portal' },
|
|
20
|
+
{ name: 'Customizing UI', desc: 'Theme tokens, custom Phlex components, layouts.', link: '/plutonium-core/getting-started/tutorial/08-customizing-ui' },
|
|
21
|
+
]"
|
|
22
|
+
:sidebar="[
|
|
23
|
+
{ heading: 'Already know your way around?', items: [
|
|
24
|
+
{ label: 'Installation', href: '/plutonium-core/getting-started/installation', note: 'bootstrap a new app' },
|
|
25
|
+
{ label: 'Concepts overview', href: '/plutonium-core/reference/' },
|
|
26
|
+
{ label: 'Generators reference', href: '/plutonium-core/reference/app/generators' },
|
|
27
|
+
]},
|
|
28
|
+
{ heading: 'Need help?', items: [
|
|
29
|
+
{ label: 'GitHub Discussions', href: 'https://github.com/radioactive-labs/plutonium-core/discussions' },
|
|
30
|
+
{ label: 'Open an issue', href: 'https://github.com/radioactive-labs/plutonium-core/issues' },
|
|
31
|
+
]},
|
|
32
|
+
]"
|
|
33
|
+
/>
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
# Installation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
For full installation reference (configuration options, base classes, what `pu:core:install` creates), see [Reference › App](/reference/app/). This page covers the quickest path.
|
|
4
4
|
|
|
5
|
-
## New
|
|
6
|
-
|
|
7
|
-
The fastest way to get started is with our application template:
|
|
5
|
+
## New application
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
rails new myapp -a propshaft -j esbuild -c tailwind \
|
|
11
9
|
-m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
This
|
|
15
|
-
- Adds the Plutonium gem
|
|
16
|
-
- Configures TailwindCSS 4 with Plutonium's theme
|
|
17
|
-
- Sets up Rodauth for authentication
|
|
18
|
-
- Creates initial migrations
|
|
19
|
-
- Configures the asset pipeline
|
|
12
|
+
This sets up Rails with Propshaft, esbuild, TailwindCSS, and Plutonium — plus Rodauth auth, asset pipeline, and initial migrations.
|
|
20
13
|
|
|
21
14
|
After the template completes:
|
|
22
15
|
|
|
@@ -26,122 +19,86 @@ rails db:migrate
|
|
|
26
19
|
bin/dev
|
|
27
20
|
```
|
|
28
21
|
|
|
29
|
-
Visit `http://localhost:3000
|
|
22
|
+
Visit `http://localhost:3000`.
|
|
30
23
|
|
|
31
|
-
## Existing
|
|
24
|
+
## Existing application
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
::: danger Use `base.rb`, not `plutonium.rb`
|
|
27
|
+
The `plutonium.rb` template re-runs full app bootstrap (dotenv, annotate, solid_*, asset config) and creates generic "initial commit" commits that clobber history. For any pre-existing app, always use `base.rb`.
|
|
28
|
+
:::
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
### Option 1: Template
|
|
36
31
|
|
|
37
|
-
```
|
|
38
|
-
|
|
32
|
+
```bash
|
|
33
|
+
bin/rails app:template \
|
|
34
|
+
LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
|
|
39
35
|
```
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
### Option 2: Manual
|
|
42
38
|
|
|
43
|
-
```
|
|
44
|
-
|
|
39
|
+
```ruby
|
|
40
|
+
# Gemfile
|
|
41
|
+
gem "plutonium"
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
### Step 2: Run the Installer
|
|
48
|
-
|
|
49
44
|
```bash
|
|
45
|
+
bundle install
|
|
50
46
|
rails generate pu:core:install
|
|
51
47
|
```
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
- Creates the Plutonium initializer
|
|
55
|
-
- Adds required configurations
|
|
56
|
-
- Sets up the asset pipeline integration
|
|
57
|
-
|
|
58
|
-
### Step 3: Install Rodauth (Optional)
|
|
59
|
-
|
|
60
|
-
If you want Plutonium's built-in authentication:
|
|
49
|
+
## Optional: authentication
|
|
61
50
|
|
|
62
51
|
```bash
|
|
63
52
|
rails generate pu:rodauth:install
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
This creates:
|
|
67
|
-
- Rodauth configuration files
|
|
68
|
-
- Account model and migrations
|
|
69
|
-
- Email templates for authentication flows
|
|
70
|
-
|
|
71
|
-
### Step 4: Run Migrations
|
|
72
|
-
|
|
73
|
-
```bash
|
|
53
|
+
rails generate pu:rodauth:account user
|
|
74
54
|
rails db:migrate
|
|
75
55
|
```
|
|
76
56
|
|
|
77
|
-
|
|
57
|
+
For account options and customization, see [Reference › Auth](/reference/auth/) and [Guides › Authentication](/guides/authentication).
|
|
78
58
|
|
|
79
|
-
|
|
59
|
+
## Optional: assets toolchain
|
|
80
60
|
|
|
81
61
|
```bash
|
|
82
62
|
rails generate pu:core:assets
|
|
83
63
|
```
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
## Verifying Installation
|
|
65
|
+
Installs npm packages, creates `tailwind.config.js` extending Plutonium's config, imports Plutonium CSS, registers Stimulus controllers. Required if you want to customize the theme — see [Reference › UI › Assets](/reference/ui/assets) and [Guides › Theming](/guides/theming).
|
|
88
66
|
|
|
89
|
-
|
|
67
|
+
## Verify
|
|
90
68
|
|
|
91
69
|
```bash
|
|
92
70
|
rails runner "puts Plutonium::VERSION"
|
|
93
71
|
```
|
|
94
72
|
|
|
95
|
-
You should see the installed version number.
|
|
96
|
-
|
|
97
73
|
## Configuration
|
|
98
74
|
|
|
99
|
-
Plutonium is configured in `config/initializers/plutonium.rb`:
|
|
100
|
-
|
|
101
75
|
```ruby
|
|
76
|
+
# config/initializers/plutonium.rb
|
|
102
77
|
Plutonium.configure do |config|
|
|
103
|
-
# Load default settings for version 1.0
|
|
104
78
|
config.load_defaults 1.0
|
|
105
79
|
|
|
106
|
-
#
|
|
107
|
-
# config.development = true
|
|
108
|
-
|
|
109
|
-
# Cache discovery (defaults to true in production, false in development)
|
|
110
|
-
# config.cache_discovery = false
|
|
80
|
+
# config.shell = :classic # legacy chrome (only for upgrades)
|
|
111
81
|
|
|
112
|
-
#
|
|
113
|
-
# config.
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
# config.assets.
|
|
117
|
-
# config.assets.favicon = "custom_favicon.ico"
|
|
118
|
-
# config.assets.stylesheet = "plutonium.css"
|
|
119
|
-
# config.assets.script = "plutonium.min.js"
|
|
82
|
+
# Custom assets (after running pu:core:assets)
|
|
83
|
+
# config.assets.stylesheet = "application"
|
|
84
|
+
# config.assets.script = "application"
|
|
85
|
+
# config.assets.logo = "custom_logo.png"
|
|
86
|
+
# config.assets.favicon = "custom_favicon.ico"
|
|
120
87
|
end
|
|
121
88
|
```
|
|
122
89
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
For the best development experience:
|
|
90
|
+
Full configuration options: [Reference › App](/reference/app/#configuration).
|
|
126
91
|
|
|
127
|
-
|
|
92
|
+
## `bin/dev` for development
|
|
128
93
|
|
|
129
|
-
Plutonium
|
|
94
|
+
Plutonium ships a Procfile that runs Rails and the CSS watcher together:
|
|
130
95
|
|
|
131
96
|
```bash
|
|
132
97
|
bin/dev
|
|
133
98
|
```
|
|
134
99
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
### 2. Enable Reloading
|
|
138
|
-
|
|
139
|
-
In development, Plutonium automatically reloads definitions and policies when files change. This is controlled by `config.enable_hotreload` (enabled by default in development).
|
|
140
|
-
|
|
141
|
-
## Next Steps
|
|
142
|
-
|
|
143
|
-
Now that Plutonium is installed:
|
|
100
|
+
## Next steps
|
|
144
101
|
|
|
145
|
-
- [
|
|
146
|
-
- [
|
|
147
|
-
- [
|
|
102
|
+
- [Tutorial](./tutorial/) — build a complete blog application step-by-step
|
|
103
|
+
- [Adding resources](/guides/adding-resources) — create your first resource
|
|
104
|
+
- [Creating packages](/guides/creating-packages) — organize code into feature and portal packages
|
|
@@ -118,13 +118,13 @@ rails db:migrate
|
|
|
118
118
|
|
|
119
119
|
## Creating a Portal
|
|
120
120
|
|
|
121
|
-
Resources need a portal to be accessible via the web. Let's create
|
|
121
|
+
Resources need a portal to be accessible via the web. Let's create a public admin portal so we can explore the UI right away — we'll add authentication in [Chapter 3](./03-authentication).
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
|
-
rails generate pu:pkg:portal admin
|
|
124
|
+
rails generate pu:pkg:portal admin --public
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
This creates the AdminPortal package with
|
|
127
|
+
This creates the `AdminPortal` package mounted at `/admin`. The `--public` flag wires the portal's controller with `Plutonium::Auth::Public`, so any visitor can access it. (Other options: `--auth=ACCOUNT` to gate via a Rodauth account, or `--byo` for your own auth.)
|
|
128
128
|
|
|
129
129
|
## Connecting the Resource
|
|
130
130
|
|
|
@@ -145,12 +145,21 @@ This:
|
|
|
145
145
|
bin/dev
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
Visit `http://localhost:3000/admin/blogging/posts`. You should see:
|
|
149
|
-
- An empty posts table
|
|
150
|
-
- A "New Post" button
|
|
151
|
-
- Search and filter options
|
|
148
|
+
Visit `http://localhost:3000/admin/blogging/posts`. You should see an empty posts table with a "New Post" button:
|
|
152
149
|
|
|
153
|
-
|
|
150
|
+

|
|
151
|
+
|
|
152
|
+
Click "New" — the form is automatically generated from your model's attributes. By default Plutonium opens it as a slideover (right) so you keep the index visible; visiting `/admin/blogging/posts/new` directly renders the same form as a standalone page (left):
|
|
153
|
+
|
|
154
|
+
| Default — slideover from index | Standalone page (direct URL) |
|
|
155
|
+
|:--:|:--:|
|
|
156
|
+
|  |  |
|
|
157
|
+
|
|
158
|
+
To always render full-page instead, set `modal false` in the definition. To pick a different style, use `modal :centered`. See [Reference › Resource › Definition › Modal](/reference/resource/definition).
|
|
159
|
+
|
|
160
|
+
Create a few posts and the table fills in:
|
|
161
|
+
|
|
162
|
+

|
|
154
163
|
|
|
155
164
|
## Understanding Auto-Detection
|
|
156
165
|
|