plutonium 0.49.1 → 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 (206) 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 +37 -0
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1323 -1184
  14. data/app/assets/plutonium.js.map +4 -4
  15. data/app/assets/plutonium.min.js +50 -49
  16. data/app/assets/plutonium.min.js.map +4 -4
  17. data/app/views/plutonium/_resource_header.html.erb +4 -4
  18. data/app/views/plutonium/_resource_sidebar.html.erb +9 -9
  19. data/app/views/resource/_resource_grid.html.erb +1 -0
  20. data/config/brakeman.ignore +25 -2
  21. data/docs/.vitepress/config.ts +37 -27
  22. data/docs/getting-started/index.md +22 -29
  23. data/docs/getting-started/installation.md +37 -80
  24. data/docs/getting-started/tutorial/index.md +4 -5
  25. data/docs/guides/adding-resources.md +66 -377
  26. data/docs/guides/authentication.md +94 -463
  27. data/docs/guides/authorization.md +124 -370
  28. data/docs/guides/creating-packages.md +94 -296
  29. data/docs/guides/custom-actions.md +121 -441
  30. data/docs/guides/index.md +22 -42
  31. data/docs/guides/multi-tenancy.md +116 -187
  32. data/docs/guides/nested-resources.md +103 -431
  33. data/docs/guides/search-filtering.md +123 -240
  34. data/docs/guides/testing.md +5 -4
  35. data/docs/guides/theming.md +157 -407
  36. data/docs/guides/troubleshooting.md +5 -3
  37. data/docs/guides/user-invites.md +106 -425
  38. data/docs/guides/user-profile.md +76 -243
  39. data/docs/index.md +1 -1
  40. data/docs/reference/app/generators.md +517 -0
  41. data/docs/reference/app/index.md +158 -0
  42. data/docs/reference/app/packages.md +146 -0
  43. data/docs/reference/app/portals.md +377 -0
  44. data/docs/reference/auth/accounts.md +230 -0
  45. data/docs/reference/auth/index.md +88 -0
  46. data/docs/reference/auth/profile.md +185 -0
  47. data/docs/reference/behavior/controllers.md +395 -0
  48. data/docs/reference/behavior/index.md +22 -0
  49. data/docs/reference/behavior/interactions.md +341 -0
  50. data/docs/reference/behavior/policies.md +417 -0
  51. data/docs/reference/index.md +56 -49
  52. data/docs/reference/resource/actions.md +423 -0
  53. data/docs/reference/resource/definition.md +508 -0
  54. data/docs/reference/resource/index.md +50 -0
  55. data/docs/reference/resource/model.md +348 -0
  56. data/docs/reference/resource/query.md +305 -0
  57. data/docs/reference/tenancy/entity-scoping.md +361 -0
  58. data/docs/reference/tenancy/index.md +36 -0
  59. data/docs/reference/tenancy/invites.md +393 -0
  60. data/docs/reference/tenancy/nested-resources.md +267 -0
  61. data/docs/reference/testing/index.md +287 -0
  62. data/docs/reference/ui/assets.md +400 -0
  63. data/docs/reference/ui/components.md +165 -0
  64. data/docs/reference/ui/displays.md +104 -0
  65. data/docs/reference/ui/forms.md +284 -0
  66. data/docs/reference/ui/index.md +30 -0
  67. data/docs/reference/ui/layouts.md +106 -0
  68. data/docs/reference/ui/pages.md +189 -0
  69. data/docs/reference/ui/tables.md +117 -0
  70. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md +841 -0
  71. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json +103 -0
  72. data/docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md +270 -0
  73. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  74. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  75. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  76. data/gemfiles/rails_7.gemfile.lock +1 -1
  77. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  78. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  79. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -0
  80. data/lib/generators/pu/invites/install_generator.rb +1 -0
  81. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +54 -5
  82. data/lib/plutonium/action/base.rb +44 -1
  83. data/lib/plutonium/action/interactive.rb +1 -1
  84. data/lib/plutonium/configuration.rb +4 -0
  85. data/lib/plutonium/definition/actions.rb +3 -0
  86. data/lib/plutonium/definition/base.rb +8 -0
  87. data/lib/plutonium/definition/index_views.rb +95 -0
  88. data/lib/plutonium/definition/metadata.rb +40 -0
  89. data/lib/plutonium/helpers/turbo_helper.rb +12 -1
  90. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +1 -1
  92. data/lib/plutonium/query/base.rb +8 -0
  93. data/lib/plutonium/query/filters/association.rb +30 -8
  94. data/lib/plutonium/query/filters/boolean.rb +5 -0
  95. data/lib/plutonium/resource/controller.rb +1 -0
  96. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  97. data/lib/plutonium/resource/controllers/presentable.rb +11 -2
  98. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  99. data/lib/plutonium/resource/definition.rb +42 -0
  100. data/lib/plutonium/resource/policy.rb +7 -0
  101. data/lib/plutonium/resource/query_object.rb +64 -6
  102. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  103. data/lib/plutonium/testing/resource_definition.rb +2 -2
  104. data/lib/plutonium/ui/action_button.rb +4 -2
  105. data/lib/plutonium/ui/component/kit.rb +12 -0
  106. data/lib/plutonium/ui/component/methods.rb +4 -0
  107. data/lib/plutonium/ui/display/base.rb +3 -1
  108. data/lib/plutonium/ui/display/resource.rb +109 -25
  109. data/lib/plutonium/ui/display/theme.rb +2 -1
  110. data/lib/plutonium/ui/dyna_frame/content.rb +8 -14
  111. data/lib/plutonium/ui/empty_card.rb +1 -1
  112. data/lib/plutonium/ui/form/base.rb +35 -3
  113. data/lib/plutonium/ui/form/components/hidden_wrapper.rb +25 -0
  114. data/lib/plutonium/ui/form/components/json.rb +58 -0
  115. data/lib/plutonium/ui/form/components/resource_select.rb +133 -1
  116. data/lib/plutonium/ui/form/components/secure_association.rb +105 -24
  117. data/lib/plutonium/ui/form/components/sticky_footer.rb +17 -0
  118. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  119. data/lib/plutonium/ui/form/resource.rb +45 -10
  120. data/lib/plutonium/ui/form/theme.rb +1 -1
  121. data/lib/plutonium/ui/frame_navigator_panel.rb +7 -4
  122. data/lib/plutonium/ui/grid/card.rb +235 -0
  123. data/lib/plutonium/ui/grid/resource.rb +149 -0
  124. data/lib/plutonium/ui/layout/base.rb +38 -1
  125. data/lib/plutonium/ui/layout/header.rb +1 -2
  126. data/lib/plutonium/ui/layout/icon_rail.rb +212 -0
  127. data/lib/plutonium/ui/layout/resource_layout.rb +10 -3
  128. data/lib/plutonium/ui/layout/sidebar.rb +12 -24
  129. data/lib/plutonium/ui/layout/topbar.rb +100 -0
  130. data/lib/plutonium/ui/modal/base.rb +109 -0
  131. data/lib/plutonium/ui/modal/centered.rb +21 -0
  132. data/lib/plutonium/ui/modal/slideover.rb +26 -0
  133. data/lib/plutonium/ui/page/base.rb +18 -6
  134. data/lib/plutonium/ui/page/edit.rb +13 -1
  135. data/lib/plutonium/ui/page/index.rb +40 -1
  136. data/lib/plutonium/ui/page/interactive_action.rb +8 -39
  137. data/lib/plutonium/ui/page/new.rb +13 -1
  138. data/lib/plutonium/ui/page/show.rb +8 -1
  139. data/lib/plutonium/ui/page_header.rb +8 -13
  140. data/lib/plutonium/ui/panel.rb +10 -19
  141. data/lib/plutonium/ui/sidebar_menu.rb +2 -25
  142. data/lib/plutonium/ui/tab_list.rb +29 -7
  143. data/lib/plutonium/ui/table/base.rb +106 -0
  144. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +12 -4
  145. data/lib/plutonium/ui/table/components/filter_form.rb +171 -0
  146. data/lib/plutonium/ui/table/components/filter_pills.rb +89 -0
  147. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +13 -12
  148. data/lib/plutonium/ui/table/components/scopes_pills.rb +67 -0
  149. data/lib/plutonium/ui/table/components/selection_column.rb +2 -11
  150. data/lib/plutonium/ui/table/components/toolbar.rb +104 -0
  151. data/lib/plutonium/ui/table/components/view_switcher.rb +81 -0
  152. data/lib/plutonium/ui/table/resource.rb +158 -89
  153. data/lib/plutonium/ui/table/theme.rb +14 -5
  154. data/lib/plutonium/version.rb +1 -1
  155. data/lib/plutonium.rb +14 -0
  156. data/lib/tasks/release.rake +15 -1
  157. data/package.json +10 -10
  158. data/src/css/components.css +304 -131
  159. data/src/css/slim_select.css +4 -0
  160. data/src/css/tokens.css +101 -85
  161. data/src/js/controllers/autosubmit_controller.js +24 -0
  162. data/src/js/controllers/bulk_actions_controller.js +15 -16
  163. data/src/js/controllers/capture_url_controller.js +14 -0
  164. data/src/js/controllers/filter_panel_controller.js +77 -19
  165. data/src/js/controllers/frame_navigator_controller.js +34 -6
  166. data/src/js/controllers/icon_rail_controller.js +22 -0
  167. data/src/js/controllers/icon_rail_flyout_controller.js +128 -0
  168. data/src/js/controllers/register_controllers.js +16 -0
  169. data/src/js/controllers/resource_tab_list_controller.js +56 -3
  170. data/src/js/controllers/row_click_controller.js +21 -0
  171. data/src/js/controllers/slim_select_controller.js +61 -0
  172. data/src/js/controllers/table_column_menu_controller.js +43 -0
  173. data/src/js/controllers/table_header_controller.js +16 -0
  174. data/src/js/controllers/view_switcher_controller.js +29 -0
  175. data/src/js/turbo/turbo_actions.js +33 -0
  176. data/yarn.lock +553 -543
  177. metadata +71 -32
  178. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  179. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  180. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  181. data/.claude/skills/plutonium-definition/SKILL.md +0 -1138
  182. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  183. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  184. data/.claude/skills/plutonium-installation/SKILL.md +0 -325
  185. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  186. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  187. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  188. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  189. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  190. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  191. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  192. data/.claude/skills/plutonium-views/SKILL.md +0 -592
  193. data/docs/reference/assets/index.md +0 -496
  194. data/docs/reference/controller/index.md +0 -412
  195. data/docs/reference/definition/actions.md +0 -449
  196. data/docs/reference/definition/fields.md +0 -383
  197. data/docs/reference/definition/index.md +0 -268
  198. data/docs/reference/definition/query.md +0 -351
  199. data/docs/reference/generators/index.md +0 -648
  200. data/docs/reference/interaction/index.md +0 -449
  201. data/docs/reference/model/features.md +0 -248
  202. data/docs/reference/model/index.md +0 -218
  203. data/docs/reference/policy/index.md +0 -456
  204. data/docs/reference/portal/index.md +0 -379
  205. data/docs/reference/views/forms.md +0 -411
  206. data/docs/reference/views/index.md +0 -501
