plutonium 0.34.1 → 0.35.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/skill.md +53 -0
  3. data/.claude/skills/{assets → plutonium-assets}/SKILL.md +13 -8
  4. data/.claude/skills/{connect-resource → plutonium-connect-resource}/SKILL.md +1 -1
  5. data/.claude/skills/{controller → plutonium-controller}/SKILL.md +27 -13
  6. data/.claude/skills/{create-resource → plutonium-create-resource}/SKILL.md +1 -1
  7. data/.claude/skills/{definition → plutonium-definition}/SKILL.md +10 -10
  8. data/.claude/skills/{definition-actions → plutonium-definition-actions}/SKILL.md +34 -9
  9. data/.claude/skills/{definition-fields → plutonium-definition-fields}/SKILL.md +38 -10
  10. data/.claude/skills/plutonium-definition-query/SKILL.md +356 -0
  11. data/.claude/skills/{forms → plutonium-forms}/SKILL.md +6 -6
  12. data/.claude/skills/{installation → plutonium-installation}/SKILL.md +9 -9
  13. data/.claude/skills/{interaction → plutonium-interaction}/SKILL.md +20 -19
  14. data/.claude/skills/{model → plutonium-model}/SKILL.md +3 -3
  15. data/.claude/skills/{model-features → plutonium-model-features}/SKILL.md +3 -3
  16. data/.claude/skills/{nested-resources → plutonium-nested-resources}/SKILL.md +5 -5
  17. data/.claude/skills/{package → plutonium-package}/SKILL.md +7 -8
  18. data/.claude/skills/{policy → plutonium-policy}/SKILL.md +26 -4
  19. data/.claude/skills/{portal → plutonium-portal}/SKILL.md +33 -31
  20. data/.claude/skills/{resource → plutonium-resource}/SKILL.md +27 -27
  21. data/.claude/skills/{rodauth → plutonium-rodauth}/SKILL.md +5 -5
  22. data/.claude/skills/plutonium-theming/SKILL.md +424 -0
  23. data/.claude/skills/{views → plutonium-views}/SKILL.md +7 -7
  24. data/CHANGELOG.md +52 -0
  25. data/CLAUDE.md +215 -0
  26. data/CONTRIBUTING.md +72 -18
  27. data/README.md +100 -19
  28. data/app/assets/plutonium.css +1 -11
  29. data/app/assets/plutonium.js +1685 -1146
  30. data/app/assets/plutonium.js.map +4 -4
  31. data/app/assets/plutonium.min.js +70 -70
  32. data/app/assets/plutonium.min.js.map +4 -4
  33. data/app/views/resource/interactive_bulk_action.html.erb +1 -5
  34. data/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  35. data/app/views/rodauth/_login_form.html.erb +15 -55
  36. data/app/views/rodauth/_login_form_footer.html.erb +2 -2
  37. data/app/views/rodauth/_password_visibility.html.erb +2 -8
  38. data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  39. data/app/views/rodauth/change_login.html.erb +36 -19
  40. data/app/views/rodauth/change_password.html.erb +34 -10
  41. data/app/views/rodauth/close_account.html.erb +12 -4
  42. data/app/views/rodauth/confirm_password.html.erb +19 -17
  43. data/app/views/rodauth/create_account.html.erb +30 -109
  44. data/app/views/rodauth/email_auth.html.erb +1 -1
  45. data/app/views/rodauth/logout.html.erb +4 -4
  46. data/app/views/rodauth/otp_auth.html.erb +13 -4
  47. data/app/views/rodauth/otp_disable.html.erb +12 -4
  48. data/app/views/rodauth/otp_setup.html.erb +29 -12
  49. data/app/views/rodauth/otp_unlock.html.erb +19 -10
  50. data/app/views/rodauth/otp_unlock_not_available.html.erb +7 -7
  51. data/app/views/rodauth/recovery_auth.html.erb +12 -4
  52. data/app/views/rodauth/recovery_codes.html.erb +12 -4
  53. data/app/views/rodauth/remember.html.erb +7 -7
  54. data/app/views/rodauth/reset_password.html.erb +23 -7
  55. data/app/views/rodauth/reset_password_request.html.erb +14 -10
  56. data/app/views/rodauth/sms_auth.html.erb +13 -4
  57. data/app/views/rodauth/sms_confirm.html.erb +13 -4
  58. data/app/views/rodauth/sms_disable.html.erb +12 -4
  59. data/app/views/rodauth/sms_request.html.erb +1 -1
  60. data/app/views/rodauth/sms_setup.html.erb +23 -7
  61. data/app/views/rodauth/two_factor_auth.html.erb +2 -2
  62. data/app/views/rodauth/two_factor_disable.html.erb +12 -4
  63. data/app/views/rodauth/two_factor_manage.html.erb +7 -7
  64. data/app/views/rodauth/unlock_account.html.erb +13 -5
  65. data/app/views/rodauth/unlock_account_request.html.erb +2 -2
  66. data/app/views/rodauth/verify_account.html.erb +25 -7
  67. data/app/views/rodauth/verify_account_resend.html.erb +14 -10
  68. data/app/views/rodauth/verify_login_change.html.erb +1 -1
  69. data/app/views/rodauth/webauthn_auth.html.erb +1 -1
  70. data/app/views/rodauth/webauthn_remove.html.erb +18 -8
  71. data/app/views/rodauth/webauthn_setup.html.erb +12 -4
  72. data/docs/.vitepress/config.ts +15 -26
  73. data/docs/.vitepress/theme/custom.css +388 -29
  74. data/docs/getting-started/index.md +1 -1
  75. data/docs/getting-started/tutorial/02-first-resource.md +9 -0
  76. data/docs/getting-started/tutorial/06-nested-resources.md +2 -2
  77. data/docs/getting-started/tutorial/07-author-portal.md +191 -0
  78. data/docs/getting-started/tutorial/{07-customizing-ui.md → 08-customizing-ui.md} +7 -7
  79. data/docs/getting-started/tutorial/index.md +5 -2
  80. data/docs/guides/authorization.md +33 -0
  81. data/docs/guides/creating-packages.md +12 -16
  82. data/docs/guides/custom-actions.md +36 -0
  83. data/docs/guides/search-filtering.md +121 -42
  84. data/docs/guides/theming.md +232 -36
  85. data/docs/index.md +203 -57
  86. data/docs/public/og-image.png +0 -0
  87. data/docs/reference/controller/index.md +14 -16
  88. data/docs/reference/definition/actions.md +38 -3
  89. data/docs/reference/definition/fields.md +3 -3
  90. data/docs/reference/definition/index.md +2 -2
  91. data/docs/reference/generators/index.md +0 -1
  92. data/docs/reference/interaction/index.md +14 -10
  93. data/docs/reference/model/index.md +0 -1
  94. data/docs/reference/portal/index.md +13 -27
  95. data/gemfiles/rails_7.gemfile.lock +1 -1
  96. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  97. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  98. data/lib/generators/pu/pkg/portal/portal_generator.rb +0 -2
  99. data/lib/generators/pu/pkg/portal/templates/app/views/package/dashboard/index.html.erb +28 -72
  100. data/lib/plutonium/action/interactive.rb +2 -2
  101. data/lib/plutonium/core/controller.rb +2 -1
  102. data/lib/plutonium/definition/actions.rb +2 -2
  103. data/lib/plutonium/lib/deep_freezer.rb +3 -7
  104. data/lib/plutonium/query/filter.rb +14 -0
  105. data/lib/plutonium/query/filters/association.rb +49 -0
  106. data/lib/plutonium/query/filters/boolean.rb +35 -0
  107. data/lib/plutonium/query/filters/date.rb +97 -0
  108. data/lib/plutonium/query/filters/date_range.rb +58 -0
  109. data/lib/plutonium/query/filters/select.rb +55 -0
  110. data/lib/plutonium/resource/controllers/crud_actions.rb +24 -6
  111. data/lib/plutonium/resource/controllers/interactive_actions.rb +76 -58
  112. data/lib/plutonium/resource/controllers/queryable.rb +4 -2
  113. data/lib/plutonium/resource/query_object.rb +1 -1
  114. data/lib/plutonium/ui/action_button.rb +23 -65
  115. data/lib/plutonium/ui/actions_dropdown.rb +103 -0
  116. data/lib/plutonium/ui/block.rb +1 -1
  117. data/lib/plutonium/ui/breadcrumbs.rb +12 -19
  118. data/lib/plutonium/ui/color_mode_selector.rb +1 -1
  119. data/lib/plutonium/ui/component/kit.rb +6 -0
  120. data/lib/plutonium/ui/component_classes.rb +102 -0
  121. data/lib/plutonium/ui/display/base.rb +15 -0
  122. data/lib/plutonium/ui/display/components/attachment.rb +6 -5
  123. data/lib/plutonium/ui/display/components/boolean.rb +23 -0
  124. data/lib/plutonium/ui/display/components/color.rb +23 -0
  125. data/lib/plutonium/ui/display/resource.rb +1 -1
  126. data/lib/plutonium/ui/display/theme.rb +29 -15
  127. data/lib/plutonium/ui/empty_card.rb +3 -3
  128. data/lib/plutonium/ui/form/base.rb +20 -0
  129. data/lib/plutonium/ui/form/components/key_value_store.rb +11 -11
  130. data/lib/plutonium/ui/form/components/resource_select.rb +31 -0
  131. data/lib/plutonium/ui/form/components/secure_association.rb +1 -2
  132. data/lib/plutonium/ui/form/components/uppy.rb +5 -4
  133. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +4 -4
  134. data/lib/plutonium/ui/form/interaction.rb +17 -1
  135. data/lib/plutonium/ui/form/query.rb +133 -80
  136. data/lib/plutonium/ui/form/theme.rb +50 -35
  137. data/lib/plutonium/ui/frame_navigator_panel.rb +2 -2
  138. data/lib/plutonium/ui/layout/base.rb +1 -1
  139. data/lib/plutonium/ui/layout/header.rb +4 -7
  140. data/lib/plutonium/ui/layout/rodauth_layout.rb +7 -7
  141. data/lib/plutonium/ui/layout/sidebar.rb +1 -1
  142. data/lib/plutonium/ui/nav_grid_menu.rb +7 -6
  143. data/lib/plutonium/ui/nav_user.rb +9 -8
  144. data/lib/plutonium/ui/page/interactive_action.rb +5 -5
  145. data/lib/plutonium/ui/page_header.rb +29 -10
  146. data/lib/plutonium/ui/panel.rb +4 -4
  147. data/lib/plutonium/ui/sidebar_menu.rb +8 -8
  148. data/lib/plutonium/ui/skeleton_table.rb +7 -8
  149. data/lib/plutonium/ui/tab_list.rb +5 -5
  150. data/lib/plutonium/ui/table/base.rb +3 -0
  151. data/lib/plutonium/ui/table/components/attachment.rb +4 -3
  152. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +82 -0
  153. data/lib/plutonium/ui/table/components/pagy_info.rb +2 -2
  154. data/lib/plutonium/ui/table/components/pagy_pagination.rb +13 -8
  155. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +101 -0
  156. data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -2
  157. data/lib/plutonium/ui/table/components/selection_column.rb +100 -0
  158. data/lib/plutonium/ui/table/display_theme.rb +6 -6
  159. data/lib/plutonium/ui/table/resource.rb +93 -52
  160. data/lib/plutonium/ui/table/theme.rb +28 -15
  161. data/lib/plutonium/version.rb +1 -1
  162. data/package.json +2 -2
  163. data/plutonium.gemspec +5 -4
  164. data/src/css/components.css +471 -0
  165. data/src/css/intl_tel_input.css +2 -2
  166. data/src/css/plutonium.css +2 -0
  167. data/src/css/tokens.css +149 -0
  168. data/src/js/controllers/bulk_actions_controller.js +109 -0
  169. data/src/js/controllers/filter_panel_controller.js +35 -0
  170. data/src/js/controllers/register_controllers.js +5 -1
  171. data/src/js/controllers/resource_drop_down_controller.js +25 -1
  172. data/src/js/controllers/slim_select_controller.js +6 -2
  173. data/src/js/turbo/turbo_actions.js +1 -1
  174. metadata +52 -39
  175. data/.claude/skills/definition-query/SKILL.md +0 -334
  176. data/docs/concepts/architecture.md +0 -226
  177. data/docs/concepts/auto-detection.md +0 -254
  178. data/docs/concepts/index.md +0 -61
  179. data/docs/concepts/packages-portals.md +0 -304
  180. data/docs/concepts/resources.md +0 -224
  181. data/docs/cookbook/blog.md +0 -411
  182. data/docs/cookbook/index.md +0 -289
  183. data/docs/cookbook/saas.md +0 -481
  184. data/docs/public/CLAUDE.md +0 -578
  185. data/lib/generators/pu/pkg/portal/templates/app/controllers/resource_controller.rb.tt +0 -5
