plutonium 0.50.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +572 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +163 -300
  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 +655 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +6 -5
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +27 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1009 -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 +37 -27
  18. data/docs/getting-started/index.md +22 -29
  19. data/docs/getting-started/installation.md +37 -80
  20. data/docs/getting-started/tutorial/index.md +4 -5
  21. data/docs/guides/adding-resources.md +66 -377
  22. data/docs/guides/authentication.md +94 -463
  23. data/docs/guides/authorization.md +124 -370
  24. data/docs/guides/creating-packages.md +94 -296
  25. data/docs/guides/custom-actions.md +121 -441
  26. data/docs/guides/index.md +22 -42
  27. data/docs/guides/multi-tenancy.md +116 -187
  28. data/docs/guides/nested-resources.md +103 -431
  29. data/docs/guides/search-filtering.md +123 -240
  30. data/docs/guides/testing.md +5 -4
  31. data/docs/guides/theming.md +157 -407
  32. data/docs/guides/troubleshooting.md +5 -3
  33. data/docs/guides/user-invites.md +106 -425
  34. data/docs/guides/user-profile.md +76 -243
  35. data/docs/index.md +1 -1
  36. data/docs/reference/app/generators.md +517 -0
  37. data/docs/reference/app/index.md +158 -0
  38. data/docs/reference/app/packages.md +146 -0
  39. data/docs/reference/app/portals.md +377 -0
  40. data/docs/reference/auth/accounts.md +230 -0
  41. data/docs/reference/auth/index.md +88 -0
  42. data/docs/reference/auth/profile.md +185 -0
  43. data/docs/reference/behavior/controllers.md +395 -0
  44. data/docs/reference/behavior/index.md +22 -0
  45. data/docs/reference/behavior/interactions.md +341 -0
  46. data/docs/reference/behavior/policies.md +417 -0
  47. data/docs/reference/index.md +56 -49
  48. data/docs/reference/resource/actions.md +423 -0
  49. data/docs/reference/resource/definition.md +508 -0
  50. data/docs/reference/resource/index.md +50 -0
  51. data/docs/reference/resource/model.md +348 -0
  52. data/docs/reference/resource/query.md +305 -0
  53. data/docs/reference/tenancy/entity-scoping.md +361 -0
  54. data/docs/reference/tenancy/index.md +36 -0
  55. data/docs/reference/tenancy/invites.md +393 -0
  56. data/docs/reference/tenancy/nested-resources.md +267 -0
  57. data/docs/reference/testing/index.md +287 -0
  58. data/docs/reference/ui/assets.md +400 -0
  59. data/docs/reference/ui/components.md +165 -0
  60. data/docs/reference/ui/displays.md +104 -0
  61. data/docs/reference/ui/forms.md +284 -0
  62. data/docs/reference/ui/index.md +30 -0
  63. data/docs/reference/ui/layouts.md +106 -0
  64. data/docs/reference/ui/pages.md +189 -0
  65. data/docs/reference/ui/tables.md +117 -0
  66. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  67. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  68. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  69. data/gemfiles/rails_7.gemfile.lock +1 -1
  70. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  71. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  72. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  73. data/lib/generators/pu/invites/install_generator.rb +1 -0
  74. data/lib/plutonium/definition/base.rb +1 -1
  75. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  76. data/lib/plutonium/helpers/turbo_helper.rb +11 -0
  77. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  78. data/lib/plutonium/resource/controller.rb +1 -0
  79. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  80. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  81. data/lib/plutonium/resource/policy.rb +7 -0
  82. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  83. data/lib/plutonium/ui/component/methods.rb +4 -0
  84. data/lib/plutonium/ui/form/base.rb +6 -2
  85. data/lib/plutonium/ui/form/components/json.rb +58 -0
  86. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  87. data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
  88. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  89. data/lib/plutonium/ui/form/resource.rb +0 -4
  90. data/lib/plutonium/ui/grid/resource.rb +1 -1
  91. data/lib/plutonium/ui/layout/base.rb +1 -0
  92. data/lib/plutonium/ui/page/base.rb +0 -7
  93. data/lib/plutonium/ui/page/index.rb +4 -4
  94. data/lib/plutonium/ui/table/resource.rb +1 -1
  95. data/lib/plutonium/version.rb +1 -1
  96. data/lib/plutonium.rb +8 -0
  97. data/lib/tasks/release.rake +15 -1
  98. data/package.json +10 -10
  99. data/src/css/slim_select.css +4 -0
  100. data/src/js/controllers/slim_select_controller.js +61 -0
  101. data/src/js/turbo/turbo_actions.js +33 -0
  102. data/yarn.lock +553 -543
  103. metadata +44 -33
  104. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  105. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  106. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  107. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  108. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  109. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  110. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  111. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  112. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  113. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  114. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  115. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  116. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  117. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  118. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  119. data/docs/reference/assets/index.md +0 -496
  120. data/docs/reference/controller/index.md +0 -412
  121. data/docs/reference/definition/actions.md +0 -462
  122. data/docs/reference/definition/fields.md +0 -383
  123. data/docs/reference/definition/index.md +0 -326
  124. data/docs/reference/definition/query.md +0 -351
  125. data/docs/reference/generators/index.md +0 -648
  126. data/docs/reference/interaction/index.md +0 -449
  127. data/docs/reference/model/features.md +0 -248
  128. data/docs/reference/model/index.md +0 -218
  129. data/docs/reference/policy/index.md +0 -456
  130. data/docs/reference/portal/index.md +0 -379
  131. data/docs/reference/views/forms.md +0 -411
  132. data/docs/reference/views/index.md +0 -544
