plutonium 0.61.0 → 0.62.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-kanban/SKILL.md +89 -24
- data/CHANGELOG.md +27 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +315 -38
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +31 -31
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/resource/_kanban_move_action_form.html.erb +1 -0
- data/app/views/resource/kanban_move_form.html.erb +1 -0
- data/config/brakeman.ignore +2 -2
- data/docs/.vitepress/config.ts +21 -1
- data/docs/.vitepress/sync-skills.mjs +45 -0
- data/docs/ai.md +99 -0
- data/docs/guides/kanban.md +128 -18
- data/docs/reference/kanban/authorization.md +25 -5
- data/docs/reference/kanban/dsl.md +49 -8
- data/docs/reference/kanban/index.md +3 -3
- data/docs/reference/kanban/positioning.md +1 -1
- data/docs/reference/resource/definition.md +10 -1
- data/docs/reference/resource/model.md +26 -0
- data/docs/reference/ui/forms.md +41 -0
- data/docs/reference/wizard/dsl.md +5 -0
- data/docs/superpowers/plans/2026-07-02-kanban-drop-interactions.md +714 -0
- data/docs/superpowers/plans/2026-07-02-kanban-drop-interactions.md.tasks.json +68 -0
- data/docs/superpowers/specs/2026-07-03-kanban-auth-simplification.md +159 -0
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +5 -0
- data/lib/plutonium/action/base.rb +8 -0
- data/lib/plutonium/configuration.rb +12 -0
- data/lib/plutonium/definition/index_views.rb +16 -0
- data/lib/plutonium/kanban/column.rb +80 -27
- data/lib/plutonium/models/has_cents.rb +30 -2
- data/lib/plutonium/resource/controller.rb +22 -1
- data/lib/plutonium/resource/controllers/crud_actions.rb +8 -0
- data/lib/plutonium/resource/controllers/kanban_actions.rb +489 -93
- data/lib/plutonium/resource/policy.rb +6 -0
- data/lib/plutonium/routing/mapper_extensions.rb +1 -0
- data/lib/plutonium/ui/display/components/currency.rb +41 -9
- data/lib/plutonium/ui/display/options/inferred_types.rb +2 -5
- data/lib/plutonium/ui/form/base.rb +6 -0
- data/lib/plutonium/ui/form/components/currency.rb +64 -0
- data/lib/plutonium/ui/form/components/intl_tel_input.rb +27 -1
- data/lib/plutonium/ui/form/components/uppy.rb +20 -2
- data/lib/plutonium/ui/form/kanban_move.rb +46 -0
- data/lib/plutonium/ui/form/options/inferred_types.rb +6 -0
- data/lib/plutonium/ui/form/resource.rb +12 -0
- data/lib/plutonium/ui/form/theme.rb +7 -0
- data/lib/plutonium/ui/grid/card.rb +40 -13
- data/lib/plutonium/ui/kanban/column.rb +111 -24
- data/lib/plutonium/ui/kanban/resource.rb +118 -11
- data/lib/plutonium/ui/layout/base.rb +1 -1
- data/lib/plutonium/ui/options/has_cents_field.rb +21 -0
- data/lib/plutonium/ui/page/index.rb +1 -1
- data/lib/plutonium/ui/page/interactive_action.rb +12 -2
- data/lib/plutonium/ui/page/kanban_move.rb +20 -0
- data/lib/plutonium/ui/page/show.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +33 -0
- data/lib/plutonium/version.rb +1 -1
- data/package.json +5 -3
- data/src/css/components.css +5 -0
- data/src/js/controllers/currency_input_controller.js +39 -0
- data/src/js/controllers/intl_tel_input_controller.js +4 -0
- data/src/js/controllers/kanban_controller.js +442 -55
- data/src/js/controllers/register_controllers.js +2 -0
- data/yarn.lock +674 -4
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62c3179e6fc69beaae9e39a5892a478bd41c546fcf0fb580dea6a4aa9680e296
|
|
4
|
+
data.tar.gz: 94c2ce8570a323536961196170f8cb7187efe724834485edfd210a783147cbe8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b9dc172a16b74c0f313606cefe964a661842aacd63c21df3ed3cb9ca20179439d2f302b53658305a375ee2b752aacf056244eb0a6875e2a31e20276b8055a2c
|
|
7
|
+
data.tar.gz: e1f47b1429392d82253f27e8268309e129d33e5d62716b04c669c13e24cc4e3b34d74728d154189c116124319c7155911e49000be008ea72a2bbdd64fecc3dbd
|
|
@@ -14,8 +14,11 @@ For field-level rendering on cards (card_fields slots), see [[plutonium-resource
|
|
|
14
14
|
- **`kanban do…end` in the Definition auto-enables `:kanban`** in `defined_index_views` — exactly like `grid_fields` enables `:grid`. You do not need to call `index_views :kanban` separately unless you want to remove the table view.
|
|
15
15
|
- **The model needs `include Plutonium::Positioning`** (and a decimal `position` column + `positioned_on` call) for drag ordering to work. Without it, cards render unordered and moves raise an error. Use `position_on false` to explicitly opt out.
|
|
16
16
|
- **Static column actions are auto-registered** as interactive resource actions at class-load time. Dynamic boards (`columns do…end`) cannot introspect their columns at load time — declare any column-action interactions separately with top-level `action` calls.
|
|
17
|
-
- **Moves bypass `permitted_attributes_for_update`** — the `
|
|
17
|
+
- **Moves bypass `permitted_attributes_for_update`** — the `on_enter` callback runs with full model access. Gate the move itself with `kanban_move?` in the policy.
|
|
18
18
|
- **Quick-add (`add: true`) only appears when `create?` is true** in the policy.
|
|
19
|
+
- **Same-column drops = positioning only** — a reorder within a column fires neither `on_exit`, `on_enter`, nor an `enter_interaction`; they represent *leaving*/*entering* a column, so only cross-column drops trigger them.
|
|
20
|
+
- **`on_exit:` is the source-side hook** — fired when a card LEAVES a column (before the destination's `on_enter`, in the same transaction). Use it for source-tied side effects (stop a timer, release a slot) the destination can't own. It fires only on drag-moves via `kanban_move`, NOT on destroy/programmatic changes/quick-add — for those, use an ActiveRecord callback.
|
|
21
|
+
- **Use `on_enter:` / `enter_interaction:`, NOT `on_drop:` / `drop_interaction:`.** The old names were renamed. They still exist as deprecated aliases but **raise in development/test** (and only warn-and-map in production), so a definition using them fails your test suite. Always write the new names.
|
|
19
22
|
|
|
20
23
|
---
|
|
21
24
|
|
|
@@ -56,15 +59,15 @@ class TaskDefinition < ResourceDefinition
|
|
|
56
59
|
kanban do
|
|
57
60
|
column :todo,
|
|
58
61
|
scope: -> { where(status: "todo") },
|
|
59
|
-
|
|
62
|
+
on_enter: ->(r) { r.update!(status: "todo") }
|
|
60
63
|
|
|
61
64
|
column :doing,
|
|
62
65
|
scope: -> { where(status: "doing") },
|
|
63
|
-
|
|
66
|
+
on_enter: ->(r) { r.update!(status: "doing") }
|
|
64
67
|
|
|
65
68
|
column :done,
|
|
66
69
|
scope: -> { where(status: "done") },
|
|
67
|
-
|
|
70
|
+
on_enter: :mark_done! # Symbol → record.mark_done!
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
```
|
|
@@ -104,7 +107,7 @@ card_fields header: :title, meta: [:status, :priority], footer: :due_at
|
|
|
104
107
|
|
|
105
108
|
- **Mode A (default)** — delegates to `record.reposition!(prev_record:, next_record:)` from `Plutonium::Positioning`. Requires the model concern and a decimal column.
|
|
106
109
|
- **Mode B (block)** — you write the persistence. Plutonium still orders by the attribute; the block only persists the new value. Block receives a `Plutonium::Kanban::Positioning::Move` (fields: `record`, `column`, `prev`, `next`, `index`).
|
|
107
|
-
- **Mode C (`false`)** — no ordering, no repositioning. `
|
|
110
|
+
- **Mode C (`false`)** — no ordering, no repositioning. `on_enter` still fires.
|
|
108
111
|
|
|
109
112
|
### `realtime`
|
|
110
113
|
|
|
@@ -135,6 +138,8 @@ end
|
|
|
135
138
|
|
|
136
139
|
Evaluates the block at request time with the view context as `self` (`current_user`, `params`, `current_scoped_entity`, helpers all available). The block must return an Array of `Plutonium::Kanban::Column` objects — `column` is a DSL method only available outside the `columns` block. Declare any column-action interactions as top-level definition `action` calls — the block is not introspectable at class-load time.
|
|
137
140
|
|
|
141
|
+
> **`enter_interaction:` is NOT supported on dynamic boards.** Its hidden action is registered from the static column list at class-load time, which a `columns do…end` board doesn't have, and the key is column-scoped/internal so there's no manual-registration escape hatch (unlike column actions). A drop into such a column is rejected with a snap-back — it does not crash. Use a static board if you need `enter_interaction:`.
|
|
142
|
+
|
|
138
143
|
```ruby
|
|
139
144
|
kanban do
|
|
140
145
|
columns do
|
|
@@ -144,7 +149,7 @@ kanban do
|
|
|
144
149
|
:"team_#{team.id}",
|
|
145
150
|
label: team.name,
|
|
146
151
|
scope: -> { where(team_id: team.id) },
|
|
147
|
-
|
|
152
|
+
on_enter: ->(r) { r.update!(team_id: team.id) }
|
|
148
153
|
)
|
|
149
154
|
end
|
|
150
155
|
end
|
|
@@ -161,12 +166,14 @@ column :key,
|
|
|
161
166
|
color: :green, # Tailwind-mapped color hint
|
|
162
167
|
wip: 3, # max cross-column moves into this column
|
|
163
168
|
scope: -> { where(…) }, # 0-arg lambda or Symbol (sent to relation)
|
|
164
|
-
|
|
169
|
+
on_enter: ->(r) { … }, # 1-arg lambda or Symbol → record.method! (card ENTERS)
|
|
170
|
+
on_exit: ->(r) { … }, # 1-arg lambda or Symbol → runs when a card LEAVES this column
|
|
171
|
+
enter_interaction: MarkLostInteraction, # record-scoped interaction run on cross-column drop (see below)
|
|
165
172
|
collapsed: true, # starts collapsed (Stimulus persists toggle to localStorage)
|
|
166
173
|
add: true, # show "+ Add" button (requires create?)
|
|
167
|
-
accepts: true, # true (default), false, Array of source keys
|
|
174
|
+
accepts: true, # true (default), false, or Array of source keys (Proc raises)
|
|
168
175
|
locked: false, # reject all incoming drops (server-enforced)
|
|
169
|
-
role: :backlog # :backlog or :
|
|
176
|
+
role: :backlog # :backlog, :done or :lost (see presets below)
|
|
170
177
|
```
|
|
171
178
|
|
|
172
179
|
### Column role presets
|
|
@@ -175,31 +182,76 @@ column :key,
|
|
|
175
182
|
|---|---|
|
|
176
183
|
| `:backlog` | `add: true` |
|
|
177
184
|
| `:done` | `color: :green`, `collapsed: true` |
|
|
185
|
+
| `:lost` | `color: :red`, `collapsed: true` |
|
|
186
|
+
|
|
187
|
+
`:done` and `:lost` are the two terminal roles — collapsed by default, colour
|
|
188
|
+
signalling the outcome (`:done` = positive close, `:lost` = negative close). The
|
|
189
|
+
natural pair for won/lost pipelines (leads, deals, tickets).
|
|
178
190
|
|
|
179
191
|
Explicit options override the preset (e.g. `role: :done, collapsed: false`).
|
|
180
192
|
|
|
181
193
|
### `accepts:`
|
|
182
194
|
|
|
183
|
-
|
|
195
|
+
Structural drop topology — which **source columns** may drop cards here:
|
|
184
196
|
|
|
185
197
|
- `true` (default) — any source allowed
|
|
186
198
|
- `false` — column is a drop target but refuses everything (snap-back)
|
|
187
199
|
- `Array` — list of source column keys allowed: `accepts: [:doing]`
|
|
188
|
-
- `Proc` (1-arg) — per-card predicate: `accepts: ->(record) { record.state == "doing" }`
|
|
189
200
|
|
|
190
|
-
Checked server-side
|
|
201
|
+
Checked server-side; client-side visual hints read `data-kanban-accepts` (so the drag UI can grey out disallowed sources). **No Proc form** — a `Proc` raises `ArgumentError`. Record- or user-conditional rules belong in `kanban_move?`, which sees the record and the `from`/`to` columns (see Authorization below).
|
|
191
202
|
|
|
192
|
-
### `
|
|
203
|
+
### `on_enter:`
|
|
193
204
|
|
|
194
205
|
Runs inside a transaction after authorization and before repositioning. Receives the record for lambda form:
|
|
195
206
|
|
|
196
207
|
```ruby
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
on_enter: ->(r) { r.update!(status: "done") } # update! directly
|
|
209
|
+
on_enter: ->(r) { r.status = "done" } # attribute assignment — saved automatically
|
|
210
|
+
on_enter: :mark_done! # dispatched as record.mark_done!
|
|
200
211
|
```
|
|
201
212
|
|
|
202
|
-
If `
|
|
213
|
+
If `on_enter` only assigns attributes without calling `save!`/`update!`, the controller calls `record.save!` automatically when the record has unsaved changes after `on_enter` returns.
|
|
214
|
+
|
|
215
|
+
### `on_exit:`
|
|
216
|
+
|
|
217
|
+
The source-side counterpart to `on_enter:`. Runs on the column a card **leaves** during a cross-column move, **before** the destination's `on_enter`, in the same transaction (so it sees the pre-move state and rolls back if the move fails). Same Symbol/Proc dispatch and auto-save behaviour as `on_enter`.
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
column :doing,
|
|
221
|
+
scope: -> { where(status: "doing") },
|
|
222
|
+
on_enter: ->(r) { r.start_timer! }, # entering Doing
|
|
223
|
+
on_exit: ->(r) { r.stop_timer! } # leaving Doing (wherever it goes)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Use it for side effects tied to the column being **left** — the destination's `on_enter` doesn't know where a card came from, so source concerns (stop a timer, release a WIP/lock, un-assign) belong here.
|
|
227
|
+
|
|
228
|
+
⚠️ It fires **only** on a drag-move through `kanban_move` — not on `destroy`, a programmatic `status` change elsewhere, or quick-add. For "whenever this leaves, no matter how", use an ActiveRecord callback. Skipped on same-column reorders.
|
|
229
|
+
|
|
230
|
+
### `enter_interaction:`
|
|
231
|
+
|
|
232
|
+
Run an input-collecting interaction when a card is dropped **into** this column from another column — for entries that need more than a membership flip (a reason, a mail, an audit entry).
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
column :lost, scope: -> { where(status: "lost") }, enter_interaction: MarkLostInteraction
|
|
236
|
+
|
|
237
|
+
class MarkLostInteraction < ResourceInteraction
|
|
238
|
+
attribute :resource # MUST be record-scoped (singular), not :resources
|
|
239
|
+
attribute :reason, :string
|
|
240
|
+
input :reason
|
|
241
|
+
validates :reason, presence: true
|
|
242
|
+
def execute
|
|
243
|
+
resource.update!(status: "lost", lost_reason: reason)
|
|
244
|
+
succeed(resource).with_message("Marked as lost") # message → toast
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- **Auto-registered as a HIDDEN record action** under a column-scoped key (`:lost` → `:lost_enter_interaction`) — unique by construction, so two columns can reuse the same interaction class. No button on show/table/grid; reachable only by dropping. **No policy method of its own** — authorized by `kanban_move?` (see Authorization).
|
|
250
|
+
- **Move flow:** cross-column drop opens the interaction's form as a modal; on submit `on_enter` + interaction + repositioning commit in **one atomic transaction**. Validation failure rolls it all back (membership write included) and re-renders the modal with errors — nothing persists. Put side-effects on `deliver_later` so a rollback sends no stray mail.
|
|
251
|
+
- **Same-column reorder = positioning only** — neither `on_enter` nor the interaction fires (both = *entering* a column).
|
|
252
|
+
- **Quick-add (`+ Add`)** applies `on_enter` + positioning post-create; the interaction is not involved.
|
|
253
|
+
- **Author contract:** with both present, `on_enter` owns the membership attribute (`status`) and the interaction owns extras. If the interaction also writes membership it must set the **same** value (idempotent). With no `on_enter`, the interaction owns everything (like `:lost`).
|
|
254
|
+
- **Limitation:** custom success *responses* (`with_redirect_response`, `with_file_response`, …) are NOT honored on the drop path — board re-renders + modal closes. Use `.with_message` for feedback.
|
|
203
255
|
|
|
204
256
|
### Column actions
|
|
205
257
|
|
|
@@ -242,24 +294,37 @@ end
|
|
|
242
294
|
|
|
243
295
|
When `kanban_move?` returns `false`, the board renders read-only — no drag handles, no drop zones.
|
|
244
296
|
|
|
297
|
+
**`kanban_move?` is the ONLY move authorization** — plain moves and `enter_interaction:` columns alike (the interaction has no policy method of its own). To gate a *specific* transition, read the destination (and source) column from the authorization context via the optional `kanban_to` / `kanban_from` policy readers (the `Column` objects; `nil` for every non-move check):
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
def kanban_move?
|
|
301
|
+
return user.manager? if kanban_to&.key == :closed_won
|
|
302
|
+
super
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Rules take no positional args in ActionPolicy — the columns arrive as declared optional context (`authorize :kanban_from/:kanban_to, optional: true` on the base policy), supplied by the controller on the move check.
|
|
307
|
+
|
|
245
308
|
### Move authorization flow
|
|
246
309
|
|
|
247
310
|
1. Record loaded via current `relation_scope` (same as index).
|
|
248
|
-
2. `kanban_move?` checked — HTTP 403 on failure.
|
|
311
|
+
2. `kanban_move?` checked (with `kanban_from`/`kanban_to` in context) — HTTP 403 on failure. This is the sole authorization; an `enter_interaction` rides on it.
|
|
249
312
|
3. Column `accepts:` / `locked:` checked — HTTP 422 + card snap-back on failure.
|
|
250
313
|
4. `wip:` limit checked for cross-column moves — HTTP 422 on failure.
|
|
251
|
-
5. `
|
|
314
|
+
5. `on_enter` fires + record repositioned, all in a transaction.
|
|
252
315
|
|
|
253
316
|
On a 422 rejection (steps 3–4) the response re-renders the source column (snap-back) **and** appends a dismissable warning toast naming the reason (e.g. `“Pending” is at its WIP limit (5).`) to the board's `#kanban-flash` region — so the snap-back is never silent. The toast renders the shared `plutonium/toast` partial directly (not via `flash`), so a stale undisplayed flash can't leak into the turbo-stream response.
|
|
254
317
|
|
|
255
318
|
### No permitted-attributes gate
|
|
256
319
|
|
|
257
|
-
Moves do not pass through `permitted_attributes_for_update`. `
|
|
320
|
+
Moves do not pass through `permitted_attributes_for_update`. `on_enter` is trusted author code; it is responsible for assigning only the appropriate attributes.
|
|
258
321
|
|
|
259
322
|
### Quick-add
|
|
260
323
|
|
|
261
324
|
The `+ Add` button (column `add: true`) only renders when the policy's `create?` is true. The opened form is the standard new-resource form.
|
|
262
325
|
|
|
326
|
+
The record is created normally, **then** the column's `on_enter` + positioning are applied to the **saved** record (it lands in the clicked column, appended to the bottom) — `on_enter` runs against a real record, exactly as on a drag. **Your grouping column must have a default** (DB or model), because `on_enter` runs after save; a `NOT NULL` grouping column with no default fails quick-add create. A raising `on_enter` keeps the created record in its default column and toasts the error (the create is not rolled back).
|
|
327
|
+
|
|
263
328
|
---
|
|
264
329
|
|
|
265
330
|
## Worked example (full)
|
|
@@ -272,18 +337,18 @@ class TaskDefinition < ResourceDefinition
|
|
|
272
337
|
|
|
273
338
|
column :todo,
|
|
274
339
|
scope: -> { where(status: "todo") },
|
|
275
|
-
|
|
340
|
+
on_enter: ->(r) { r.update!(status: "todo") },
|
|
276
341
|
role: :backlog # add: true
|
|
277
342
|
|
|
278
343
|
column :doing,
|
|
279
344
|
scope: -> { where(status: "doing") },
|
|
280
|
-
|
|
345
|
+
on_enter: ->(r) { r.update!(status: "doing") },
|
|
281
346
|
wip: 3
|
|
282
347
|
|
|
283
348
|
column :done,
|
|
284
349
|
scope: -> { where(status: "done") },
|
|
285
|
-
|
|
286
|
-
accepts:
|
|
350
|
+
on_enter: :mark_done!,
|
|
351
|
+
accepts: [:doing], # only cards coming from :doing
|
|
287
352
|
role: :done do # color: :green, collapsed: true
|
|
288
353
|
action :archive_all,
|
|
289
354
|
interaction: ArchiveTasksInteraction,
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.62.0] - 2026-07-04
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Run input-less column actions directly instead of an empty modal
|
|
10
|
+
- Don't call signed_id on an unsaved ActiveStorage blob in uppy
|
|
11
|
+
- Active_shrine downstream fixes — mime-types gem + resource param double-read
|
|
12
|
+
- Stop the board blanking on search/filter/scope
|
|
13
|
+
- Size currency input padding to its unit prefix
|
|
14
|
+
- Preserve collapse + horizontal scroll across moves and refresh
|
|
15
|
+
- Give the body a base text color so unstyled text stays visible
|
|
16
|
+
- Give modal dialogs a base text color so unstyled text stays visible
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- AI agent on-ramp — llms.txt, /ai quickstart, crawlable skills
|
|
21
|
+
- Type-aware kanban meta badges + has_cents currency unit
|
|
22
|
+
- Expose intl-tel-input options + default_phone_country config
|
|
23
|
+
- Currency input + currency/choice-aware wizard review summary
|
|
24
|
+
- Add :lost terminal column role
|
|
25
|
+
- Keep the board fresh + scrolled across writes and actions
|
|
26
|
+
- [**breaking**] Drop interactions, immediate drops, on_exit + on_drop→on_enter rename ([#67](https://github.com/radioactive-labs/plutonium-core/issues/67))
|
|
27
|
+
|
|
28
|
+
### Refactoring
|
|
29
|
+
|
|
30
|
+
- Server-read collapse cookie + stable frame placeholders
|
|
31
|
+
|
|
5
32
|
## [0.61.0] - 2026-06-30
|
|
6
33
|
|
|
7
34
|
### Bug Fixes
|