@@ -1,254 +0,0 @@
1
- # Auto-Detection
2
-
3
- Plutonium automatically detects configuration from your models, reducing boilerplate code. You only write configuration when you need to override the defaults.
4
-
5
- ## How Auto-Detection Works
6
-
7
- When Plutonium renders a resource, it examines:
8
- 1. Database columns (types, constraints)
9
- 2. Model validations
10
- 3. Model associations
11
- 4. Existing configuration
12
-
13
- From this, it determines sensible defaults for forms, tables, and displays.
14
-
15
- ## Field Detection
16
-
17
- ### From Column Types
18
-
19
- | Column Type | Default Input | Default Display |
20
- |-------------|---------------|-----------------|
21
- | `string` | Text input | Text |
22
- | `text` | Textarea | Formatted text |
23
- | `integer` | Number input | Number |
24
- | `float`/`decimal` | Decimal input | Formatted number |
25
- | `boolean` | Checkbox | Yes/No badge |
26
- | `date` | Date picker | Formatted date |
27
- | `datetime` | Datetime picker | Formatted datetime |
28
- | `time` | Time picker | Formatted time |
29
- | `json`/`jsonb` | JSON editor | JSON display |
30
-
31
- ### From Constraints
32
-
33
- ```ruby
34
- # Schema
35
- t.string :email, null: false
36
-
37
- # Auto-detects: required field
38
- field :email # Marked as required in form
39
- ```
40
-
41
- ### From Validations
42
-
43
- ```ruby
44
- # Model
45
- validates :title, presence: true, length: { maximum: 100 }
46
-
47
- # Auto-detects:
48
- # - Required field
49
- # - Max length constraint
50
- ```
51
-
52
- ## Association Detection
53
-
54
- ### belongs_to
55
-
56
- ```ruby
57
- # Model
58
- belongs_to :user
59
-
60
- # Auto-detects:
61
- # - Select input with user options
62
- # - Link to user in display
63
- # - Automatic eager loading in queries
64
- ```
65
-
66
- ### has_many
67
-
68
- ```ruby
69
- # Model
70
- has_many :comments
71
-
72
- # Auto-detects:
73
- # - Association panel on detail page
74
- # - Count display in tables (optional)
75
- # - Nested forms support (if configured)
76
- ```
77
-
78
- ### has_one
79
-
80
- ```ruby
81
- # Model
82
- has_one :profile
83
-
84
- # Auto-detects:
85
- # - Inline display on detail page
86
- # - Nested form support
87
- ```
88
-
89
- ## Validation Detection
90
-
91
- | Validation | Effect |
92
- |------------|--------|
93
- | `presence` | Field marked required |
94
- | `length` | Min/max constraints |
95
- | `numericality` | Number input with constraints |
96
- | `inclusion` | Select input with options |
97
- | `format` | Pattern attribute on input |
98
-
99
- ```ruby
100
- # Model
101
- validates :status, inclusion: { in: %w[draft published archived] }
102
-
103
- # Auto-detects: Select with options
104
- field :status # Renders as select with draft/published/archived options
105
- ```
106
-
107
- ## Overriding Defaults
108
-
109
- Auto-detection provides a starting point. Override when needed:
110
-
111
- ### Field Type Override
112
-
113
- ```ruby
114
- # Auto-detected: text input
115
- # Override: rich text editor
116
- field :body, as: :rich_text
117
- ```
118
-
119
- ### Field Options
120
-
121
- ```ruby
122
- # Auto-detected: select with model options
123
- # Override: custom collection
124
- field :user, collection: -> { User.active.pluck(:name, :id) }
125
- ```
126
-
127
- ### Hiding Fields
128
-
129
- ```ruby
130
- # Exclude from forms
131
- exclude_from_form :created_at, :updated_at
132
-
133
- # Or per-field
134
- field :user, as: :hidden
135
- ```
136
-
137
- ### Required Override
138
-
139
- ```ruby
140
- # Model allows null, but form requires it
141
- field :nickname, required: true
142
- ```
143
-
144
- ## Table Column Detection
145
-
146
- By default, tables show a subset of fields. Plutonium prioritizes:
147
- 1. Non-association fields
148
- 2. Non-text fields (text is too long for tables)
149
- 3. Fields with meaningful data
150
-
151
- ### Override Columns
152
-
153
- ```ruby
154
- # Explicitly set columns
155
- column :title
156
- column :published
157
- column :user
158
- column :created_at
159
- ```
160
-
161
- ## Search Detection
162
-
163
- Plutonium can auto-detect searchable fields:
164
- - String columns are searchable by default
165
- - Text columns can be included
166
-
167
- ### Override Search
168
-
169
- ```ruby
170
- # Custom search implementation
171
- search do |scope, query|
172
- scope.where("title ILIKE ?", "%#{query}%")
173
- end
174
- ```
175
-
176
- ## Filter Detection
177
-
178
- For associations, Plutonium can auto-generate filters:
179
-
180
- ```ruby
181
- # Auto-detected from belongs_to :user
182
- filter :user # Select filter with user options
183
- ```
184
-
185
- ### Override Filters
186
-
187
- ```ruby
188
- filter :status, as: :select, collection: %w[draft published archived]
189
- filter :created_at, as: :date_range
190
- ```
191
-
192
- ## When Auto-Detection Runs
193
-
194
- Auto-detection happens at render time, not definition time. This means:
195
-
196
- 1. Changes to models are reflected immediately
197
- 2. New columns appear in forms automatically
198
- 3. Removed columns disappear from forms
199
-
200
- ## Caching
201
-
202
- In production, auto-detection results are cached for performance. Clear the cache after schema changes:
203
-
204
- ```ruby
205
- Rails.cache.clear
206
- ```
207
-
208
- ## Best Practices
209
-
210
- ### 1. Start with Defaults
211
- Let auto-detection do its job. Only configure what you need to change.
212
-
213
- ### 2. Be Explicit When It Matters
214
- For important forms, explicitly declare fields to prevent surprise changes:
215
-
216
- ```ruby
217
- # Explicit field list
218
- field :title
219
- field :body
220
- field :published
221
-
222
- # vs relying on auto-detection
223
- # (new columns would appear automatically)
224
- ```
225
-
226
- ### 3. Use Models as Documentation
227
- Your model's validations and associations document the expected UI:
228
-
229
- ```ruby
230
- class Post < ResourceRecord
231
- belongs_to :user # Select input
232
- validates :title, presence: true # Required field
233
- validates :status, inclusion: {...} # Select options
234
- end
235
- ```
236
-
237
- ### 4. Test After Schema Changes
238
- After adding/removing columns, verify forms still work correctly.
239
-
240
- ## Debugging Auto-Detection
241
-
242
- To see what Plutonium detected:
243
-
244
- ```ruby
245
- definition = PostDefinition.new
246
- puts definition.detected_fields.inspect
247
- puts definition.detected_columns.inspect
248
- ```
249
-
250
- ## Related Topics
251
-
252
- - [Fields Reference](/reference/definition/fields) - All field options
253
- - [Model Reference](/reference/model/) - Model configuration
254
- - [Resources](./resources) - Understanding resources
@@ -1,61 +0,0 @@
1
- # Core Concepts
2
-
3
- This section explains the fundamental concepts behind Plutonium's architecture.
4
-
5
- ## Overview
6
-
7
- Plutonium is built around a few key principles:
8
-
9
- 1. **Separation of Concerns** - Each layer has a single responsibility
10
- 2. **Convention over Configuration** - Smart defaults, override when needed
11
- 3. **Modularity** - Features organized into independent packages
12
- 4. **Full Customization** - Every layer can be overridden
13
-
14
- ## Key Concepts
15
-
16
- ### [Architecture](./architecture)
17
- How the Model → Definition → Policy → Controller layers work together to create a resource.
18
-
19
- ### [Resources](./resources)
20
- What resources are and how they differ from plain Rails models.
21
-
22
- ### [Packages and Portals](./packages-portals)
23
- How to organize your application using Feature Packages and Portal Packages.
24
-
25
- ### [Auto-Detection](./auto-detection)
26
- How Plutonium automatically discovers fields, associations, and validations.
27
-
28
- ## The Big Picture
29
-
30
- ```
31
- ┌─────────────────────────────────────────────────────────────┐
32
- │ PORTAL │
33
- │ (Web Interface - routes, authentication, UI customization) │
34
- └─────────────────────────────────────────────────────────────┘
35
-
36
-
37
- ┌─────────────────────────────────────────────────────────────┐
38
- │ CONTROLLER │
39
- │ (HTTP handling - request/response, rendering) │
40
- └─────────────────────────────────────────────────────────────┘
41
-
42
-
43
- ┌─────────────────────────────────────────────────────────────┐
44
- │ POLICY │
45
- │ (Authorization - who can do what) │
46
- └─────────────────────────────────────────────────────────────┘
47
-
48
-
49
- ┌─────────────────────────────────────────────────────────────┐
50
- │ DEFINITION │
51
- │ (Presentation - how resources render) │
52
- └─────────────────────────────────────────────────────────────┘
53
-
54
-
55
- ┌─────────────────────────────────────────────────────────────┐
56
- │ MODEL │
57
- │ (Data - structure, validations, business rules) │
58
- └─────────────────────────────────────────────────────────────┘
59
- ```
60
-
61
- Each layer builds on the one below it, and each can be customized independently.
@@ -1,304 +0,0 @@
1
- # Packages and Portals
2
-
3
- Plutonium organizes applications using two types of packages: **Feature Packages** for business logic and **Portal Packages** for web interfaces.
4
-
5
- ## Why Packages?
6
-
7
- Packages provide:
8
- - **Modularity** - Features are isolated and self-contained
9
- - **Reusability** - Share features across multiple interfaces
10
- - **Scalability** - Large apps stay organized
11
- - **Team collaboration** - Teams can own specific packages
12
-
13
- ## Feature Packages
14
-
15
- Feature packages contain your business logic: models, definitions, policies, interactions, and controllers.
16
-
17
- ### Creating a Feature Package
18
-
19
- ```bash
20
- rails generate pu:pkg:package blogging
21
- ```
22
-
23
- This creates:
24
-
25
- ```
26
- packages/blogging/
27
- ├── app/
28
- │ ├── controllers/blogging/
29
- │ ├── definitions/blogging/
30
- │ ├── interactions/blogging/
31
- │ ├── models/blogging/
32
- │ ├── policies/blogging/
33
- │ └── views/blogging/
34
- └── lib/
35
- └── engine.rb
36
- ```
37
-
38
- ### Feature Package Structure
39
-
40
- ```ruby
41
- # packages/blogging/lib/engine.rb
42
- module Blogging
43
- class Engine < Rails::Engine
44
- include Plutonium::Package::Engine
45
-
46
- # Package configuration here
47
- end
48
- end
49
- ```
50
-
51
- ### Adding Resources to a Feature Package
52
-
53
- ```bash
54
- rails generate pu:res:scaffold Post title:string body:text --dest=blogging
55
- ```
56
-
57
- Resources are namespaced under the package:
58
-
59
- ```ruby
60
- # packages/blogging/app/models/blogging/post.rb
61
- module Blogging
62
- class Post < Blogging::ResourceRecord
63
- end
64
- end
65
- ```
66
-
67
- ## Portal Packages
68
-
69
- Portal packages are web interfaces that expose resources to users. Each portal can have its own authentication, authorization, and UI customizations.
70
-
71
- ### Creating a Portal Package
72
-
73
- ```bash
74
- rails generate pu:pkg:portal admin
75
- ```
76
-
77
- This creates:
78
-
79
- ```
80
- packages/admin_portal/
81
- ├── app/
82
- │ ├── controllers/admin_portal/
83
- │ └── views/admin_portal/
84
- ├── config/
85
- │ └── routes.rb
86
- ├── lib/
87
- │ └── admin_portal/
88
- │ └── engine.rb
89
- ├── admin_portal.gemspec
90
- └── Gemfile
91
- ```
92
-
93
- ### Portal Engine
94
-
95
- ```ruby
96
- # packages/admin_portal/lib/admin_portal/engine.rb
97
- module AdminPortal
98
- class Engine < Rails::Engine
99
- include Plutonium::Portal::Engine
100
-
101
- config.after_initialize do
102
- # Optional: Scope to an entity (multi-tenancy)
103
- scope_to_entity Organization
104
- end
105
- end
106
- end
107
- ```
108
-
109
- ### Portal Authentication
110
-
111
- Authentication is configured in the controller concern:
112
-
113
- ```ruby
114
- # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
115
- module AdminPortal
116
- module Concerns
117
- module Controller
118
- extend ActiveSupport::Concern
119
- include Plutonium::Portal::Controller
120
- include Plutonium::Auth::Rodauth(:admin)
121
- end
122
- end
123
- end
124
- ```
125
-
126
- ### Connecting Resources to Portals
127
-
128
- ```bash
129
- rails generate pu:res:conn Blogging::Post --dest=admin_portal
130
- ```
131
-
132
- This:
133
- 1. Registers the resource in portal routes
134
- 2. Creates portal-specific controller
135
- 3. Creates portal-specific policy with attribute permissions
136
-
137
- ## Multiple Portals
138
-
139
- A common pattern is having different portals for different user types:
140
-
141
- ```
142
- packages/
143
- ├── admin_portal/ # Full access for administrators
144
- ├── author_portal/ # Content management for authors
145
- └── customer_portal/ # Public-facing interface
146
- ```
147
-
148
- Each portal can:
149
- - Use different authentication
150
- - Show different fields
151
- - Apply different policies
152
- - Have unique UI customization
153
-
154
- ### Example: Same Resource, Different Portals
155
-
156
- ```ruby
157
- # Admin sees everything
158
- # packages/admin_portal/app/policies/admin_portal/blogging/post_policy.rb
159
- module AdminPortal
160
- module Blogging
161
- class PostPolicy < ::Blogging::PostPolicy
162
- def read?
163
- true # Admins see all posts
164
- end
165
- end
166
- end
167
- end
168
-
169
- # Authors see only their posts
170
- # packages/author_portal/app/policies/author_portal/blogging/post_policy.rb
171
- module AuthorPortal
172
- module Blogging
173
- class PostPolicy < ::Blogging::PostPolicy
174
- def read?
175
- record.user_id == user.id
176
- end
177
- end
178
- end
179
- end
180
- ```
181
-
182
- ## Package Dependencies
183
-
184
- Feature packages can depend on each other:
185
-
186
- ```ruby
187
- # packages/blogging/blogging.gemspec
188
- Gem::Specification.new do |spec|
189
- spec.add_dependency "users" # Depends on users package
190
- end
191
- ```
192
-
193
- ## Mounting Packages
194
-
195
- Packages are mounted in the main application routes:
196
-
197
- ```ruby
198
- # config/routes.rb
199
- Rails.application.routes.draw do
200
- mount AdminPortal::Engine, at: "/admin"
201
- mount AuthorPortal::Engine, at: "/author"
202
- mount CustomerPortal::Engine, at: "/"
203
- end
204
- ```
205
-
206
- ## Authentication per Portal
207
-
208
- Each portal can use different authentication via its controller concern:
209
-
210
- ```ruby
211
- # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
212
- module AdminPortal
213
- module Concerns
214
- module Controller
215
- extend ActiveSupport::Concern
216
- include Plutonium::Portal::Controller
217
- include Plutonium::Auth::Rodauth(:admin)
218
- end
219
- end
220
- end
221
-
222
- # packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
223
- module CustomerPortal
224
- module Concerns
225
- module Controller
226
- extend ActiveSupport::Concern
227
- include Plutonium::Portal::Controller
228
- include Plutonium::Auth::Rodauth(:customer)
229
- end
230
- end
231
- end
232
- ```
233
-
234
- ## Entity Scoping (Multi-tenancy)
235
-
236
- Portals can be scoped to an entity:
237
-
238
- ```ruby
239
- module CustomerPortal
240
- class Engine < Rails::Engine
241
- include Plutonium::Portal::Engine
242
-
243
- config.after_initialize do
244
- # All resources scoped to current organization
245
- scope_to_entity Organization
246
- end
247
- end
248
- end
249
- ```
250
-
251
- With entity scoping:
252
- - All queries automatically filter by entity
253
- - New records automatically belong to entity
254
- - Users can only access their entity's data
255
-
256
- ## Portal Customization
257
-
258
- ### Custom Layouts
259
-
260
- ```ruby
261
- # packages/admin_portal/app/views/layouts/admin_portal/application.rb
262
- module AdminPortal
263
- class ApplicationLayout < Plutonium::UI::Layout::Application
264
- def render_logo
265
- img(src: asset_path("admin-logo.svg"))
266
- end
267
- end
268
- end
269
- ```
270
-
271
- ### Portal-Specific Definitions
272
-
273
- ```ruby
274
- # packages/admin_portal/app/definitions/admin_portal/blogging/post_definition.rb
275
- module AdminPortal
276
- module Blogging
277
- class PostDefinition < ::Blogging::PostDefinition
278
- # Add admin-specific fields
279
- field :internal_notes, as: :text
280
- end
281
- end
282
- end
283
- ```
284
-
285
- ## Best Practices
286
-
287
- ### 1. One Feature, One Package
288
- Keep packages focused. A "blogging" package shouldn't handle user management.
289
-
290
- ### 2. Portal-Specific Overrides
291
- Put customizations in the portal package, not the feature package.
292
-
293
- ### 3. Shared Logic in Features
294
- Business logic goes in feature packages, UI customization in portals.
295
-
296
- ### 4. Clear Naming
297
- - Feature packages: noun (blogging, inventory, billing)
298
- - Portal packages: role + portal (admin_portal, customer_portal)
299
-
300
- ## Related Topics
301
-
302
- - [Architecture](./architecture) - How layers work together
303
- - [Resources](./resources) - Understanding resources
304
- - [Portal Reference](/reference/portal/) - Portal configuration