plutonium 0.37.0 โ 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium-controller/SKILL.md +25 -2
- data/.claude/skills/plutonium-definition-fields/SKILL.md +33 -0
- data/.claude/skills/plutonium-nested-resources/SKILL.md +79 -19
- data/.claude/skills/plutonium-policy/SKILL.md +93 -6
- data/CHANGELOG.md +36 -0
- data/CLAUDE.md +8 -10
- data/CONTRIBUTING.md +6 -8
- data/Rakefile +16 -1
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +9371 -11492
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +55 -55
- data/app/assets/plutonium.min.js.map +4 -4
- data/docs/guides/index.md +5 -0
- data/docs/guides/nested-resources.md +132 -29
- data/docs/guides/troubleshooting.md +82 -0
- data/docs/reference/controller/index.md +1 -1
- data/docs/reference/definition/fields.md +33 -0
- data/docs/reference/model/index.md +1 -1
- data/docs/reference/policy/index.md +77 -6
- data/gemfiles/rails_7.gemfile.lock +3 -3
- data/gemfiles/rails_8.0.gemfile.lock +3 -3
- data/gemfiles/rails_8.1.gemfile.lock +3 -3
- data/lib/plutonium/core/controller.rb +144 -19
- data/lib/plutonium/core/controllers/association_resolver.rb +86 -0
- data/lib/plutonium/helpers/display_helper.rb +12 -0
- data/lib/plutonium/query/filters/association.rb +25 -3
- data/lib/plutonium/resource/controller.rb +90 -9
- data/lib/plutonium/resource/controllers/authorizable.rb +17 -4
- data/lib/plutonium/resource/controllers/crud_actions.rb +7 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +9 -0
- data/lib/plutonium/resource/controllers/presentable.rb +13 -11
- data/lib/plutonium/resource/policy.rb +85 -2
- data/lib/plutonium/resource/record/routes.rb +31 -1
- data/lib/plutonium/routing/mapper_extensions.rb +40 -4
- data/lib/plutonium/routing/route_set_extensions.rb +3 -0
- data/lib/plutonium/ui/breadcrumbs.rb +1 -1
- data/lib/plutonium/ui/display/resource.rb +5 -2
- data/lib/plutonium/ui/form/components/key_value_store.rb +17 -5
- data/lib/plutonium/ui/page/index.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +1 -1
- data/package.json +6 -5
- data/plutonium.gemspec +1 -1
- data/src/js/controllers/key_value_store_controller.js +6 -0
- data/src/js/controllers/resource_drop_down_controller.js +3 -3
- data/yarn.lock +1465 -693
- metadata +6 -5
- data/app/javascript/controllers/key_value_store_controller.js +0 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a3ade68b80487615f8ad46432edb306cde755d712e7222b11705427f9a283ca
|
|
4
|
+
data.tar.gz: 10e95e8c085c8b7229929f50517fce9d964a1ff2ca31b0ee20662ca863c88f47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4a5ac3024c168686c6c315d2d7fa280c708a7e807580a422cb20223b6ccf2f95e96876e6a56d9ca88ee8fcbb63cc906dba33ad02af73a62f167bd91431a1a08
|
|
7
|
+
data.tar.gz: a0644bf92081d308e0ef306d38168d63138864c54534fea161c20b9f2d79e8556914239dd3122854686e0fee140758f9aa49b2b9c72248a48868045405fffbd5
|
|
@@ -194,22 +194,45 @@ build_collection # Build table component
|
|
|
194
194
|
resource_url_for(@post) # URL for record
|
|
195
195
|
resource_url_for(@post, action: :edit) # Edit URL
|
|
196
196
|
resource_url_for(Post) # Index URL
|
|
197
|
+
|
|
198
|
+
# With parent (nested resources)
|
|
199
|
+
resource_url_for(@comment, parent: @post) # Nested URL
|
|
200
|
+
resource_url_for(Comment, action: :new, parent: @post)
|
|
201
|
+
|
|
202
|
+
# Cross-package URLs
|
|
203
|
+
resource_url_for(@post, package: AdminPortal)
|
|
197
204
|
```
|
|
198
205
|
|
|
199
206
|
## Nested Resources
|
|
200
207
|
|
|
201
|
-
Parent records are automatically resolved:
|
|
208
|
+
Parent records are automatically resolved from routes with the `nested_` prefix:
|
|
202
209
|
|
|
203
210
|
```ruby
|
|
204
|
-
# Route: /users/:user_id/
|
|
211
|
+
# Route: /users/:user_id/nested_posts/:id
|
|
205
212
|
class PostsController < ::ResourceController
|
|
206
213
|
# current_parent returns the User
|
|
214
|
+
# current_nested_association returns :posts
|
|
207
215
|
# resource_record! returns the Post scoped to that User
|
|
208
216
|
end
|
|
209
217
|
```
|
|
210
218
|
|
|
219
|
+
### Key Methods for Nested Resources
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
current_parent # Parent record (e.g., User instance)
|
|
223
|
+
current_nested_association # Association name (e.g., :posts)
|
|
224
|
+
parent_route_param # URL param (e.g., :user_id)
|
|
225
|
+
parent_input_param # Form param (e.g., :user)
|
|
226
|
+
```
|
|
227
|
+
|
|
211
228
|
Parent fields are automatically excluded from forms/displays. Override with presentation hooks (see above).
|
|
212
229
|
|
|
230
|
+
### has_one Support
|
|
231
|
+
|
|
232
|
+
For `has_one` associations, routes are singular:
|
|
233
|
+
- `/users/:user_id/nested_profile` (no `:id` param)
|
|
234
|
+
- Index redirects to show (or new if no record exists)
|
|
235
|
+
|
|
213
236
|
## Entity Scoping (Multi-tenancy)
|
|
214
237
|
|
|
215
238
|
When a portal is scoped to an entity:
|
|
@@ -321,6 +321,39 @@ column :status, align: :center # Center
|
|
|
321
321
|
column :amount, align: :end # Right
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
+
### Value Formatting
|
|
325
|
+
|
|
326
|
+
Use `formatter` for simple value transformations without a full block:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
# Truncate long text
|
|
330
|
+
column :description, formatter: ->(value) { value&.truncate(30) }
|
|
331
|
+
|
|
332
|
+
# Format numbers
|
|
333
|
+
column :price, formatter: ->(value) { "$%.2f" % value if value }
|
|
334
|
+
|
|
335
|
+
# Transform values
|
|
336
|
+
column :status, formatter: ->(value) { value&.humanize&.upcase }
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The `formatter` option:
|
|
340
|
+
- Receives the field value as its argument
|
|
341
|
+
- Returns the transformed value for display
|
|
342
|
+
- Works with `column` and `display` declarations
|
|
343
|
+
- Is simpler than block syntax when you only need to transform the value
|
|
344
|
+
|
|
345
|
+
**formatter vs block:** Use `formatter` when you only need the value. Use a block when you need access to the full record:
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
# formatter - receives just the value
|
|
349
|
+
column :name, formatter: ->(value) { value&.titleize }
|
|
350
|
+
|
|
351
|
+
# block - receives the full record
|
|
352
|
+
column :full_name do |record|
|
|
353
|
+
"#{record.first_name} #{record.last_name}"
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
324
357
|
### Custom Column Rendering
|
|
325
358
|
|
|
326
359
|
Use a block to customize how a column value is displayed. The block receives the raw record:
|
|
@@ -5,7 +5,7 @@ description: Plutonium nested resources - parent/child routes, scoping, and URL
|
|
|
5
5
|
|
|
6
6
|
# Nested Resources
|
|
7
7
|
|
|
8
|
-
Plutonium automatically creates nested routes for `has_many` associations, scopes queries to the parent, and handles URL generation.
|
|
8
|
+
Plutonium automatically creates nested routes for `has_many` and `has_one` associations, scopes queries to the parent, and handles URL generation.
|
|
9
9
|
|
|
10
10
|
## How It Works
|
|
11
11
|
|
|
@@ -15,12 +15,17 @@ When you register resources with parent-child relationships:
|
|
|
15
15
|
# In portal routes
|
|
16
16
|
register_resource ::Company
|
|
17
17
|
register_resource ::Property # has belongs_to :company
|
|
18
|
+
register_resource ::CompanyProfile # has belongs_to :company (has_one on Company)
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
Plutonium automatically creates nested routes:
|
|
21
|
-
- `/companies/:company_id/
|
|
22
|
-
- `/companies/:company_id/
|
|
23
|
-
- `/companies/:company_id/
|
|
21
|
+
Plutonium automatically creates nested routes with a `nested_` prefix:
|
|
22
|
+
- `/companies/:company_id/nested_properties` - Properties scoped to company (has_many)
|
|
23
|
+
- `/companies/:company_id/nested_properties/new` - New property for company
|
|
24
|
+
- `/companies/:company_id/nested_properties/:id` - Property in company context
|
|
25
|
+
- `/companies/:company_id/nested_company_profile` - Singular profile (has_one)
|
|
26
|
+
- `/companies/:company_id/nested_company_profile/new` - New profile for company
|
|
27
|
+
|
|
28
|
+
The `nested_` prefix prevents route conflicts when the same resource is registered both as a top-level and nested resource.
|
|
24
29
|
|
|
25
30
|
## Automatic Behavior
|
|
26
31
|
|
|
@@ -81,26 +86,45 @@ end
|
|
|
81
86
|
|
|
82
87
|
## Query Scoping
|
|
83
88
|
|
|
84
|
-
Collections are automatically scoped to the parent via policies:
|
|
89
|
+
Collections are automatically scoped to the parent via policies. The policy receives `parent` and `parent_association` context:
|
|
85
90
|
|
|
86
91
|
```ruby
|
|
87
92
|
class PropertyPolicy < ResourcePolicy
|
|
93
|
+
# parent: the parent record (e.g., Company instance)
|
|
94
|
+
# parent_association: the association name (e.g., :properties)
|
|
95
|
+
|
|
88
96
|
relation_scope do |relation|
|
|
89
|
-
relation = super(relation) # Applies
|
|
90
|
-
# entity_scope is the current_parent
|
|
97
|
+
relation = super(relation) # Applies parent scoping automatically
|
|
91
98
|
relation
|
|
92
99
|
end
|
|
93
100
|
end
|
|
94
101
|
```
|
|
95
102
|
|
|
96
|
-
|
|
103
|
+
### How Parent Scoping Works
|
|
97
104
|
|
|
105
|
+
For **has_many** associations, scoping uses the association directly:
|
|
98
106
|
```ruby
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
# => Property.where(company: company)
|
|
107
|
+
# parent.properties => Company#properties
|
|
108
|
+
parent.send(parent_association)
|
|
102
109
|
```
|
|
103
110
|
|
|
111
|
+
For **has_one** associations, scoping uses a where clause:
|
|
112
|
+
```ruby
|
|
113
|
+
# Property.where(company_id: company.id) with limit
|
|
114
|
+
relation.where(foreign_key => parent.id)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Parent vs Entity Scope
|
|
118
|
+
|
|
119
|
+
When a parent is present, parent scoping takes precedence over entity scoping:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# With parent: scopes via parent association
|
|
123
|
+
# Without parent: falls back to entity_scope (multi-tenancy)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This prevents double-scoping - the parent was already authorized and entity-scoped during its own authorization.
|
|
127
|
+
|
|
104
128
|
### Custom Association Scope
|
|
105
129
|
|
|
106
130
|
For complex relationships, define a custom scope:
|
|
@@ -118,21 +142,37 @@ end
|
|
|
118
142
|
Use `resource_url_for` with the `parent:` option:
|
|
119
143
|
|
|
120
144
|
```ruby
|
|
121
|
-
# Child collection
|
|
145
|
+
# Child collection (has_many)
|
|
122
146
|
resource_url_for(Property, parent: company)
|
|
123
|
-
# => /companies/123/
|
|
147
|
+
# => /companies/123/nested_properties
|
|
124
148
|
|
|
125
149
|
# Child record
|
|
126
150
|
resource_url_for(property, parent: company)
|
|
127
|
-
# => /companies/123/
|
|
151
|
+
# => /companies/123/nested_properties/456
|
|
128
152
|
|
|
129
153
|
# New child form
|
|
130
154
|
resource_url_for(Property, action: :new, parent: company)
|
|
131
|
-
# => /companies/123/
|
|
155
|
+
# => /companies/123/nested_properties/new
|
|
132
156
|
|
|
133
157
|
# Edit child
|
|
134
158
|
resource_url_for(property, action: :edit, parent: company)
|
|
135
|
-
# => /companies/123/
|
|
159
|
+
# => /companies/123/nested_properties/456/edit
|
|
160
|
+
|
|
161
|
+
# Singular resource (has_one)
|
|
162
|
+
resource_url_for(company_profile, parent: company)
|
|
163
|
+
# => /companies/123/nested_company_profile
|
|
164
|
+
|
|
165
|
+
resource_url_for(CompanyProfile, action: :new, parent: company)
|
|
166
|
+
# => /companies/123/nested_company_profile/new
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Cross-Package URL Generation
|
|
170
|
+
|
|
171
|
+
Generate URLs for resources in a different package:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
# From AdminPortal, generate URL to CustomerPortal resource
|
|
175
|
+
resource_url_for(property, parent: company, package: CustomerPortal)
|
|
136
176
|
```
|
|
137
177
|
|
|
138
178
|
## Association Panels
|
|
@@ -192,12 +232,32 @@ resource_params
|
|
|
192
232
|
|
|
193
233
|
You don't need to include hidden fields for the parent in forms.
|
|
194
234
|
|
|
235
|
+
## has_one Associations
|
|
236
|
+
|
|
237
|
+
Plutonium supports both `has_many` and `has_one` associations:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
class Company < ResourceRecord
|
|
241
|
+
has_many :properties # Plural routes
|
|
242
|
+
has_one :company_profile # Singular routes
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Routes generated:
|
|
247
|
+
- `has_many`: `/companies/:id/nested_properties` (plural, with `:id` param)
|
|
248
|
+
- `has_one`: `/companies/:id/nested_company_profile` (singular, no `:id` param)
|
|
249
|
+
|
|
250
|
+
For has_one associations:
|
|
251
|
+
- Index redirects to show (or new if no record exists)
|
|
252
|
+
- Only one record can exist per parent
|
|
253
|
+
- Forms don't show parent field (determined by URL)
|
|
254
|
+
|
|
195
255
|
## Nesting Limitations
|
|
196
256
|
|
|
197
257
|
Plutonium supports **one level of nesting**:
|
|
198
258
|
|
|
199
|
-
- โ
`/companies/:company_id/
|
|
200
|
-
- โ `/companies/:company_id/
|
|
259
|
+
- โ
`/companies/:company_id/nested_properties` (parent โ child)
|
|
260
|
+
- โ `/companies/:company_id/nested_properties/:property_id/nested_units` (grandparent โ parent โ child)
|
|
201
261
|
|
|
202
262
|
## Common Patterns
|
|
203
263
|
|
|
@@ -182,13 +182,13 @@ relation_scope do |relation|
|
|
|
182
182
|
end
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
### With
|
|
185
|
+
### With Parent Scoping (Nested Resources)
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
For nested resources, call `super` to apply automatic parent scoping:
|
|
188
188
|
|
|
189
189
|
```ruby
|
|
190
190
|
relation_scope do |relation|
|
|
191
|
-
relation = super(relation) #
|
|
191
|
+
relation = super(relation) # Applies parent scoping automatically
|
|
192
192
|
|
|
193
193
|
if user.admin?
|
|
194
194
|
relation
|
|
@@ -198,6 +198,71 @@ relation_scope do |relation|
|
|
|
198
198
|
end
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
**Parent scoping takes precedence over entity scoping.** When a parent is present:
|
|
202
|
+
- For `has_many`: scopes via `parent.association_name`
|
|
203
|
+
- For `has_one`: scopes via `where(foreign_key: parent.id)`
|
|
204
|
+
|
|
205
|
+
### With Entity Scoping (Multi-tenancy)
|
|
206
|
+
|
|
207
|
+
When no parent is present, `super` applies entity scoping:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
relation_scope do |relation|
|
|
211
|
+
relation = super(relation) # Apply entity scope if no parent
|
|
212
|
+
|
|
213
|
+
if user.admin?
|
|
214
|
+
relation
|
|
215
|
+
else
|
|
216
|
+
relation.where(published: true)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### default_relation_scope is Required
|
|
222
|
+
|
|
223
|
+
Plutonium verifies that `default_relation_scope` is called in every `relation_scope`. This prevents accidental multi-tenancy leaks when overriding scopes.
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
# โ This will raise an error
|
|
227
|
+
relation_scope do |relation|
|
|
228
|
+
relation.where(published: true) # Missing default_relation_scope!
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# โ
Correct - call default_relation_scope
|
|
232
|
+
relation_scope do |relation|
|
|
233
|
+
default_relation_scope(relation).where(published: true)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# โ
Also correct - super calls default_relation_scope
|
|
237
|
+
relation_scope do |relation|
|
|
238
|
+
super(relation).where(published: true)
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
When overriding an inherited scope:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
class AdminPostPolicy < PostPolicy
|
|
246
|
+
relation_scope do |relation|
|
|
247
|
+
# Replace inherited scope but keep Plutonium's parent/entity scoping
|
|
248
|
+
default_relation_scope(relation)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Skipping Default Scoping (Rare)
|
|
254
|
+
|
|
255
|
+
If you intentionally need to skip scoping, call `skip_default_relation_scope!`:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
relation_scope do |relation|
|
|
259
|
+
skip_default_relation_scope!
|
|
260
|
+
relation # No parent/entity scoping applied
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Consider using a separate portal instead of skipping scoping.
|
|
265
|
+
|
|
201
266
|
## Portal-Specific Policies
|
|
202
267
|
|
|
203
268
|
Override policies per portal:
|
|
@@ -306,9 +371,31 @@ end
|
|
|
306
371
|
Policies have access to:
|
|
307
372
|
|
|
308
373
|
```ruby
|
|
309
|
-
user
|
|
310
|
-
record
|
|
311
|
-
entity_scope
|
|
374
|
+
user # Current user (required)
|
|
375
|
+
record # The resource being authorized
|
|
376
|
+
entity_scope # Current scoped entity (for multi-tenancy)
|
|
377
|
+
parent # Parent record for nested resources (nil if not nested)
|
|
378
|
+
parent_association # Association name on parent (e.g., :comments)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Nested Resource Context
|
|
382
|
+
|
|
383
|
+
For nested resources (e.g., `/posts/123/nested_comments`), the policy receives:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
class CommentPolicy < ResourcePolicy
|
|
387
|
+
def create?
|
|
388
|
+
# parent is the Post instance
|
|
389
|
+
# parent_association is :comments
|
|
390
|
+
parent.present? && user.can_comment_on?(parent)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
relation_scope do |relation|
|
|
394
|
+
# super() uses parent and parent_association for scoping
|
|
395
|
+
relation = super(relation)
|
|
396
|
+
relation
|
|
397
|
+
end
|
|
398
|
+
end
|
|
312
399
|
```
|
|
313
400
|
|
|
314
401
|
### Custom Context
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## [0.38.0] - 2026-01-25
|
|
2
|
+
|
|
3
|
+
### ๐ Features
|
|
4
|
+
|
|
5
|
+
- *(core)* Handle ActionPolicy::Unauthorized for non-HTML formats
|
|
6
|
+
- *(interactive_actions)* Add non-HTML response handlers for successful actions
|
|
7
|
+
- *(routing)* Add has_one nested resource support
|
|
8
|
+
- *(routing)* [**breaking**] Refactor nested resource URL generation with named route helpers
|
|
9
|
+
- *(policy)* Add default_relation_scope method with verification
|
|
10
|
+
- *(nested)* Use association names for nested resource titles and breadcrumbs
|
|
11
|
+
|
|
12
|
+
### ๐ Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Remove duplicate kv store controller and move improvements into original
|
|
15
|
+
- *(filters)* Improve association filter class resolution
|
|
16
|
+
- *(ui)* Improve dropdown positioning with viewport boundary
|
|
17
|
+
- *(crud)* Use correct action attributes for form re-rendering on errors
|
|
18
|
+
- *(form)* Distinguish empty vs not-submitted key-value store fields
|
|
19
|
+
- *(controller)* Use existing record context for form param extraction
|
|
20
|
+
|
|
21
|
+
### ๐ Documentation
|
|
22
|
+
|
|
23
|
+
- *(definition)* Document formatter option for columns and displays
|
|
24
|
+
- Cleanup review definition
|
|
25
|
+
- Add troubleshooting guide for inflection issue
|
|
26
|
+
- Update nested resource routes to use nested_ prefix
|
|
27
|
+
|
|
28
|
+
### ๐งช Testing
|
|
29
|
+
|
|
30
|
+
- Refactor tests to use real module implementations instead of mocks
|
|
31
|
+
|
|
32
|
+
### โ๏ธ Miscellaneous Tasks
|
|
33
|
+
|
|
34
|
+
- Use chokidar to fix dev build cyclic dependency issues
|
|
35
|
+
- Warn when running tests without Appraisal
|
|
36
|
+
- Switch to yarn
|
|
1
37
|
## [0.37.0] - 2026-01-21
|
|
2
38
|
|
|
3
39
|
### ๐ Features
|
data/CLAUDE.md
CHANGED
|
@@ -77,16 +77,16 @@ When working on JavaScript or CSS in `src/`:
|
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
# Watch mode (rebuilds on changes to src/build/)
|
|
80
|
-
|
|
80
|
+
yarn dev
|
|
81
81
|
|
|
82
82
|
# Production build (to app/assets/)
|
|
83
|
-
|
|
83
|
+
yarn build
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
86
|
+
- `yarn dev` - watches and rebuilds to `src/build/` for development
|
|
87
|
+
- `yarn build` - compiles to `app/assets/` for release
|
|
88
88
|
|
|
89
|
-
**Always run `
|
|
89
|
+
**Always run `yarn dev`** in a terminal when working on frontend code.
|
|
90
90
|
|
|
91
91
|
### Running Tests
|
|
92
92
|
|
|
@@ -114,10 +114,8 @@ Generators are in `lib/generators/pu/`. Test by:
|
|
|
114
114
|
### Documentation
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
pnpm dev # Local preview at localhost:5173
|
|
120
|
-
pnpm build # Build for production
|
|
117
|
+
yarn docs:dev # Local preview at localhost:5173
|
|
118
|
+
yarn docs:build # Build for production
|
|
121
119
|
```
|
|
122
120
|
|
|
123
121
|
## Code Conventions
|
|
@@ -212,4 +210,4 @@ This:
|
|
|
212
210
|
### Update documentation
|
|
213
211
|
1. Edit files in `docs/`
|
|
214
212
|
2. If user-facing behavior, update relevant skill in `.claude/skills/`
|
|
215
|
-
3. Run `
|
|
213
|
+
3. Run `yarn docs:build` to verify no broken links
|
data/CONTRIBUTING.md
CHANGED
|
@@ -104,7 +104,7 @@ git push origin main --tags
|
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
bundle install
|
|
107
|
-
|
|
107
|
+
yarn install
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
### Environment
|
|
@@ -123,10 +123,10 @@ Frontend source is in `src/`. When making JS or CSS changes:
|
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
# Watch mode - keeps rebuilding as you edit
|
|
126
|
-
|
|
126
|
+
yarn dev
|
|
127
127
|
|
|
128
128
|
# Production build - run before committing
|
|
129
|
-
|
|
129
|
+
yarn build
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
### Running Tests
|
|
@@ -160,10 +160,8 @@ bin/dev
|
|
|
160
160
|
### Documentation
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
pnpm dev # Preview at localhost:5173
|
|
166
|
-
pnpm build # Check for errors
|
|
163
|
+
yarn docs:dev # Preview at localhost:5173
|
|
164
|
+
yarn docs:build # Check for errors
|
|
167
165
|
```
|
|
168
166
|
|
|
169
167
|
### Changelog Generation (optional)
|
|
@@ -180,7 +178,7 @@ cargo install git-cliff # via Rust
|
|
|
180
178
|
2. Create a feature branch: `git checkout -b feat/my-feature`
|
|
181
179
|
3. Make your changes with conventional commits
|
|
182
180
|
4. Run tests: `bundle exec appraisal rake test`
|
|
183
|
-
5. Build assets: `
|
|
181
|
+
5. Build assets: `yarn build`
|
|
184
182
|
6. Push and create a pull request
|
|
185
183
|
|
|
186
184
|
## Questions?
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
|
|
|
8
8
|
task default: %i[test standard]
|
|
9
9
|
|
|
10
10
|
task :assets do
|
|
11
|
-
`
|
|
11
|
+
`yarn build`
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# https://stackoverflow.com/questions/15707940/rake-before-task-hook
|
|
@@ -20,3 +20,18 @@ Rake::TestTask.new do |t|
|
|
|
20
20
|
t.test_files = FileList["test/**/*_test.rb"]
|
|
21
21
|
t.verbose = true
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# Warn users to run tests through Appraisal
|
|
25
|
+
Rake::Task["test"].enhance do
|
|
26
|
+
# This runs after test completes successfully - no action needed
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task :check_appraisal do
|
|
30
|
+
unless ENV["BUNDLE_GEMFILE"]&.include?("gemfiles/")
|
|
31
|
+
warn "\nโ ๏ธ Tests should be run through Appraisal for the correct gem environment:"
|
|
32
|
+
warn " bundle exec appraisal rails-8.1 rake test"
|
|
33
|
+
warn " bundle exec appraisal rake test # runs all Rails versions\n\n"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Rake::Task["test"].enhance [:check_appraisal]
|