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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +574 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +167 -302
  5. data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
  6. data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
  7. data/.claude/skills/plutonium-tenancy/SKILL.md +674 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +9 -6
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +44 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1010 -1214
  14. data/app/assets/plutonium.js.map +3 -3
  15. data/app/assets/plutonium.min.js +52 -51
  16. data/app/assets/plutonium.min.js.map +3 -3
  17. data/docs/.vitepress/config.ts +38 -29
  18. data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
  19. data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
  20. data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
  21. data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
  22. data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
  23. data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
  24. data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
  25. data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
  26. data/docs/.vitepress/theme/custom.css +144 -0
  27. data/docs/.vitepress/theme/index.ts +58 -1
  28. data/docs/getting-started/index.md +33 -57
  29. data/docs/getting-started/installation.md +37 -80
  30. data/docs/getting-started/tutorial/02-first-resource.md +17 -8
  31. data/docs/getting-started/tutorial/03-authentication.md +31 -23
  32. data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
  33. data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
  34. data/docs/getting-started/tutorial/07-author-portal.md +8 -0
  35. data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
  36. data/docs/getting-started/tutorial/index.md +4 -5
  37. data/docs/guides/adding-resources.md +66 -377
  38. data/docs/guides/authentication.md +98 -462
  39. data/docs/guides/authorization.md +124 -370
  40. data/docs/guides/creating-packages.md +93 -298
  41. data/docs/guides/custom-actions.md +126 -441
  42. data/docs/guides/customizing-ui.md +258 -0
  43. data/docs/guides/index.md +49 -52
  44. data/docs/guides/multi-tenancy.md +123 -186
  45. data/docs/guides/nested-resources.md +137 -396
  46. data/docs/guides/search-filtering.md +127 -238
  47. data/docs/guides/testing.md +10 -5
  48. data/docs/guides/theming.md +168 -405
  49. data/docs/guides/troubleshooting.md +5 -3
  50. data/docs/guides/user-invites.md +112 -425
  51. data/docs/guides/user-profile.md +82 -241
  52. data/docs/index.md +10 -219
  53. data/docs/public/asciinema/home-scaffold.cast +305 -0
  54. data/docs/public/images/guides/custom-actions-bulk.png +0 -0
  55. data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
  56. data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
  57. data/docs/public/images/guides/nested-inputs.png +0 -0
  58. data/docs/public/images/guides/nested-resources-tab.png +0 -0
  59. data/docs/public/images/guides/search-filtering-index.png +0 -0
  60. data/docs/public/images/guides/search-filtering-panel.png +0 -0
  61. data/docs/public/images/guides/theming-after.png +0 -0
  62. data/docs/public/images/guides/theming-before.png +0 -0
  63. data/docs/public/images/guides/user-invites-landing.png +0 -0
  64. data/docs/public/images/guides/user-profile-edit.png +0 -0
  65. data/docs/public/images/guides/user-profile-show.png +0 -0
  66. data/docs/public/images/home-index.png +0 -0
  67. data/docs/public/images/home-new.png +0 -0
  68. data/docs/public/images/home-show.png +0 -0
  69. data/docs/public/images/tutorial/02-empty-index.png +0 -0
  70. data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
  71. data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
  72. data/docs/public/images/tutorial/02-new-form.png +0 -0
  73. data/docs/public/images/tutorial/03-create-account.png +0 -0
  74. data/docs/public/images/tutorial/03-login.png +0 -0
  75. data/docs/public/images/tutorial/04-admin-index.png +0 -0
  76. data/docs/public/images/tutorial/05-actions-menu.png +0 -0
  77. data/docs/public/images/tutorial/05-row-actions.png +0 -0
  78. data/docs/public/images/tutorial/06-comments-tab.png +0 -0
  79. data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
  80. data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
  81. data/docs/public/images/tutorial/07-author-portal.png +0 -0
  82. data/docs/public/images/tutorial/08-customized-index.png +0 -0
  83. data/docs/reference/app/generators.md +517 -0
  84. data/docs/reference/app/index.md +158 -0
  85. data/docs/reference/app/packages.md +146 -0
  86. data/docs/reference/app/portals.md +377 -0
  87. data/docs/reference/auth/accounts.md +229 -0
  88. data/docs/reference/auth/index.md +88 -0
  89. data/docs/reference/auth/profile.md +185 -0
  90. data/docs/reference/behavior/controllers.md +395 -0
  91. data/docs/reference/behavior/index.md +22 -0
  92. data/docs/reference/behavior/interactions.md +341 -0
  93. data/docs/reference/behavior/policies.md +417 -0
  94. data/docs/reference/index.md +67 -48
  95. data/docs/reference/resource/actions.md +423 -0
  96. data/docs/reference/resource/definition.md +508 -0
  97. data/docs/reference/resource/index.md +50 -0
  98. data/docs/reference/resource/model.md +348 -0
  99. data/docs/reference/resource/query.md +305 -0
  100. data/docs/reference/tenancy/entity-scoping.md +368 -0
  101. data/docs/reference/tenancy/index.md +36 -0
  102. data/docs/reference/tenancy/invites.md +400 -0
  103. data/docs/reference/tenancy/nested-resources.md +267 -0
  104. data/docs/reference/testing/index.md +287 -0
  105. data/docs/reference/ui/assets.md +400 -0
  106. data/docs/reference/ui/components.md +165 -0
  107. data/docs/reference/ui/displays.md +104 -0
  108. data/docs/reference/ui/forms.md +284 -0
  109. data/docs/reference/ui/index.md +30 -0
  110. data/docs/reference/ui/layouts.md +106 -0
  111. data/docs/reference/ui/pages.md +189 -0
  112. data/docs/reference/ui/tables.md +121 -0
  113. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
  114. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
  115. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  116. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  117. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  118. data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
  119. data/gemfiles/rails_7.gemfile.lock +1 -1
  120. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  121. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  122. data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
  123. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  124. data/lib/generators/pu/invites/install_generator.rb +45 -0
  125. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
  126. data/lib/generators/pu/profile/conn_generator.rb +2 -2
  127. data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
  128. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
  129. data/lib/generators/pu/rodauth/account_generator.rb +2 -1
  130. data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
  131. data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
  132. data/lib/generators/pu/rodauth/views_generator.rb +0 -2
  133. data/lib/generators/pu/saas/membership/USAGE +4 -1
  134. data/lib/generators/pu/saas/setup_generator.rb +16 -4
  135. data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
  136. data/lib/plutonium/definition/base.rb +1 -1
  137. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  138. data/lib/plutonium/helpers/turbo_helper.rb +30 -0
  139. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  140. data/lib/plutonium/resource/controller.rb +1 -0
  141. data/lib/plutonium/resource/controllers/crud_actions.rb +23 -5
  142. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  143. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  144. data/lib/plutonium/resource/policy.rb +7 -0
  145. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  146. data/lib/plutonium/ui/component/methods.rb +5 -0
  147. data/lib/plutonium/ui/form/base.rb +23 -3
  148. data/lib/plutonium/ui/form/components/json.rb +58 -0
  149. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  150. data/lib/plutonium/ui/form/components/secure_association.rb +103 -22
  151. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  152. data/lib/plutonium/ui/form/interaction.rb +1 -1
  153. data/lib/plutonium/ui/form/resource.rb +0 -4
  154. data/lib/plutonium/ui/form/theme.rb +1 -1
  155. data/lib/plutonium/ui/grid/resource.rb +1 -1
  156. data/lib/plutonium/ui/layout/base.rb +1 -0
  157. data/lib/plutonium/ui/page/base.rb +0 -7
  158. data/lib/plutonium/ui/page/edit.rb +1 -1
  159. data/lib/plutonium/ui/page/index.rb +4 -4
  160. data/lib/plutonium/ui/page/new.rb +1 -1
  161. data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
  162. data/lib/plutonium/ui/table/resource.rb +1 -1
  163. data/lib/plutonium/version.rb +1 -1
  164. data/lib/plutonium.rb +8 -0
  165. data/lib/tasks/release.rake +15 -1
  166. data/package.json +13 -10
  167. data/src/css/slim_select.css +4 -0
  168. data/src/js/controllers/form_controller.js +5 -4
  169. data/src/js/controllers/slim_select_controller.js +61 -0
  170. data/src/js/turbo/turbo_actions.js +33 -0
  171. data/yarn.lock +661 -544
  172. metadata +86 -33
  173. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  174. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  175. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  176. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  177. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  178. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  179. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  180. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  181. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  182. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  183. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  184. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  185. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  186. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  187. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  188. data/docs/reference/assets/index.md +0 -496
  189. data/docs/reference/controller/index.md +0 -412
  190. data/docs/reference/definition/actions.md +0 -462
  191. data/docs/reference/definition/fields.md +0 -383
  192. data/docs/reference/definition/index.md +0 -326
  193. data/docs/reference/definition/query.md +0 -351
  194. data/docs/reference/generators/index.md +0 -648
  195. data/docs/reference/interaction/index.md +0 -449
  196. data/docs/reference/model/features.md +0 -248
  197. data/docs/reference/model/index.md +0 -218
  198. data/docs/reference/policy/index.md +0 -456
  199. data/docs/reference/portal/index.md +0 -379
  200. data/docs/reference/views/forms.md +0 -411
  201. 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.)