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,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"planPath": "docs/superpowers/plans/2026-05-15-public-pages-overhaul.md",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"subject": "Task 0: Shared CSS tokens and primitives",
|
|
7
|
+
"status": "pending",
|
|
8
|
+
"description": "**Goal:** Add design-system tokens to docs/.vitepress/theme/custom.css.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/custom.css\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"tokens defined\",\"primitives render\",\"no regressions\"],\"requiresUserVerification\":false}\n```"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": 2,
|
|
12
|
+
"subject": "Task 1: HomeHero component",
|
|
13
|
+
"status": "pending",
|
|
14
|
+
"blockedBy": [1],
|
|
15
|
+
"description": "**Goal:** Build the locked hero.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeHero.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"hero renders\",\"cursor blinks\",\"responsive\",\"CTAs work\"],\"requiresUserVerification\":false}\n```"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 3,
|
|
19
|
+
"subject": "Task 2: HomeStopWriting (Section 1)",
|
|
20
|
+
"status": "pending",
|
|
21
|
+
"blockedBy": [1, 2],
|
|
22
|
+
"description": "**Goal:** Surface-area before/after.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeStopWriting.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"section renders\",\"stats correct\",\"responsive\"],\"requiresUserVerification\":false}\n```"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": 4,
|
|
26
|
+
"subject": "Task 3: HomePillars (Section 2)",
|
|
27
|
+
"status": "pending",
|
|
28
|
+
"blockedBy": [1, 3],
|
|
29
|
+
"description": "**Goal:** Four-pillar grid.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomePillars.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"4 pillars render\",\"copy verbatim\",\"responsive\"],\"requiresUserVerification\":false}\n```"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 5,
|
|
33
|
+
"subject": "Task 4: HomeWalkthrough layout (Section 3)",
|
|
34
|
+
"status": "pending",
|
|
35
|
+
"blockedBy": [1, 4],
|
|
36
|
+
"description": "**Goal:** Walkthrough section layout with placeholders.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeWalkthrough.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"layout reserves space\",\"labels visible\"],\"requiresUserVerification\":false}\n```"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": 6,
|
|
40
|
+
"subject": "Task 5: HomeAudienceSplit (Section 4)",
|
|
41
|
+
"status": "pending",
|
|
42
|
+
"blockedBy": [1, 5],
|
|
43
|
+
"description": "**Goal:** Side-by-side audience split.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeAudienceSplit.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"two columns\",\"locked copy\",\"responsive\"],\"requiresUserVerification\":false}\n```"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": 7,
|
|
47
|
+
"subject": "Task 6: HomeInTheBox (Section 5)",
|
|
48
|
+
"status": "pending",
|
|
49
|
+
"blockedBy": [1, 6],
|
|
50
|
+
"description": "**Goal:** Categorized in-the-box section.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeInTheBox.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"3 rows render\",\"copy matches spec\"],\"requiresUserVerification\":false}\n```"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": 8,
|
|
54
|
+
"subject": "Task 7: HomeCta with template-toggle pills (Section 6)",
|
|
55
|
+
"status": "pending",
|
|
56
|
+
"blockedBy": [1, 7],
|
|
57
|
+
"description": "**Goal:** Manifesto CTA with reactive template-toggle pills.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeCta.vue\",\"docs/.vitepress/theme/index.ts\",\"docs/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"pills toggle\",\"URLs correct\",\"CTAs link\"],\"requiresUserVerification\":false}\n```"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": 9,
|
|
61
|
+
"subject": "Task 8: SectionLanding shared component",
|
|
62
|
+
"status": "pending",
|
|
63
|
+
"blockedBy": [1],
|
|
64
|
+
"description": "**Goal:** Reusable rail+sidebar layout for section landings.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/SectionLanding.vue\",\"docs/.vitepress/theme/index.ts\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"numbered renders\",\"categorized renders\",\"responsive\"],\"requiresUserVerification\":false}\n```"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": 10,
|
|
68
|
+
"subject": "Task 9: Getting Started landing",
|
|
69
|
+
"status": "pending",
|
|
70
|
+
"blockedBy": [9],
|
|
71
|
+
"description": "**Goal:** Rewrite getting-started/index.md.\n\n```json:metadata\n{\"files\":[\"docs/getting-started/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"8 steps render\",\"links resolve\",\"prereqs preserved\"],\"requiresUserVerification\":false}\n```"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": 11,
|
|
75
|
+
"subject": "Task 10: Guides landing",
|
|
76
|
+
"status": "pending",
|
|
77
|
+
"blockedBy": [9],
|
|
78
|
+
"description": "**Goal:** Rewrite guides/index.md.\n\n```json:metadata\n{\"files\":[\"docs/guides/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"categories render\",\"links resolve\"],\"requiresUserVerification\":false}\n```"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": 12,
|
|
82
|
+
"subject": "Task 11: Reference landing",
|
|
83
|
+
"status": "pending",
|
|
84
|
+
"blockedBy": [9],
|
|
85
|
+
"description": "**Goal:** Rewrite reference/index.md.\n\n```json:metadata\n{\"files\":[\"docs/reference/index.md\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"categories match real sidebar\",\"links resolve\"],\"requiresUserVerification\":false}\n```"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": 13,
|
|
89
|
+
"subject": "Task 12: Demo app + asciinema + screenshots",
|
|
90
|
+
"status": "pending",
|
|
91
|
+
"description": "**Goal:** Produce walkthrough assets from a fresh demo app.\n\n```json:metadata\n{\"files\":[\"docs/public/images/home-portal.png\",\"docs/public/images/home-index.png\",\"docs/public/images/home-form.png\",\"docs/public/asciinema/home-scaffold.cast\"],\"verifyCommand\":\"ls -la docs/public/images/home-*.png docs/public/asciinema/home-scaffold.cast\",\"acceptanceCriteria\":[\"4 assets exist\",\"non-empty\",\"correct format\"],\"requiresUserVerification\":false}\n```"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": 14,
|
|
95
|
+
"subject": "Task 13: Wire assets into HomeWalkthrough",
|
|
96
|
+
"status": "pending",
|
|
97
|
+
"blockedBy": [5, 13],
|
|
98
|
+
"description": "**Goal:** Replace placeholders with real screenshots + asciinema embed.\n\n```json:metadata\n{\"files\":[\"docs/.vitepress/theme/components/HomeWalkthrough.vue\",\"docs/.vitepress/theme/index.ts\"],\"verifyCommand\":\"yarn docs:dev\",\"acceptanceCriteria\":[\"screenshots render\",\"asciinema plays\",\"no console errors\"],\"requiresUserVerification\":false}\n```"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": 15,
|
|
102
|
+
"subject": "Task 14: Visual sweep + user verification",
|
|
103
|
+
"status": "pending",
|
|
104
|
+
"blockedBy": [8, 10, 11, 12, 14],
|
|
105
|
+
"description": "**Goal:** Cross-cutting sweep and user sign-off.\n\n```json:metadata\n{\"files\":[],\"verifyCommand\":\"yarn docs:build\",\"acceptanceCriteria\":[\"all 4 pages render in both modes\",\"no console errors\",\"build succeeds\",\"user approved\"],\"requiresUserVerification\":true,\"userVerificationPrompt\":\"All four public pages are live in `yarn docs:dev`. Have you reviewed Home, Getting Started, Guides, and Reference and are they ready to ship?\"}\n```"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"lastUpdated": "2026-05-15T00:00:00Z"
|
|
109
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Typeahead Endpoint Design
|
|
2
|
+
|
|
3
|
+
**Status:** Approved (2026-05-09)
|
|
4
|
+
**Author:** stefan
|
|
5
|
+
**Scope:** New backend-driven typeahead/autocomplete primitive for resource form inputs and index filter inputs.
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Add an async typeahead endpoint to every Plutonium resource so association-backed selects (and any future typeahead-capable input) can fetch matching records from the server instead of materialising up to `DEFAULT_CHOICE_LIMIT` options into the page at render time. This unblocks association pickers over large tables (where the existing 100-row cap silently truncates) without forcing every input into a custom JS solution.
|
|
10
|
+
|
|
11
|
+
## Non-goals
|
|
12
|
+
|
|
13
|
+
- Pagination of typeahead results (we use a hard cap with an overflow indicator; pagination can be added later if a real need surfaces).
|
|
14
|
+
- Rich result rows (subtitle, icon, avatar). MVP returns minimal `{value, label}` per row; richer payloads are a separate iteration.
|
|
15
|
+
- Replacing the existing eager-list ResourceSelect; the eager path stays as the fallback / small-table mode.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
Three layers, mirroring how `Plutonium::Resource::Controllers::InteractiveActions` is composed today.
|
|
20
|
+
|
|
21
|
+
### 1. Routing — `Plutonium::Routing::MapperExtensions`
|
|
22
|
+
|
|
23
|
+
Two routes are added to the existing `interactive_resource_actions` concern (auto-mounted on every Plutonium resource alongside `record_actions`, `bulk_actions`, etc.):
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
GET /<resource>/typeahead/input/:name?q=… → typeahead_input
|
|
27
|
+
GET /<resource>/typeahead/filter/:name?q=… → typeahead_filter
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Both collection-level. **No member variant** — authorization on the parent resource class is sufficient (see "Authorization" below).
|
|
31
|
+
|
|
32
|
+
### 2. Controller concern — `Plutonium::Resource::Controllers::Typeahead`
|
|
33
|
+
|
|
34
|
+
Two thin actions plus a single `before_action` for auth.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
module Plutonium::Resource::Controllers::Typeahead
|
|
38
|
+
extend ActiveSupport::Concern
|
|
39
|
+
|
|
40
|
+
included do
|
|
41
|
+
before_action :authorize_typeahead!, only: %i[typeahead_input typeahead_filter]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def typeahead_input
|
|
45
|
+
name = params[:name].to_sym
|
|
46
|
+
defn = current_definition.defined_inputs[name]
|
|
47
|
+
return head(:not_found) unless defn
|
|
48
|
+
|
|
49
|
+
render_typeahead_response(defn)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def typeahead_filter
|
|
53
|
+
name = params[:name].to_sym
|
|
54
|
+
filter = current_query_object.filter_definitions[name]
|
|
55
|
+
return head(:not_found) unless filter
|
|
56
|
+
|
|
57
|
+
defn = filter.defined_inputs[:value]
|
|
58
|
+
return head(:not_found) unless defn
|
|
59
|
+
|
|
60
|
+
render_typeahead_response(defn)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def render_typeahead_response(defn)
|
|
66
|
+
klass = lookup_input_class(defn)
|
|
67
|
+
return render(json: { error: "input is not typeahead-capable" }, status: :bad_request) unless klass < Plutonium::UI::Form::Components::Searchable
|
|
68
|
+
|
|
69
|
+
widget = klass.build_for_typeahead(defn[:options] || {})
|
|
70
|
+
results, has_more = widget.typeahead(
|
|
71
|
+
query: params[:q].to_s,
|
|
72
|
+
limit: TYPEAHEAD_LIMIT,
|
|
73
|
+
controller: self
|
|
74
|
+
)
|
|
75
|
+
render json: { results: results, has_more: has_more }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def authorize_typeahead!
|
|
79
|
+
authorize! resource_class, to: :typeahead?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Maps the input definition's :as symbol (e.g. :resource_select) to a
|
|
83
|
+
# component class. Backed by an explicit registry — only inputs that
|
|
84
|
+
# opted in by including Searchable register here, so anything not in
|
|
85
|
+
# the registry falls through to the 400 branch.
|
|
86
|
+
def lookup_input_class(defn)
|
|
87
|
+
Plutonium::UI::Form::Components::Searchable.registry[defn[:options]&.[](:as)&.to_sym]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`TYPEAHEAD_LIMIT` is a module-level constant (default `50`). Easy to tune.
|
|
93
|
+
|
|
94
|
+
### 3. Search behavior — `Plutonium::UI::Form::Components::Searchable`
|
|
95
|
+
|
|
96
|
+
A small mixin. Mixed into `ResourceSelect` (and into any future input that wants typeahead). Two-method public surface:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
module Plutonium::UI::Form::Components::Searchable
|
|
100
|
+
extend ActiveSupport::Concern
|
|
101
|
+
|
|
102
|
+
# Maps :as symbol -> component class. Each typeahead-capable widget
|
|
103
|
+
# populates this when it includes Searchable so the controller can
|
|
104
|
+
# dispatch by name without a brittle inflection convention.
|
|
105
|
+
def self.registry
|
|
106
|
+
@registry ||= {}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class_methods do
|
|
110
|
+
# Subclasses call this to claim their :as symbol in the registry.
|
|
111
|
+
def typeahead_input_name(name)
|
|
112
|
+
Plutonium::UI::Form::Components::Searchable.registry[name.to_sym] = self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Allocates the widget and assigns just the ivars #typeahead needs.
|
|
116
|
+
# Bypasses Phlex's render-time build_attributes pipeline so we don't
|
|
117
|
+
# need a field/form context to run the search.
|
|
118
|
+
def build_for_typeahead(options)
|
|
119
|
+
allocate.tap { |w| w.send(:apply_typeahead_options, options) }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns [results_array, has_more_bool]. results entries are { value:, label: }.
|
|
124
|
+
def typeahead(query:, limit:, controller:)
|
|
125
|
+
raw = collect_typeahead_candidates(query, controller: controller)
|
|
126
|
+
over = raw.length > limit
|
|
127
|
+
[raw.first(limit).map { |r| serialize_typeahead_row(r) }, over]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`ResourceSelect` implements `apply_typeahead_options`, `collect_typeahead_candidates`, and `serialize_typeahead_row`:
|
|
133
|
+
|
|
134
|
+
- `apply_typeahead_options(options)` reads `@association_class`, `@raw_choices`, `@choice_limit`, `@skip_authorization` from the input definition's options hash — the same keys the existing `build_attributes` consumes at render time.
|
|
135
|
+
- `collect_typeahead_candidates` branches:
|
|
136
|
+
- if `@raw_choices` (static list) — `@raw_choices.select { |label, _| label.to_s.downcase.include?(query.downcase) }`. No auth: choices are static, definition-author-controlled.
|
|
137
|
+
- elsif `@association_class` — runs the search through `controller.send(:authorized_resource_scope, @association_class)` so the associated resource's `policy.relation_scope` enforces row-level auth, then applies the associated resource definition's `search` block if present, else `LIKE` on the column backing `to_label` (or skips filtering when query is blank).
|
|
138
|
+
- `serialize_typeahead_row(row)` returns `{ value: row.to_signed_global_id.to_s, label: row.to_label }` for records, or `{ value: raw_value, label: raw_label }` for static choices.
|
|
139
|
+
|
|
140
|
+
The cap is **`limit + 1`** at the SQL level (`LIMIT 51` for a `limit: 50` request) so we can detect overflow without a separate `COUNT`.
|
|
141
|
+
|
|
142
|
+
## Authorization
|
|
143
|
+
|
|
144
|
+
Two gates, layered:
|
|
145
|
+
|
|
146
|
+
1. **Parent gate** — `policy.typeahead?` on the resource hosting the endpoint. Defaults to `index?` (collection-shaped — typeahead is "list/search records of this class", not "show one record"). Override per-resource if needed (e.g. `def typeahead? = create? || update?` to require write intent).
|
|
147
|
+
2. **Row gate** — when the input is association-backed, results are scoped through the *associated* resource's `policy.relation_scope` via the existing `authorized_resource_scope` helper. So a user can typeahead Authors only if they're allowed to read Authors, regardless of whether they can edit Posts.
|
|
148
|
+
|
|
149
|
+
Static `choices` lists bypass the row gate (they're not records, they're definition-author-controlled enumerations).
|
|
150
|
+
|
|
151
|
+
## Data flow
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Browser (Stimulus controller)
|
|
155
|
+
fetch GET /widgets/typeahead/input/author?q=ali
|
|
156
|
+
↓
|
|
157
|
+
Typeahead#typeahead_input
|
|
158
|
+
authorize_typeahead! → policy.typeahead? on Widget [parent gate]
|
|
159
|
+
defn = current_definition.defined_inputs[:author]
|
|
160
|
+
widget = ResourceSelect.build_for_typeahead(defn[:options])
|
|
161
|
+
widget.typeahead(query: "ali", limit: 50, controller: self)
|
|
162
|
+
authorized_resource_scope(User).where("name LIKE ?", "%ali%").limit(51) [row gate]
|
|
163
|
+
serialize each → { value: sgid, label: to_label }
|
|
164
|
+
render json: { results: [...], has_more: false }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Components
|
|
168
|
+
|
|
169
|
+
| File | Responsibility |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `lib/plutonium/routing/mapper_extensions.rb` | Add 2 routes to the `interactive_resource_actions` concern. |
|
|
172
|
+
| `lib/plutonium/resource/controllers/typeahead.rb` | **New.** Controller concern with `typeahead_input`/`typeahead_filter` actions, auth, dispatch, JSON serialization. |
|
|
173
|
+
| `lib/plutonium/resource/controller.rb` | Include `Controllers::Typeahead`. |
|
|
174
|
+
| `lib/plutonium/resource/policy.rb` | Add `typeahead?` defaulting to `index?`. |
|
|
175
|
+
| `lib/plutonium/ui/form/components/searchable.rb` | **New.** `Searchable` mixin (class-level `build_for_typeahead`, instance-level `typeahead`). |
|
|
176
|
+
| `lib/plutonium/ui/form/components/resource_select.rb` | Include `Searchable`, call `typeahead_input_name :resource_select` to register. Implement `apply_typeahead_options`, `collect_typeahead_candidates`, `serialize_typeahead_row`. Wire Stimulus controller + remote URL data attrs into the rendered `<select>`. |
|
|
177
|
+
| `src/js/controllers/resource_select_controller.js` | **New.** Stimulus controller: debounced fetch, populates options on the underlying `<select>`, surfaces overflow hint, handles network errors. |
|
|
178
|
+
|
|
179
|
+
## Error handling
|
|
180
|
+
|
|
181
|
+
- Unknown input/filter name → `404 Not Found`.
|
|
182
|
+
- Input class registered but doesn't include `Searchable` → `400 Bad Request` with `{error: "input is not typeahead-capable"}`.
|
|
183
|
+
- Authorization failure → existing `ActionPolicy::Unauthorized` flow → `403`.
|
|
184
|
+
- Empty/blank `q` → return all candidates within the cap (so initial dropdown open shows something useful, mirroring the eager mode).
|
|
185
|
+
- Network/parse errors on the JS side → controller leaves the existing `<select>` options intact and shows a small "couldn't search" inline notice; user can retry.
|
|
186
|
+
|
|
187
|
+
## Testing
|
|
188
|
+
|
|
189
|
+
- **Unit — `Searchable#typeahead` (ResourceSelect):** static choices filter case-insensitively; association case routes through `authorized_resource_scope`; overflow detection (`limit+1` rows in DB → `has_more: true`); blank query returns top-N.
|
|
190
|
+
- **Controller — `Typeahead#typeahead_input` / `typeahead_filter`:** happy path renders correct JSON envelope; unknown name → 404; non-searchable input class → 400; auth denied → 403.
|
|
191
|
+
- **Integration:** full request through `admin_portal` hitting a registered resource, verifying SGID round-trip (the value in the response is accepted by ResourceSelect on form submit).
|
|
192
|
+
- **JS — Stimulus controller:** debounces input, handles `has_more`, handles network errors. Lightweight, behavior-focused.
|
|
193
|
+
|
|
194
|
+
## Migration & rollout
|
|
195
|
+
|
|
196
|
+
- Existing eager `ResourceSelect` keeps working — typeahead is opt-in per render via a flag on the input definition (e.g. `as: :resource_select, typeahead: true`). When unset, the component renders today's eager list. The Stimulus controller only attaches when `data-resource-select-typeahead-url-value` is present.
|
|
197
|
+
- Filter inputs default to typeahead when the underlying input class supports it (filters are the worst pain point for the 100-row cap).
|
|
198
|
+
|
|
199
|
+
## Open questions / deferred
|
|
200
|
+
|
|
201
|
+
- Server-driven sort order beyond what `relation_scope` returns (e.g. recency, fuzzy-rank). Out of scope for MVP.
|
|
202
|
+
- Multi-select typeahead UX (chips, paste-multiple). MVP supports `multiple: true` mechanically (the array of SGIDs round-trips fine), but the dropdown UX is single-select-shaped. Iteration.
|
|
203
|
+
- Caching/coalescing repeated queries client-side. Defer.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Skill Compaction & Consolidation Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-12
|
|
4
|
+
**Status:** Approved (pending implementation)
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
The `.claude/skills/` directory currently holds 19 Plutonium skills totaling ~7,846 lines. Several issues:
|
|
9
|
+
|
|
10
|
+
- Skills are too verbose for a stable-API framework. Plutonium rarely changes shape, so re-explaining concepts has little ROI.
|
|
11
|
+
- Several skills are read together for any non-trivial task (e.g. create-resource + model + definition). Loading them separately wastes context.
|
|
12
|
+
- Some skills duplicate content (Rails-isms, philosophy preambles, repeated DSL explanations).
|
|
13
|
+
|
|
14
|
+
Skills are written for **developers using the framework**, not for first-time Rails users. They should read like reference + decision rules, not tutorials.
|
|
15
|
+
|
|
16
|
+
## Goals
|
|
17
|
+
|
|
18
|
+
1. Reduce total skill volume by ~45% (target: ~4,150 lines from 7,846).
|
|
19
|
+
2. Merge skills that are almost always loaded together.
|
|
20
|
+
3. Keep skills self-contained with inline code examples (chosen over linking to `test/dummy`).
|
|
21
|
+
4. Preserve high-value reference material (option/DSL/field tables).
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Restructuring user-facing `docs/` site.
|
|
26
|
+
- Changing the framework API.
|
|
27
|
+
- Splitting examples into separate files outside the skill.
|
|
28
|
+
|
|
29
|
+
## Target Skill Map
|
|
30
|
+
|
|
31
|
+
From 19 skills to 8:
|
|
32
|
+
|
|
33
|
+
| New skill | Merges | Est. lines |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `plutonium` | (router, kept) | ~150 |
|
|
36
|
+
| `plutonium-app` | installation + portal + package | ~600 |
|
|
37
|
+
| `plutonium-resource` | create-resource + model + definition | ~800 |
|
|
38
|
+
| `plutonium-behavior` | controller + policy + interaction | ~700 |
|
|
39
|
+
| `plutonium-ui` | views + forms + assets | ~700 |
|
|
40
|
+
| `plutonium-auth` | (kept, compacted) | ~350 |
|
|
41
|
+
| `plutonium-tenancy` | entity-scoping + nested-resources + invites | ~600 |
|
|
42
|
+
| `plutonium-testing` | (kept, compacted) | ~250 |
|
|
43
|
+
|
|
44
|
+
**Total: ~4,150 lines.**
|
|
45
|
+
|
|
46
|
+
### Rationale per merge
|
|
47
|
+
|
|
48
|
+
- **plutonium-app** — installation, portal creation, and package creation are the setup arc. Always done together on a new app.
|
|
49
|
+
- **plutonium-resource** — model declarations, scaffold options, and definition DSL are the core "build a resource" workflow.
|
|
50
|
+
- **plutonium-behavior** — controllers, policies, and interactions form the request/authorization/business-logic layer.
|
|
51
|
+
- **plutonium-ui** — views, forms, and assets all touch presentation. Assets covers the toolchain backing both views and forms.
|
|
52
|
+
- **plutonium-tenancy** — entity-scoping is the core mechanic; nested-resources and invites are both consumers of that mechanic.
|
|
53
|
+
- **plutonium-auth** stays solo at the user's request (rodauth/profile is distinct enough from tenancy/invites).
|
|
54
|
+
- **plutonium-testing** stays solo (orthogonal concern, loaded only for test work).
|
|
55
|
+
|
|
56
|
+
## Compaction Rules
|
|
57
|
+
|
|
58
|
+
Applied to every skill during merge.
|
|
59
|
+
|
|
60
|
+
**Cut:**
|
|
61
|
+
- Rails/Ruby basics — assume reader knows Rails.
|
|
62
|
+
- Philosophy/motivation preambles.
|
|
63
|
+
- Duplicated content across merged skills (one canonical location per concept).
|
|
64
|
+
- Verbose prose where a 10-line snippet shows the same thing.
|
|
65
|
+
- Marketing copy ("Plutonium gives you...").
|
|
66
|
+
|
|
67
|
+
**Keep:**
|
|
68
|
+
- Decision rules ("use X when…, Y when…").
|
|
69
|
+
- Non-obvious gotchas and constraints.
|
|
70
|
+
- Short canonical inline snippets.
|
|
71
|
+
- **Option/field/DSL tables** — high-value reference, kept verbatim.
|
|
72
|
+
- Cross-references to other skills via `[[plutonium-resource]]` style links.
|
|
73
|
+
|
|
74
|
+
## Format per merged skill
|
|
75
|
+
|
|
76
|
+
1. **Header paragraph** — what this covers + when to load.
|
|
77
|
+
2. **Sub-sections per merged topic** — each with: decision rules → minimal inline example → gotchas → tables (where applicable).
|
|
78
|
+
3. **Cross-references** at bottom.
|
|
79
|
+
|
|
80
|
+
## Rollout
|
|
81
|
+
|
|
82
|
+
1. **Pilot:** `plutonium-resource` first (largest, hardest — biggest signal on whether the template works).
|
|
83
|
+
2. **Review pilot together** — adjust template if needed.
|
|
84
|
+
3. **Apply pattern** to the remaining merges. One PR per merged skill OR all-in-one (TBD with user).
|
|
85
|
+
4. **Update `plutonium` router skill** last — it references the new names.
|
|
86
|
+
5. **Delete old skill directories** only after the new one lands.
|
|
87
|
+
|
|
88
|
+
Skills require a gem release to take effect for users (per `CLAUDE.md`), so this ships as a single release regardless of how PRs are split.
|
|
89
|
+
|
|
90
|
+
## Risks
|
|
91
|
+
|
|
92
|
+
- **Loss of granularity for context loading** — a single merged skill loads more tokens even when only one sub-topic is needed. Mitigated by aggressive compaction (loose budget but still much smaller than today's biggest individual skills).
|
|
93
|
+
- **Cross-references breaking** — the `plutonium` router skill and any external references must update at the same time as the merge.
|
|
94
|
+
- **Drift from `docs/`** — the user-facing docs site may still reference old skill structure; out of scope for this spec but worth noting.
|
|
95
|
+
|
|
96
|
+
## Open Questions
|
|
97
|
+
|
|
98
|
+
- Should the merge land as one PR or eight? (Deferred to rollout time.)
|
|
99
|
+
- Are there external references to the old skill names (other repos, marketplace listings) that need updating?
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Docs Restructure & Compaction Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-13
|
|
4
|
+
**Status:** Draft — awaiting approval
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
Following the skill consolidation (19 → 8 skills, ~37% volume cut), the `docs/` site is misaligned in two ways:
|
|
9
|
+
|
|
10
|
+
1. **Reference structure** mirrors the OLD skill structure (separate `model/`, `definition/`, `policy/`, `controller/`, `interaction/`, `views/`, `assets/`, `portal/`, `generators/`). It should mirror the new 7 functional areas.
|
|
11
|
+
2. **Concept/task split is muddled.** Some "guides" are really concept explanations (e.g. `guides/authorization.md` overlaps `reference/policy/`). Some concepts have NO reference home (auth, tenancy, testing — they live in `guides/` only).
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
**Primary goal: quality.** Volume reduction is incidental.
|
|
16
|
+
|
|
17
|
+
1. Restructure `reference/` to mirror the 7 functional areas from the skill consolidation — so readers can predict where to look.
|
|
18
|
+
2. Establish a clean role split: **guides = task recipes ("how do I X")**, **reference = concept lookup ("what does X do")**. Some duplication is fine when framed as different entry points; tables of options live in ONE place.
|
|
19
|
+
3. Improve every page on these axes:
|
|
20
|
+
- **Right place** — concepts in reference, recipes in guides.
|
|
21
|
+
- **Right structure** — 🚨 callouts at top for "you'll regret this" rules; option/decision tables for scannability; decision rules over generic exhortations.
|
|
22
|
+
- **Right content** — keep WHY explanations that help reason about edge cases; cut marketing copy and empty "best practices" exhortations; verify technical accuracy as we go (the skill work caught real bugs — same energy).
|
|
23
|
+
4. Light pass on the tutorial — preserve narrative flow; improve clarity where prose is unclear.
|
|
24
|
+
|
|
25
|
+
## Non-Goals
|
|
26
|
+
|
|
27
|
+
- Restructuring `getting-started/` navigation (the tutorial arc stays).
|
|
28
|
+
- Changing VitePress theme, search provider, or build pipeline.
|
|
29
|
+
- Adding new content beyond reorganizing what exists.
|
|
30
|
+
|
|
31
|
+
## Current state
|
|
32
|
+
|
|
33
|
+
40 markdown files, ~13,222 lines.
|
|
34
|
+
|
|
35
|
+
| Area | Files | Notes |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `getting-started/` | 11 | Index + installation + 8-step tutorial. Narrative learning arc. |
|
|
38
|
+
| `guides/` | 14 | Task-oriented but inconsistent — some concept-heavy. |
|
|
39
|
+
| `reference/` | 16 | Concept-by-concept, mirrors the OLD skill structure. |
|
|
40
|
+
|
|
41
|
+
## Target reference structure (mirrors 7 skill areas)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
reference/
|
|
45
|
+
├── index.md ← rewritten overview, links to the 7 areas
|
|
46
|
+
├── app/
|
|
47
|
+
│ ├── index.md ← installation, configuration
|
|
48
|
+
│ ├── packages.md ← feature + portal packages
|
|
49
|
+
│ ├── portals.md ← portal engines, mounting, route registration
|
|
50
|
+
│ └── generators.md ← full generator catalog
|
|
51
|
+
├── resource/
|
|
52
|
+
│ ├── index.md ← overview, the 4 layers
|
|
53
|
+
│ ├── model.md ← `Plutonium::Resource::Record`, has_cents, SGID, routing (merges current model/)
|
|
54
|
+
│ ├── definition.md ← field/input/display/column, page chrome (merges current definition/index + fields)
|
|
55
|
+
│ ├── query.md ← search, filters, scopes, sort
|
|
56
|
+
│ └── actions.md ← custom + bulk actions
|
|
57
|
+
├── behavior/
|
|
58
|
+
│ ├── index.md ← overview, the controller/policy/interaction trio
|
|
59
|
+
│ ├── controllers.md ← hooks, key methods, presentation
|
|
60
|
+
│ ├── policies.md ← actions, permitted attributes, associations, relation_scope
|
|
61
|
+
│ └── interactions.md ← structure, outcomes, chaining, URL generation
|
|
62
|
+
├── ui/
|
|
63
|
+
│ ├── index.md ← overview
|
|
64
|
+
│ ├── pages.md ← IndexPage/ShowPage/NewPage/EditPage, hooks
|
|
65
|
+
│ ├── forms.md ← field builder, layouts, theming, association inputs (current views/forms.md)
|
|
66
|
+
│ ├── displays.md ← Display class, custom rendering
|
|
67
|
+
│ ├── tables.md ← Table class, customization
|
|
68
|
+
│ ├── components.md ← component kit, custom Phlex components
|
|
69
|
+
│ ├── layouts.md ← shell, eject, ResourceLayout
|
|
70
|
+
│ └── assets.md ← Tailwind config, Stimulus, design tokens, .pu-* classes (current assets/)
|
|
71
|
+
├── auth/ ← NEW (currently only in guides/)
|
|
72
|
+
│ ├── index.md ← Rodauth overview
|
|
73
|
+
│ ├── accounts.md ← basic, admin, SaaS account types
|
|
74
|
+
│ └── profile.md ← profile resource, SecuritySection
|
|
75
|
+
├── tenancy/ ← NEW (currently spread across guides/)
|
|
76
|
+
│ ├── index.md ← overview, three pieces (portal/policy/model)
|
|
77
|
+
│ ├── entity-scoping.md ← associated_with, three model shapes
|
|
78
|
+
│ ├── nested-resources.md ← parent/child routes, scoping
|
|
79
|
+
│ └── invites.md ← invitation system
|
|
80
|
+
└── testing/ ← NEW (currently only in guides/)
|
|
81
|
+
├── index.md
|
|
82
|
+
├── crud.md
|
|
83
|
+
├── policy.md
|
|
84
|
+
├── nested.md
|
|
85
|
+
├── portal-access.md
|
|
86
|
+
└── auth-helpers.md
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Content migrations
|
|
90
|
+
|
|
91
|
+
| Current file | Goes to |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `reference/model/index.md` + `features.md` | `reference/resource/model.md` |
|
|
94
|
+
| `reference/definition/index.md` + `fields.md` | `reference/resource/definition.md` |
|
|
95
|
+
| `reference/definition/query.md` | `reference/resource/query.md` |
|
|
96
|
+
| `reference/definition/actions.md` | `reference/resource/actions.md` |
|
|
97
|
+
| `reference/controller/index.md` | `reference/behavior/controllers.md` |
|
|
98
|
+
| `reference/policy/index.md` | `reference/behavior/policies.md` |
|
|
99
|
+
| `reference/interaction/index.md` | `reference/behavior/interactions.md` |
|
|
100
|
+
| `reference/views/index.md` | split → `pages.md`, `displays.md`, `tables.md`, `components.md`, `layouts.md` |
|
|
101
|
+
| `reference/views/forms.md` | `reference/ui/forms.md` |
|
|
102
|
+
| `reference/assets/index.md` | `reference/ui/assets.md` |
|
|
103
|
+
| `reference/portal/index.md` | `reference/app/portals.md` |
|
|
104
|
+
| `reference/generators/index.md` | `reference/app/generators.md` |
|
|
105
|
+
| `getting-started/installation.md` | concept part → `reference/app/index.md`; task part stays |
|
|
106
|
+
| `guides/authentication.md` | concept part → `reference/auth/index.md` + `accounts.md`; recipe stays |
|
|
107
|
+
| `guides/user-profile.md` | concept part → `reference/auth/profile.md`; recipe stays |
|
|
108
|
+
| `guides/multi-tenancy.md` | concept part → `reference/tenancy/entity-scoping.md`; recipe stays |
|
|
109
|
+
| `guides/nested-resources.md` | concept part → `reference/tenancy/nested-resources.md`; recipe stays |
|
|
110
|
+
| `guides/user-invites.md` | concept part → `reference/tenancy/invites.md`; recipe stays |
|
|
111
|
+
| `guides/testing.md` | concept part → `reference/testing/*`; recipe stays |
|
|
112
|
+
| `guides/creating-packages.md` | concept part → `reference/app/packages.md`; recipe stays |
|
|
113
|
+
|
|
114
|
+
## Guides restructure
|
|
115
|
+
|
|
116
|
+
Each guide becomes a clean **task recipe**:
|
|
117
|
+
|
|
118
|
+
- Single goal stated at the top ("Add authentication to your app")
|
|
119
|
+
- Numbered step-by-step
|
|
120
|
+
- Each step links to the relevant reference page for "why" and "what else"
|
|
121
|
+
- No exhaustive option tables (those live in reference)
|
|
122
|
+
- ~50-150 lines each, down from ~300-600
|
|
123
|
+
|
|
124
|
+
Keep the 14 guides at their current paths so external links don't break.
|
|
125
|
+
|
|
126
|
+
## Editing principles (quality-first)
|
|
127
|
+
|
|
128
|
+
Not "cut everything"; cut what doesn't earn its keep. Specifically:
|
|
129
|
+
|
|
130
|
+
**Cut:**
|
|
131
|
+
- Marketing copy ("Plutonium is awesome because…").
|
|
132
|
+
- Empty "best practices" exhortations ("write clean code", "test your code").
|
|
133
|
+
- Content duplicated across pages — one canonical home + cross-link.
|
|
134
|
+
- Verbose prose where a 10-line snippet shows the same thing.
|
|
135
|
+
- Rails-101 explanations that don't set up a Plutonium twist.
|
|
136
|
+
|
|
137
|
+
**Keep — and add more of:**
|
|
138
|
+
- **WHY explanations** that help readers reason about edge cases.
|
|
139
|
+
- **Non-obvious gotchas** — the dangerous-default stuff that bites people.
|
|
140
|
+
- **Decision rules** ("use X when you need Y") over generic exhortations.
|
|
141
|
+
- **Option / field / DSL tables** — readers scan them, they don't read prose.
|
|
142
|
+
- **Inline code examples** that work — copy-pasteable, no `...` stand-ins.
|
|
143
|
+
- **🚨 callouts at top of each page** for the "you'll regret this" rules.
|
|
144
|
+
|
|
145
|
+
**Verify as we go.** The skill work caught real bugs (auto-detection rules, association input behavior, action visibility flags, `views` DSL naming). Same energy here — when prose claims X, check the source.
|
|
146
|
+
|
|
147
|
+
## Tutorial compaction pass
|
|
148
|
+
|
|
149
|
+
Same cuts as above, but preserve:
|
|
150
|
+
|
|
151
|
+
- Step structure (1-8 stays)
|
|
152
|
+
- Narrative flow (one step builds on the next)
|
|
153
|
+
- Frequent "expected output" / "verify" callouts
|
|
154
|
+
- Screenshots and visuals (keep all references)
|
|
155
|
+
|
|
156
|
+
Target: 10-20% volume reduction without losing pedagogical value.
|
|
157
|
+
|
|
158
|
+
## VitePress sidebar rewrite
|
|
159
|
+
|
|
160
|
+
`.vitepress/config.ts` sidebar rewritten for the new structure. Three sidebars:
|
|
161
|
+
|
|
162
|
+
- `/getting-started/` — unchanged
|
|
163
|
+
- `/guides/` — same 14 entries, reorganized into the 7 functional groups
|
|
164
|
+
- `/reference/` — new 7-area structure
|
|
165
|
+
|
|
166
|
+
## Rollout
|
|
167
|
+
|
|
168
|
+
1. **Pilot one reference area** — pick `reference/resource/` (largest, most-read). Build it from scratch using the merged skills as the template. Get user feedback on shape.
|
|
169
|
+
2. **Build out the other 6 areas** — one at a time or in parallel, your call.
|
|
170
|
+
3. **Move concept content** from guides into reference. Hollow guides become recipes.
|
|
171
|
+
4. **Tutorial compaction pass** — minimal, last.
|
|
172
|
+
5. **Rewrite VitePress sidebar** — once all paths exist.
|
|
173
|
+
6. **Delete old reference directories** — `model/`, `definition/`, `policy/`, etc. — in one sweep after everything's in place.
|
|
174
|
+
7. **Build the site locally** — verify no dead links, search index regenerates cleanly.
|
|
175
|
+
|
|
176
|
+
## Risks
|
|
177
|
+
|
|
178
|
+
- **External links break.** GitHub PRs, blog posts, Stack Overflow answers may link to `reference/model/`, `reference/definition/actions`, etc. Mitigation: VitePress supports redirects, OR keep stub pages that redirect via meta refresh. Cheap to add.
|
|
179
|
+
- **Guides ↔ reference duplication drifts.** If a recipe and its reference page describe the same option differently, readers get confused. Mitigation: linting (no duplicate DSL/option tables across guide + reference).
|
|
180
|
+
- **Volume isn't the metric.** Some pages will get longer (currently underdeveloped topics: tenancy, testing, profile). Some will get shorter. The win is navigability and clarity, not line count.
|
|
181
|
+
|
|
182
|
+
## Open questions
|
|
183
|
+
|
|
184
|
+
- Add `.vitepress` redirect configuration for old reference paths? (Recommended: yes, low cost.)
|
|
185
|
+
- Should `reference/app/generators.md` be the full catalog or a per-area split? (Recommended: full catalog — it's reference, scannability matters.)
|
|
186
|
+
- Are there guides that should be **deleted entirely** because they're 100% concept overlap with reference? (Decide after pilot.)
|