@@ -1,651 +0,0 @@
1
- ---
2
- name: plutonium-views
3
- description: Use BEFORE building a custom page, panel, table, layout, or Phlex component in Plutonium. Also when overriding IndexPage/ShowPage/Form classes in a definition.
4
- ---
5
-
6
- # Plutonium Views
7
-
8
- ## 🚨 Critical (read first)
9
- - **Override via nested classes in the definition.** `class ShowPage < ShowPage; end`, `class Form < Form; end` — don't replace the entire view layer.
10
- - **Use the render hooks.** `render_before_content`, `render_after_content`, `render_before_toolbar`, etc. — they exist so you don't have to override `view_template` and re-implement everything.
11
- - **All pages inherit `DynaFrameContent`** so turbo-frame requests render only the content. Don't fight it — modals and frame nav "just work".
12
- - **For custom components, use `Plutonium::UI::Component::Base`** so you inherit the component kit (`PageHeader`, `Panel`, `Block`, etc.) and access to resource helpers.
13
- - **Related skills:** `plutonium-forms` (form customization), `plutonium-assets` (theming + component classes), `plutonium-definition` (field-level rendering), `plutonium-controller` (presentation hooks like `present_parent?`).
14
-
15
- Plutonium uses [Phlex](https://www.phlex.fun/) for all view components. This provides a Ruby-first approach to building HTML with full IDE support and type safety.
16
-
17
- ## Architecture Overview
18
-
19
- ```
20
- Definition
21
- ├── IndexPage → renders Table
22
- ├── ShowPage → renders Display
23
- ├── NewPage → renders Form
24
- ├── EditPage → renders Form
25
- └── InteractiveActionPage → renders Form
26
- ```
27
-
28
- Each definition has nested classes you can override:
29
-
30
- ```ruby
31
- class PostDefinition < ResourceDefinition
32
- class IndexPage < IndexPage; end
33
- class ShowPage < ShowPage; end
34
- class NewPage < NewPage; end
35
- class EditPage < EditPage; end
36
- class Form < Form; end
37
- class Table < Table; end
38
- class Display < Display; end
39
- end
40
- ```
41
-
42
- ## Page Customization
43
-
44
- ### Page Configuration
45
-
46
- Set page titles and descriptions in definitions:
47
-
48
- ```ruby
49
- class PostDefinition < ResourceDefinition
50
- # Static titles
51
- index_page_title "Blog Posts"
52
- index_page_description "Manage all published articles"
53
-
54
- show_page_title "Article Details"
55
- new_page_title "Write New Article"
56
- edit_page_title "Edit Article"
57
-
58
- # Control breadcrumbs
59
- breadcrumbs true # Global default
60
- index_page_breadcrumbs false # Per-page override
61
- show_page_breadcrumbs true
62
- end
63
- ```
64
-
65
- ### Custom Page Class
66
-
67
- Override page rendering by subclassing:
68
-
69
- ```ruby
70
- class PostDefinition < ResourceDefinition
71
- class ShowPage < ShowPage
72
- private
73
-
74
- # Custom title logic
75
- def page_title
76
- "#{object.title} - #{object.author.name}"
77
- end
78
-
79
- # Add content before the main area
80
- def render_before_content
81
- div(class: "alert alert-info") {
82
- "This post has #{object.comments.count} comments"
83
- }
84
- end
85
-
86
- # Add content after
87
- def render_after_content
88
- render RelatedPostsComponent.new(post: object)
89
- end
90
-
91
- # Override the toolbar
92
- def render_toolbar
93
- div(class: "flex gap-2") {
94
- button(class: "btn") { "Preview" }
95
- button(class: "btn btn-primary") { "Publish" }
96
- }
97
- end
98
- end
99
- end
100
- ```
101
-
102
- ### Page Hooks
103
-
104
- All pages inherit these customization hooks:
105
-
106
- | Hook | Purpose |
107
- |------|---------|
108
- | `render_before_header` | Before entire header section |
109
- | `render_after_header` | After entire header section |
110
- | `render_before_breadcrumbs` | Before breadcrumbs |
111
- | `render_after_breadcrumbs` | After breadcrumbs |
112
- | `render_before_page_header` | Before title/actions |
113
- | `render_after_page_header` | After title/actions |
114
- | `render_before_toolbar` | Before toolbar |
115
- | `render_after_toolbar` | After toolbar |
116
- | `render_before_content` | Before main content |
117
- | `render_after_content` | After main content |
118
- | `render_before_footer` | Before footer |
119
- | `render_after_footer` | After footer |
120
-
121
- ### Custom View Files
122
-
123
- For complete control, create custom ERB view files that replace the default entirely.
124
-
125
- **File locations:**
126
-
127
- ```
128
- # Main app (for a PostsController)
129
- app/views/posts/index.html.erb
130
- app/views/posts/show.html.erb
131
- app/views/posts/new.html.erb
132
- app/views/posts/edit.html.erb
133
-
134
- # Portal-specific
135
- packages/admin_portal/app/views/admin_portal/posts/show.html.erb
136
- ```
137
-
138
- **Default view structure:**
139
-
140
- The default views simply render the page class:
141
-
142
- ```erb
143
- <%# app/views/resource/show.html.erb %>
144
- <%= render current_definition.show_page_class.new %>
145
- ```
146
-
147
- **Custom view example:**
148
-
149
- ```erb
150
- <%# app/views/posts/show.html.erb %>
151
- <div class="max-w-4xl mx-auto">
152
- <article class="prose lg:prose-xl">
153
- <h1><%= resource_record!.title %></h1>
154
- <div class="meta text-gray-500">
155
- By <%= resource_record!.author.name %> on <%= resource_record!.created_at.strftime("%B %d, %Y") %>
156
- </div>
157
- <%= raw resource_record!.content %>
158
- </article>
159
-
160
- <div class="mt-8">
161
- <%= link_to "Edit", resource_url_for(resource_record!, action: :edit), class: "btn" %>
162
- <%= link_to "Back", resource_url_for(Post), class: "btn" %>
163
- </div>
164
- </div>
165
- ```
166
-
167
- **Mixing approaches:**
168
-
169
- Render the default page with additions:
170
-
171
- ```erb
172
- <%# app/views/posts/show.html.erb %>
173
- <div class="announcement-banner">
174
- Special announcement here
175
- </div>
176
-
177
- <%= render current_definition.show_page_class.new %>
178
-
179
- <div class="related-posts">
180
- <%= render partial: "related_posts", locals: { post: resource_record! } %>
181
- </div>
182
- ```
183
-
184
- ## Form Customization
185
-
186
- ### Custom Form Template
187
-
188
- Override how fields are rendered:
189
-
190
- ```ruby
191
- class PostDefinition < ResourceDefinition
192
- class Form < Form
193
- def form_template
194
- # Custom layout with sections
195
- div(class: "grid grid-cols-2 gap-6") {
196
- div {
197
- h3(class: "text-lg font-medium") { "Basic Info" }
198
- render_resource_field :title
199
- render_resource_field :slug
200
- }
201
-
202
- div {
203
- h3(class: "text-lg font-medium") { "Content" }
204
- render_resource_field :content
205
- }
206
- }
207
-
208
- div(class: "mt-6") {
209
- h3(class: "text-lg font-medium") { "Publishing" }
210
- render_resource_field :published_at
211
- render_resource_field :category
212
- }
213
-
214
- render_actions
215
- end
216
- end
217
- end
218
- ```
219
-
220
- ### Form Methods
221
-
222
- | Method | Purpose |
223
- |--------|---------|
224
- | `render_fields` | Render all permitted fields |
225
- | `render_resource_field(name)` | Render a single field |
226
- | `render_actions` | Render submit buttons |
227
- | `record` | The form object (alias: `object`) |
228
- | `resource_fields` | List of permitted field names |
229
- | `resource_definition` | The definition instance |
230
-
231
- ## Display Customization
232
-
233
- ### Custom Display Template
234
-
235
- Override the show page detail rendering:
236
-
237
- ```ruby
238
- class PostDefinition < ResourceDefinition
239
- class Display < Display
240
- def display_template
241
- # Hero section
242
- div(class: "bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-lg text-white mb-6") {
243
- h1(class: "text-3xl font-bold") { object.title }
244
- p(class: "mt-2 opacity-90") { object.excerpt }
245
- }
246
-
247
- # Main content
248
- Block do
249
- fields_wrapper do
250
- render_resource_field :author
251
- render_resource_field :published_at
252
- render_resource_field :category
253
- end
254
- end
255
-
256
- # Full-width content
257
- Block do
258
- div(class: "prose max-w-none") {
259
- raw object.content
260
- }
261
- end
262
-
263
- # Associations (tabs)
264
- render_associations if present_associations?
265
- end
266
- end
267
- end
268
- ```
269
-
270
- ### Display Methods
271
-
272
- | Method | Purpose |
273
- |--------|---------|
274
- | `render_fields` | Render all permitted fields in a block |
275
- | `render_resource_field(name)` | Render single field |
276
- | `render_associations` | Render association tabs |
277
- | `object` | The record being displayed |
278
- | `resource_fields` | List of permitted field names |
279
- | `resource_associations` | List of permitted associations |
280
-
281
- ## Table Customization
282
-
283
- ### Custom Table Rendering
284
-
285
- Override list page table:
286
-
287
- ```ruby
288
- class PostDefinition < ResourceDefinition
289
- class Table < Table
290
- def view_template
291
- render_search_bar
292
- render_scopes_bar
293
-
294
- if collection.empty?
295
- render_empty_card
296
- else
297
- # Custom card grid instead of table
298
- div(class: "grid grid-cols-3 gap-4") {
299
- collection.each do |post|
300
- render PostCardComponent.new(post:)
301
- end
302
- }
303
- end
304
-
305
- render_footer
306
- end
307
- end
308
- end
309
- ```
310
-
311
- ### Table Methods
312
-
313
- | Method | Purpose |
314
- |--------|---------|
315
- | `render_search_bar` | Search input |
316
- | `render_scopes_bar` | Scope tabs |
317
- | `render_table` | Default table |
318
- | `render_empty_card` | Empty state |
319
- | `render_footer` | Pagination |
320
- | `collection` | The paginated records |
321
- | `resource_fields` | Column field names |
322
-
323
- ## Component Kit
324
-
325
- Plutonium provides shorthand methods for common components:
326
-
327
- ```ruby
328
- class MyPage < Plutonium::UI::Page::Base
329
- def render_content
330
- # These are automatically rendered
331
- PageHeader(title: "Dashboard")
332
-
333
- Panel(class: "mt-4") {
334
- p { "Content here" }
335
- }
336
-
337
- Block {
338
- TabList(items: tabs)
339
- }
340
-
341
- EmptyCard("No items found")
342
-
343
- ActionButton(action, url: "/posts/new")
344
- end
345
- end
346
- ```
347
-
348
- Available kit methods:
349
- - `Breadcrumbs()`
350
- - `PageHeader(title:, description:, actions:)`
351
- - `Panel(**attrs)`
352
- - `Block(**attrs)`
353
- - `TabList(items:)`
354
- - `EmptyCard(message)`
355
- - `ActionButton(action, url:)`
356
- - `DynaFrameHost(src:, loading:)` - Lazy-loading turbo frame
357
- - `DynaFrameContent(content) { |frame| ... }` - Frame-aware content wrapper
358
- - `TableSearchBar()`
359
- - `TableScopesBar()`
360
- - `TableInfo(pagy)`
361
- - `TablePagination(pagy)`
362
-
363
- ## DynaFrameContent Pattern
364
-
365
- `DynaFrameContent` enables frame-aware rendering. For turbo-frame requests, only the content is rendered inside the frame. For regular requests, the full page renders with header/footer.
366
-
367
- ```ruby
368
- # How Page::Base uses DynaFrameContent
369
- def view_template(&block)
370
- DynaFrameContent(page_content(block)) do |frame|
371
- render_header # Skipped for frame requests
372
- frame.render_content # Always rendered (in turbo-frame for frame requests)
373
- render_footer # Skipped for frame requests
374
- end
375
- end
376
- ```
377
-
378
- This pattern means:
379
- - **Regular page load**: Full page with header, content, footer
380
- - **Turbo-frame request**: Only `<turbo-frame id="...">` with content inside
381
-
382
- All pages automatically inherit this behavior. No special handling needed for modals or frame navigation.
383
-
384
- ## Custom Components
385
-
386
- ### Creating a Phlex Component
387
-
388
- ```ruby
389
- # app/components/post_card_component.rb
390
- class PostCardComponent < Plutonium::UI::Component::Base
391
- def initialize(post:)
392
- @post = post
393
- end
394
-
395
- def view_template
396
- div(class: "bg-white rounded-lg shadow p-4") {
397
- h3(class: "font-bold") { @post.title }
398
- p(class: "text-gray-600 mt-2") { @post.excerpt }
399
-
400
- div(class: "mt-4 flex justify-between items-center") {
401
- span(class: "text-sm text-gray-500") { @post.published_at&.strftime("%B %d, %Y") }
402
- a(href: resource_url_for(@post), class: "text-blue-600") { "Read more" }
403
- }
404
- }
405
- end
406
- end
407
- ```
408
-
409
- ### Using in Definitions
410
-
411
- Reference components in field definitions:
412
-
413
- ```ruby
414
- class PostDefinition < ResourceDefinition
415
- # Custom display component
416
- display :status, as: StatusBadgeComponent
417
-
418
- # Custom input component
419
- input :color, as: ColorPickerComponent
420
-
421
- # Block with component
422
- display :metrics do |field|
423
- MetricsChartComponent.new(data: field.value)
424
- end
425
- end
426
- ```
427
-
428
- ## Page Chrome (Shell)
429
-
430
- `Plutonium.configuration.shell` controls the layout shipped above the
431
- resource pages. Default is **`:modern`** (topbar + icon rail) — only
432
- override to **`:classic`** (legacy header + sidebar) if you're upgrading
433
- from a pre-`:modern` version and want to preserve the old chrome:
434
-
435
- ```ruby
436
- Plutonium.configure do |config|
437
- config.shell = :classic
438
- end
439
- ```
440
-
441
- To customize the shipped chrome per-portal, eject the templates:
442
-
443
- ```bash
444
- rails generate pu:eject:shell --dest=admin_portal
445
- ```
446
-
447
- This copies `_resource_header.html.erb` and `_resource_sidebar.html.erb`
448
- into the portal's `app/views/plutonium/`. The eject is independent of
449
- `shell` — you can run it on either.
450
-
451
- ## Modal & Slideover Forms
452
-
453
- The framework's `:new` / `:edit` actions render inline inside a modal.
454
- Choose the chrome per-resource via the `modal` DSL on the definition:
455
-
456
- ```ruby
457
- class PostDefinition < ResourceDefinition
458
- modal :slideover # default — slide-in panel from the right
459
- # modal :centered # centered dialog
460
- # modal false # full standalone page (no modal)
461
- end
462
- ```
463
-
464
- Custom interactive actions render in their own dialog with their own
465
- `modal:` option (`:centered` default, or `:slideover`).
466
-
467
- ### Detecting render context in components
468
-
469
- Custom pages / forms can branch on render context:
470
-
471
- | Helper | True when |
472
- |--------|-----------|
473
- | `in_frame?` | Request is targeting a turbo-frame |
474
- | `in_modal?` | Request is rendering inside a modal/slideover |
475
-
476
- Use them to pin action strips, omit nav chrome, or swap layouts.
477
-
478
- ## Tabs & URL Hash
479
-
480
- Show pages with associations render the **Details** tab first followed
481
- by one tab per permitted association. The active tab is reflected in
482
- the URL hash (`#products`, `#refund-requests`) so the page deep-links and
483
- the active state survives reloads / back navigation.
484
-
485
- Tab rows scroll horizontally on narrow viewports — they don't wrap.
486
-
487
- ## Layout Customization
488
-
489
- ### Eject Layout
490
-
491
- Copy the layout template to your project:
492
-
493
- ```bash
494
- rails generate pu:eject:layout
495
- ```
496
-
497
- This copies `layouts/resource.html.erb` to your portal.
498
-
499
- ### Custom Layout Class
500
-
501
- Override the Phlex layout:
502
-
503
- ```ruby
504
- # packages/admin_portal/app/views/layouts/admin_portal/resource_layout.rb
505
- module AdminPortal
506
- class ResourceLayout < Plutonium::UI::Layout::ResourceLayout
507
- private
508
-
509
- # Custom body classes
510
- def body_attributes
511
- {class: "antialiased bg-slate-100 dark:bg-slate-900"}
512
- end
513
-
514
- # Add custom header content
515
- def render_before_main
516
- super
517
- render AnnouncementBanner.new if Announcement.active.any?
518
- end
519
-
520
- # Custom scripts
521
- def render_body_scripts
522
- super
523
- script(src: "/custom-analytics.js")
524
- end
525
- end
526
- end
527
- ```
528
-
529
- ### Layout Hooks
530
-
531
- | Hook | Purpose |
532
- |------|---------|
533
- | `render_before_main` | Before main content area |
534
- | `render_after_main` | After main (modals, etc.) |
535
- | `render_before_content` | Inside main, before content |
536
- | `render_after_content` | Inside main, after content |
537
- | `render_flash` | Flash messages |
538
- | `render_head` | HTML head section |
539
- | `render_title` | Page title tag |
540
- | `render_metatags` | Meta tags |
541
- | `render_assets` | CSS/JS assets |
542
- | `render_body_scripts` | Scripts at end of body |
543
-
544
- ## Available Context
545
-
546
- Both ERB views and Phlex components have access to the same context.
547
-
548
- ### Resource Methods
549
-
550
- | Method | Description |
551
- |--------|-------------|
552
- | `resource_class` | The model class (e.g., `Post`) |
553
- | `resource_record!` | Current record (raises if not found) |
554
- | `resource_record?` | Current record (nil if not found) |
555
- | `current_parent` | Parent record for nested routes |
556
- | `current_scoped_entity` | Entity for multi-tenant portals |
557
-
558
- ### Definition & Policy
559
-
560
- | Method | Description |
561
- |--------|-------------|
562
- | `current_definition` | Definition instance for current resource |
563
- | `current_policy` | Policy instance for current record |
564
- | `current_authorized_scope` | Scoped collection user can access |
565
-
566
- ### Authentication
567
-
568
- | Method | Description |
569
- |--------|-------------|
570
- | `current_user` | Authenticated user (if using Rodauth) |
571
-
572
- ### URL Helpers
573
-
574
- | Method | Description |
575
- |--------|-------------|
576
- | `resource_url_for(record)` | URL for a record |
577
- | `resource_url_for(record, action: :edit)` | Action URL for record |
578
- | `resource_url_for(Model)` | Index URL for model |
579
- | `resource_url_for(Model, action: :new)` | New URL for model |
580
- | `resource_url_for(record, parent: parent)` | Nested resource URL |
581
-
582
- ### Display Helpers
583
-
584
- | Method | Description |
585
- |--------|-------------|
586
- | `display_name_of(record)` | Human-readable name for record |
587
- | `resource_name(klass)` | Singular model name |
588
- | `resource_name_plural(klass)` | Plural model name |
589
-
590
- ### In Phlex Components
591
-
592
- ```ruby
593
- class MyComponent < Plutonium::UI::Component::Base
594
- def view_template
595
- # All the above methods work directly
596
- current_user
597
- resource_record!
598
- resource_url_for(@post)
599
-
600
- # Rails helpers via helpers proxy
601
- helpers.link_to(...)
602
- helpers.image_tag(...)
603
- helpers.number_to_currency(...)
604
- end
605
- end
606
- ```
607
-
608
- ### In ERB Views
609
-
610
- ```erb
611
- <%# All methods available directly %>
612
- <%= resource_record!.title %>
613
- <%= current_user.name %>
614
- <%= link_to "Edit", resource_url_for(resource_record!, action: :edit) %>
615
-
616
- <%# Render Phlex components %>
617
- <%= render current_definition.show_page_class.new %>
618
- <%= render MyCustomComponent.new(post: resource_record!) %>
619
- ```
620
-
621
- ## Portal-Specific Views
622
-
623
- Each portal can have its own view overrides:
624
-
625
- ```ruby
626
- # Base definition
627
- class PostDefinition < ResourceDefinition
628
- class ShowPage < ShowPage
629
- # Default behavior
630
- end
631
- end
632
-
633
- # Admin portal override
634
- class AdminPortal::PostDefinition < ::PostDefinition
635
- class ShowPage < ShowPage # Inherits from ::PostDefinition::ShowPage
636
- def render_after_content
637
- super
638
- render AdminOnlySection.new(post: object)
639
- end
640
- end
641
- end
642
- ```
643
-
644
- ## Related Skills
645
-
646
- - `plutonium-forms` - Custom form templates and field builders
647
- - `plutonium-assets` - TailwindCSS and component theming
648
- - `plutonium-definition` - Field/input/display configuration
649
- - `plutonium-definition` - Action buttons and interactions
650
- - `plutonium-controller` - Presentation hooks (`present_parent?`, etc.)
651
- - `plutonium-portal` - Portal-specific customization