@@ -0,0 +1,189 @@
1
+ # Pages
2
+
3
+ Each definition has nested page classes for index / show / new / edit / interactive-action. Override the ones you need.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Definition
9
+ ├── IndexPage → renders Table
10
+ ├── ShowPage → renders Display
11
+ ├── NewPage → renders Form
12
+ ├── EditPage → renders Form
13
+ └── InteractiveActionPage → renders Form
14
+ ```
15
+
16
+ ## Page classes
17
+
18
+ ```ruby
19
+ class PostDefinition < ResourceDefinition
20
+ class IndexPage < IndexPage; end
21
+ class ShowPage < ShowPage; end
22
+ class NewPage < NewPage; end
23
+ class EditPage < EditPage; end
24
+ class InteractiveActionPage < InteractiveActionPage; end
25
+ class Form < Form; end
26
+ class Table < Table; end
27
+ class Display < Display; end
28
+ end
29
+ ```
30
+
31
+ ## Page titles, descriptions, breadcrumbs
32
+
33
+ ```ruby
34
+ class PostDefinition < ResourceDefinition
35
+ index_page_title "Blog Posts"
36
+ index_page_description "Manage all published articles"
37
+ show_page_title "Article Details"
38
+ show_page_title -> { current_record!.title } # dynamic
39
+ new_page_title "Create Post"
40
+ edit_page_title -> { "Edit: #{current_record!.title}" }
41
+
42
+ breadcrumbs true # global default
43
+ index_page_breadcrumbs false # per-page override
44
+ show_page_breadcrumbs true
45
+ new_page_breadcrumbs true
46
+ edit_page_breadcrumbs true
47
+ interactive_action_page_breadcrumbs true
48
+ end
49
+ ```
50
+
51
+ ## Page hooks (preferred over `view_template`)
52
+
53
+ Every page inherits these — use them instead of overriding `view_template` to preserve breadcrumbs, header, and DynaFrame behavior:
54
+
55
+ | Hook | Position |
56
+ |---|---|
57
+ | `render_before_header` / `_after_header` | wraps the entire header section |
58
+ | `render_before_breadcrumbs` / `_after_breadcrumbs` | around the breadcrumb row |
59
+ | `render_before_page_header` / `_after_page_header` | around the title + actions block |
60
+ | `render_before_toolbar` / `_after_toolbar` | around the action toolbar |
61
+ | `render_before_content` / `_after_content` | around main content |
62
+ | `render_before_footer` / `_after_footer` | around footer/pagination |
63
+
64
+ Example:
65
+
66
+ ```ruby
67
+ class ShowPage < ShowPage
68
+ private
69
+
70
+ def page_title
71
+ "#{object.title} — #{object.author.name}"
72
+ end
73
+
74
+ def render_before_content
75
+ div(class: "alert alert-info") do
76
+ plain "This post has #{object.comments.count} comments"
77
+ end
78
+ end
79
+
80
+ def render_after_content
81
+ render RelatedPostsComponent.new(post: object)
82
+ end
83
+
84
+ def render_toolbar
85
+ div(class: "flex gap-2") do
86
+ button(class: "pu-btn pu-btn-md pu-btn-secondary") { "Preview" }
87
+ button(class: "pu-btn pu-btn-md pu-btn-primary") { "Publish" }
88
+ end
89
+ end
90
+ end
91
+ ```
92
+
93
+ ## Custom ERB views (full replacement)
94
+
95
+ For total control, drop the page class entirely with an ERB view at the controller path:
96
+
97
+ ```
98
+ app/views/posts/show.html.erb
99
+ packages/admin_portal/app/views/admin_portal/posts/show.html.erb
100
+ ```
101
+
102
+ The default view simply renders the page class:
103
+
104
+ ```erb
105
+ <%= render current_definition.show_page_class.new %>
106
+ ```
107
+
108
+ Mix — keep the default and add chrome around it:
109
+
110
+ ```erb
111
+ <div class="announcement-banner">Special announcement</div>
112
+ <%= render current_definition.show_page_class.new %>
113
+ <div class="related"><%= render partial: "related" %></div>
114
+ ```
115
+
116
+ ## Detecting render context
117
+
118
+ | Helper | True when |
119
+ |---|---|
120
+ | `in_frame?` | Request targets a turbo-frame |
121
+ | `in_modal?` | Request renders inside a modal/slideover (primary or secondary) |
122
+ | `in_secondary_modal?` | Request renders inside the stacked secondary modal |
123
+
124
+ Use to pin action strips, omit nav chrome, or swap layouts.
125
+
126
+ ### Stacked modals (secondary frame)
127
+
128
+ Association inputs include an inline `+` button. When the parent form is itself rendered in a modal, the `+` opens a **second stacked modal** in `Plutonium::REMOTE_MODAL_SECONDARY_FRAME` instead of replacing the primary modal. On successful create, the secondary closes and the primary frame reloads so the new record appears in the select — no developer wiring.
129
+
130
+ For custom flows: `helpers.turbo_stream_close_frame(frame_id)` and `helpers.turbo_stream_reload_frame(frame_id)` are available.
131
+
132
+ See [Forms › Association inputs](./forms#association-inputs).
133
+
134
+ ## Modals & slideovers
135
+
136
+ The framework's `:new` / `:edit` actions render inline inside a modal. Choose the chrome per-resource via the definition:
137
+
138
+ ```ruby
139
+ class PostDefinition < ResourceDefinition
140
+ modal :slideover # default — slide-in panel from the right
141
+ # modal :centered # centered dialog
142
+ # modal false # full standalone pages (no modal)
143
+ end
144
+ ```
145
+
146
+ Custom interactive actions render in their own dialog with their own per-action `modal:` option (`:centered` default, or `:slideover`). See [Resource › Actions](/reference/resource/actions#action-options).
147
+
148
+ ## Tabs on the show page
149
+
150
+ Show pages with `permitted_associations` (see [Behavior › Policy](/reference/behavior/policies#association-permissions)) render a tablist: **Details** tab first, then one tab per association. The active tab is reflected in the URL hash (`#products`, `#refund-requests`) so the page deep-links and the active state survives reload / back navigation. Tab rows scroll horizontally on narrow viewports — they don't wrap.
151
+
152
+ ## Portal-specific overrides
153
+
154
+ Each portal can override page classes independently. The portal definition inherits from the base definition, and its nested classes inherit from the base's nested classes:
155
+
156
+ ```ruby
157
+ class AdminPortal::PostDefinition < ::PostDefinition
158
+ class ShowPage < ShowPage # inherits from ::PostDefinition::ShowPage
159
+ def render_after_content
160
+ super
161
+ render AdminOnlySection.new(post: object)
162
+ end
163
+ end
164
+ end
165
+ ```
166
+
167
+ ## Available context
168
+
169
+ Inside any page / form / display / Phlex component, the same set of helpers is available — model accessors, definition/policy methods, URL helpers, `current_user`. For the full list, see [Behavior › Controllers › Key methods](/reference/behavior/controllers#key-methods) — pages inherit the same surface.
170
+
171
+ In Phlex components, Rails helpers are accessed via the `helpers` proxy:
172
+
173
+ ```ruby
174
+ class MyComponent < Plutonium::UI::Component::Base
175
+ def view_template
176
+ helpers.link_to(...)
177
+ helpers.number_to_currency(...)
178
+ end
179
+ end
180
+ ```
181
+
182
+ ## Related
183
+
184
+ - [Forms](./forms) — Form class, field builder, themes
185
+ - [Displays](./displays) — show-page Display class
186
+ - [Tables](./tables) — index-page Table class
187
+ - [Components](./components) — built-in component kit, custom Phlex components, DynaFrame
188
+ - [Layouts](./layouts) — overall shell, eject, ResourceLayout
189
+ - [Resource › Definition](/reference/resource/definition) — page titles, breadcrumbs, modal mode, metadata panel
@@ -0,0 +1,117 @@
1
+ # Tables
2
+
3
+ The index page's table rendering. Override the `Table` nested class in your definition for custom layouts (e.g. card grids).
4
+
5
+ ## Custom table template
6
+
7
+ ```ruby
8
+ class PostDefinition < ResourceDefinition
9
+ class Table < Table
10
+ def view_template
11
+ render_search_bar
12
+ render_scopes_bar
13
+
14
+ if collection.empty?
15
+ render_empty_card
16
+ else
17
+ # Replace the table with a card grid
18
+ div(class: "grid grid-cols-3 gap-4") do
19
+ collection.each { |post| render PostCardComponent.new(post:) }
20
+ end
21
+ end
22
+
23
+ render_footer
24
+ end
25
+ end
26
+ end
27
+ ```
28
+
29
+ ## Methods
30
+
31
+ | Method | Purpose |
32
+ |---|---|
33
+ | `render_search_bar` | Toolbar search input |
34
+ | `render_scopes_bar` | Quick-filter scope buttons |
35
+ | `render_table` | Default table rendering |
36
+ | `render_empty_card` | Empty state |
37
+ | `render_footer` | Pagination |
38
+ | `collection` | Paginated records |
39
+ | `resource_fields` | Column field names |
40
+
41
+ ## Per-column customization
42
+
43
+ Prefer declaring column behavior in the **definition** rather than overriding the entire `Table`:
44
+
45
+ ```ruby
46
+ class PostDefinition < ResourceDefinition
47
+ column :title, align: :start # default
48
+ column :status, align: :center
49
+ column :amount, align: :end
50
+
51
+ # formatter — receives just the value
52
+ column :description, formatter: ->(value) { value&.truncate(30) }
53
+ column :price, formatter: ->(value) { "$%.2f" % value if value }
54
+
55
+ # block — receives the full record
56
+ column :full_name do |record|
57
+ "#{record.first_name} #{record.last_name}"
58
+ end
59
+ end
60
+ ```
61
+
62
+ See [Resource › Definition › Column options](/reference/resource/definition#column-options).
63
+
64
+ ## Grid view
65
+
66
+ For card-based layouts as a switchable alternative to the table, use the built-in Grid view — declare `grid_fields` in the definition:
67
+
68
+ ```ruby
69
+ class UserDefinition < ResourceDefinition
70
+ grid_fields(
71
+ image: :avatar,
72
+ header: :name,
73
+ subheader: :email,
74
+ body: :bio,
75
+ meta: [:role, :status],
76
+ footer: :last_seen_at
77
+ )
78
+
79
+ default_index_view :grid
80
+ end
81
+ ```
82
+
83
+ See [Resource › Definition › Index views](/reference/resource/definition#index-views-table-grid). You only need a custom `Table` class when you want something neither Table nor Grid covers.
84
+
85
+ ## Theming
86
+
87
+ Override the theme via a nested `Theme` class:
88
+
89
+ ```ruby
90
+ class PostDefinition < ResourceDefinition
91
+ class Table < Table
92
+ class Theme < Plutonium::UI::Table::Theme
93
+ def self.theme
94
+ super.merge(
95
+ wrapper: "pu-table-wrapper",
96
+ base: "pu-table",
97
+ header: "pu-table-header",
98
+ header_cell: "pu-table-header-cell",
99
+ body_row: "pu-table-body-row",
100
+ body_cell: "pu-table-body-cell"
101
+ )
102
+ end
103
+ end
104
+ end
105
+ end
106
+ ```
107
+
108
+ ### Theme keys
109
+
110
+ `wrapper`, `base`, `header`, `header_cell`, `body_row`, `body_cell`, `sort_icon`.
111
+
112
+ ## Related
113
+
114
+ - [Pages](./pages) — `IndexPage` render hooks (a lighter alternative for top/bottom chrome)
115
+ - [Components](./components) — `PostCardComponent` and other reusable Phlex pieces
116
+ - [Resource › Definition](/reference/resource/definition) — column configuration, grid view
117
+ - [Resource › Query](/reference/resource/query) — search, filters, scopes, sort