studio-engine 0.27.0 → 0.28.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79aac649d7403deadeaa9f055762802f111b7082865398432753dfdd93b998e0
|
|
4
|
+
data.tar.gz: 80307a1b7077886c9853ad5a6ab7f2dc79a9d7bf6d4db7aa883f5eb6162a77bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8fb8f96918656e88d2d8cc3379716d544afd461bdaa19205c480e273ab71199c25cede8c7b729f9b6cf3400388a7976ac34f4135d96fd60b3e8e4083969f360e
|
|
7
|
+
data.tar.gz: 2f329ab8c0c240cf56e11e2e9d7bc1c3c6711e24dcb52ee82a2de2785c1697cf333e3bcc17f056db22400e4e606d6c30108565502bd8a6119e2d7b50e18a1a64
|
|
@@ -48,9 +48,16 @@
|
|
|
48
48
|
onMoveHook: opts.onMoveHook || null,
|
|
49
49
|
onDropHook: opts.onDropHook || null,
|
|
50
50
|
labels: opts.labels || {},
|
|
51
|
+
// gap 3: the column key a data-exit-action="archive" remove re-parents INTO
|
|
52
|
+
// (its #dropzone-<key>) rather than deleting the card. null ⇒ 0.27.0 removal.
|
|
53
|
+
archiveZone: opts.archiveZone || null,
|
|
51
54
|
|
|
52
55
|
// --- state ---------------------------------------------------------
|
|
53
56
|
toasts: [],
|
|
57
|
+
// gap 1: board-level chrome state lives HERE, on the studioBoard scope, so a
|
|
58
|
+
// board's header_slot controls + column show_expr bind to it directly — no
|
|
59
|
+
// bespoke outer Alpine wrapper. Seeded from board_state; {} when omitted.
|
|
60
|
+
state: (opts.state && typeof opts.state === "object") ? opts.state : {},
|
|
54
61
|
|
|
55
62
|
// --- init ----------------------------------------------------------
|
|
56
63
|
init: function () {
|
|
@@ -75,6 +82,19 @@
|
|
|
75
82
|
zoneKey: function (el) { return el ? el.getAttribute("data-" + this.zoneAttr) : null; },
|
|
76
83
|
label: function (zone) { return this.labels[zone] || zone; },
|
|
77
84
|
|
|
85
|
+
// --- board-chrome state helpers (gap 1) ----------------------------
|
|
86
|
+
// Generic, domain-free operations over `state` so a board's header_slot /
|
|
87
|
+
// column show_expr drive filters + toggles without a bespoke wrapper.
|
|
88
|
+
toggleState: function (key) { this.state[key] = !this.state[key]; },
|
|
89
|
+
listHas: function (key, val) {
|
|
90
|
+
return Array.isArray(this.state[key]) && this.state[key].indexOf(val) !== -1;
|
|
91
|
+
},
|
|
92
|
+
toggleInState: function (key, val) {
|
|
93
|
+
if (!Array.isArray(this.state[key])) this.state[key] = [];
|
|
94
|
+
var i = this.state[key].indexOf(val);
|
|
95
|
+
if (i === -1) { this.state[key].push(val); } else { this.state[key].splice(i, 1); }
|
|
96
|
+
},
|
|
97
|
+
|
|
78
98
|
// --- SortableJS wiring ---------------------------------------------
|
|
79
99
|
initSortables: function () {
|
|
80
100
|
if (typeof Sortable === "undefined") return;
|
|
@@ -271,6 +291,21 @@
|
|
|
271
291
|
var card = document.getElementById(target);
|
|
272
292
|
if (!card) { renderNow(el); return; }
|
|
273
293
|
self.animateCardExit(card, exitAction).then(function () {
|
|
294
|
+
// gap 3: an `archive` exit RE-PARENTS the card into the archive
|
|
295
|
+
// column (#dropzone-<archiveZone>) instead of vanishing, when the
|
|
296
|
+
// board named one and it exists. Everything else falls through to
|
|
297
|
+
// Turbo's remove (the 0.27.0 behavior).
|
|
298
|
+
if (exitAction === "archive" && self.archiveZone) {
|
|
299
|
+
var zone = document.getElementById("dropzone-" + self.archiveZone);
|
|
300
|
+
if (zone) {
|
|
301
|
+
var anchor = self.emptySelector ? zone.querySelector(self.emptySelector) : null;
|
|
302
|
+
zone.insertBefore(card, anchor);
|
|
303
|
+
self.setCardZone(card, self.archiveZone);
|
|
304
|
+
self.resetCardExit(card);
|
|
305
|
+
self.updateCounts();
|
|
306
|
+
return; // keep the card in the archive column — do NOT remove it
|
|
307
|
+
}
|
|
308
|
+
}
|
|
274
309
|
renderNow(el);
|
|
275
310
|
self.updateCounts();
|
|
276
311
|
});
|
|
@@ -279,7 +314,13 @@
|
|
|
279
314
|
},
|
|
280
315
|
|
|
281
316
|
animateCardExit: function (card, kind) {
|
|
282
|
-
if (!card
|
|
317
|
+
if (!card) return Promise.resolve();
|
|
318
|
+
// gap 4: stamp an observable marker for WHICH exit is running (archive vs
|
|
319
|
+
// delete). Set before the reduced-motion guard so the marker is present
|
|
320
|
+
// even when the animation itself is skipped — the re-parent branch and any
|
|
321
|
+
// e2e both key off it.
|
|
322
|
+
card.dataset.exitAction = kind;
|
|
323
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return Promise.resolve();
|
|
283
324
|
card.style.pointerEvents = "none";
|
|
284
325
|
var frames = kind === "archive"
|
|
285
326
|
? [{ opacity: 1, transform: "translateY(0) scale(1)" }, { opacity: 0, transform: "translateY(18px) scale(0.86)" }]
|
|
@@ -292,6 +333,18 @@
|
|
|
292
333
|
return anim.finished.catch(function () {});
|
|
293
334
|
},
|
|
294
335
|
|
|
336
|
+
// Clear an exit animation + its marker so a re-parented card is fully
|
|
337
|
+
// visible again in the archive column (used by the gap-3 re-parent path).
|
|
338
|
+
resetCardExit: function (card) {
|
|
339
|
+
if (!card) return;
|
|
340
|
+
card.getAnimations({ subtree: false }).forEach(function (a) { a.cancel(); });
|
|
341
|
+
delete card.dataset.exitAction;
|
|
342
|
+
card.style.pointerEvents = "";
|
|
343
|
+
card.style.opacity = "";
|
|
344
|
+
card.style.transform = "";
|
|
345
|
+
card.style.filter = "";
|
|
346
|
+
},
|
|
347
|
+
|
|
295
348
|
// --- toasts --------------------------------------------------------
|
|
296
349
|
toast: function (message, type) {
|
|
297
350
|
if (!this.toastsEnabled) {
|
|
@@ -19,10 +19,27 @@
|
|
|
19
19
|
studio/board/_column (the `.kanban-dropzone`) and studio/board/_card_shell (the
|
|
20
20
|
`.kanban-card`) — an app may hand-roll a bespoke card as long as it matches.
|
|
21
21
|
|
|
22
|
+
CHROME COMPOSITION (the recipe that replaces a bespoke outer Alpine wrapper) —
|
|
23
|
+
a board with filters / toggles no longer needs to wrap this primitive in its own
|
|
24
|
+
x-data component. Everything the chrome needs lives ON the studioBoard scope:
|
|
25
|
+
1. `board_state:` seeds a reactive bag on the factory (`state`). Pass the
|
|
26
|
+
chrome's flags/lists, e.g. { showArchived: false, hiddenApps: [] }.
|
|
27
|
+
2. `header_slot:` (and `above_board_slot:`) render INSIDE the studioBoard
|
|
28
|
+
section, so their controls bind straight to that state and the generic
|
|
29
|
+
helpers the factory exposes: `toggleState('showArchived')`,
|
|
30
|
+
`toggleInState('hiddenApps', repo)`, `listHas('hiddenApps', repo)`.
|
|
31
|
+
3. A toggle-able column reads the same state via its `show_expr:`
|
|
32
|
+
(e.g. show_expr: "state.showArchived"); cards x-show off `listHas(...)`.
|
|
33
|
+
4. An Archived column that a stage→archived broadcast should re-parent INTO
|
|
34
|
+
(not delete) is named by the board's `archive_zone:`.
|
|
35
|
+
|
|
22
36
|
locals:
|
|
23
37
|
columns: (req) [{ key:, label:, cards:, count:, badge_class:,
|
|
24
|
-
collapsible:, hidden:,
|
|
25
|
-
|
|
38
|
+
count_class:, collapsible:, hidden:, show_expr:, min_width:,
|
|
39
|
+
kickoff:, header_extra: }]. `cards` are records passed to
|
|
40
|
+
`card_partial`; `count` defaults to cards.size. See
|
|
41
|
+
studio/board/_column for the additive per-column hooks
|
|
42
|
+
(count_class / show_expr / min_width / kickoff).
|
|
26
43
|
card_partial: (req) partial rendered per card (e.g. "tasks/task_card").
|
|
27
44
|
card_as: local the record binds to in card_partial. Default :card.
|
|
28
45
|
card_locals: per-card extra locals — a Proc ->(record){ Hash } or a Hash.
|
|
@@ -50,6 +67,11 @@
|
|
|
50
67
|
empty_label: empty-zone text. Default "Drop here".
|
|
51
68
|
header_slot: raw HTML rendered above the columns (pass html_safe content).
|
|
52
69
|
above_board_slot: raw HTML rendered between the header and the columns.
|
|
70
|
+
board_state: Hash seeding the factory's reactive `state` bag (board-level
|
|
71
|
+
chrome: filter/toggle flags + lists). nil ⇒ empty {}.
|
|
72
|
+
archive_zone: column key a `remove` exit-stream marked data-exit-action=
|
|
73
|
+
"archive" RE-PARENTS the card into (its #dropzone-<key>),
|
|
74
|
+
instead of deleting it. nil ⇒ the card is removed (0.27.0).
|
|
53
75
|
fx: window[<name>] module the factory defers add/exit anims to.
|
|
54
76
|
on_move_hook: window[<name>] opaque fn, called with { record, from, to }.
|
|
55
77
|
on_drop_hook: window[<name>] opaque fn, called with { ids, zone }.
|
|
@@ -79,6 +101,8 @@
|
|
|
79
101
|
empty_label = local_assigns.fetch(:empty_label, "Drop here")
|
|
80
102
|
header_slot = local_assigns.fetch(:header_slot, nil)
|
|
81
103
|
above_board_slot = local_assigns.fetch(:above_board_slot, nil)
|
|
104
|
+
board_state = local_assigns.fetch(:board_state, nil) # gap 1: chrome-state home
|
|
105
|
+
archive_zone = local_assigns.fetch(:archive_zone, nil) # gap 3: exit re-parent target
|
|
82
106
|
fx = local_assigns.fetch(:fx, nil)
|
|
83
107
|
on_move_hook = local_assigns.fetch(:on_move_hook, nil)
|
|
84
108
|
on_drop_hook = local_assigns.fetch(:on_drop_hook, nil)
|
|
@@ -102,6 +126,8 @@
|
|
|
102
126
|
toasts: !!toasts,
|
|
103
127
|
live: live_channel.present?,
|
|
104
128
|
demo: !!demo,
|
|
129
|
+
state: (board_state.is_a?(Hash) ? board_state : nil),
|
|
130
|
+
archiveZone: (archive_zone ? archive_zone.to_s : nil),
|
|
105
131
|
fx: fx,
|
|
106
132
|
onMoveHook: on_move_hook,
|
|
107
133
|
onDropHook: on_drop_hook,
|
|
@@ -12,10 +12,25 @@
|
|
|
12
12
|
|
|
13
13
|
locals:
|
|
14
14
|
column: (req) { key:, label:, cards:, count:, badge_class:,
|
|
15
|
-
collapsible:, hidden:,
|
|
15
|
+
count_class:, collapsible:, hidden:, show_expr:,
|
|
16
|
+
min_width:, kickoff:, header_extra: }. `cards` are the
|
|
16
17
|
records passed to the card partial; `count` defaults to
|
|
17
18
|
cards.size; `badge_class` picks a components/_badge scheme
|
|
18
19
|
for the count (else a plain neutral count chip).
|
|
20
|
+
Additive board-chrome hooks (all opt-in, all default to
|
|
21
|
+
the 0.27.0 rendering when omitted):
|
|
22
|
+
count_class: raw class string colouring the neutral
|
|
23
|
+
count chip (e.g. a task_stage_count_classes value).
|
|
24
|
+
Ignored when badge_class is set. Default neutral chip.
|
|
25
|
+
show_expr: Alpine x-show expression for a toggle-able
|
|
26
|
+
column (binds against the board's studioBoard scope,
|
|
27
|
+
e.g. "state.showArchived"). Adds x-cloak so it can't
|
|
28
|
+
flash before Alpine boots. Independent of `hidden:`.
|
|
29
|
+
min_width: the column's sm min-width utility class.
|
|
30
|
+
Default "sm:min-w-[190px]".
|
|
31
|
+
kickoff: raw HTML rendered in a fixed-height slot
|
|
32
|
+
under the header row (a per-column kickoff chip);
|
|
33
|
+
nil renders nothing.
|
|
19
34
|
zone_attr: data-attr name for the zone. Default "stage".
|
|
20
35
|
dom_id_prefix: card dom-id prefix, forwarded to the cards. Default "card-".
|
|
21
36
|
zone_selector_class: the drop-zone class. Default "kanban-dropzone".
|
|
@@ -41,9 +56,20 @@
|
|
|
41
56
|
cards = column[:cards] || []
|
|
42
57
|
count = column.fetch(:count) { cards.size }
|
|
43
58
|
badge_class = column[:badge_class]
|
|
59
|
+
# gap 2: an opt-in raw class colouring the neutral count chip; nil keeps the
|
|
60
|
+
# exact 0.27.0 neutral chip classes.
|
|
61
|
+
count_class = column[:count_class]
|
|
44
62
|
collapsible = column[:collapsible]
|
|
45
63
|
hidden = column[:hidden]
|
|
64
|
+
# gap 5: a bindable visibility expr (x-show), a per-board min-width, and a
|
|
65
|
+
# per-column kickoff slot — each nil/absent renders identically to 0.27.0.
|
|
66
|
+
show_expr = column[:show_expr]
|
|
67
|
+
min_width = column[:min_width] || "sm:min-w-[190px]"
|
|
68
|
+
kickoff = column[:kickoff]
|
|
46
69
|
header_extra = column[:header_extra]
|
|
70
|
+
# The neutral count chip's colour classes: an opt-in raw class, else the 0.27.0
|
|
71
|
+
# neutral default. The layout classes are shared; only the colour band varies.
|
|
72
|
+
count_color = count_class || "bg-surface-alt text-secondary border border-subtle"
|
|
47
73
|
|
|
48
74
|
# Merge the per-card hook (Proc or Hash) with the record binding.
|
|
49
75
|
resolve_locals = lambda do |record|
|
|
@@ -51,21 +77,29 @@
|
|
|
51
77
|
{ card_as => record }.merge(extra || {})
|
|
52
78
|
end
|
|
53
79
|
%>
|
|
54
|
-
<div class="studio-board-column w-full flex-none sm:flex-1
|
|
80
|
+
<div class="studio-board-column w-full flex-none sm:flex-1 <%= min_width %>"
|
|
55
81
|
data-board-column="<%= key %>"
|
|
56
82
|
<% if collapsible %>data-board-collapsible="true"<% end %>
|
|
83
|
+
<% if show_expr %>x-show="<%= show_expr %>" x-cloak<% end %>
|
|
57
84
|
<% if hidden %>x-cloak style="display:none"<% end %>>
|
|
58
85
|
<div class="mb-3 px-1 flex items-center gap-2">
|
|
59
86
|
<h3 class="text-sm font-bold text-heading uppercase tracking-wider"><%= label %></h3>
|
|
60
87
|
<% if badge_class %>
|
|
61
88
|
<%= render "components/badge", text: count.to_s, scheme: badge_class, data_board_count: key %>
|
|
62
89
|
<% else %>
|
|
63
|
-
<span class="stage-count inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-full text-xs font-bold
|
|
90
|
+
<span class="stage-count inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-full text-xs font-bold <%= count_color %>"
|
|
64
91
|
data-board-count="<%= key %>"><%= count %></span>
|
|
65
92
|
<% end %>
|
|
66
93
|
<% if header_extra %><%= header_extra %><% end %>
|
|
67
94
|
</div>
|
|
68
95
|
|
|
96
|
+
<%# gap 5: per-column kickoff slot — a fixed-height row under the header so every
|
|
97
|
+
column's dropzone starts at the same y (no height jog) when only some carry a
|
|
98
|
+
chip. Renders nothing when the column passes no kickoff. %>
|
|
99
|
+
<% if kickoff %>
|
|
100
|
+
<div class="studio-board-kickoff mt-2 min-h-[26px]"><%= kickoff %></div>
|
|
101
|
+
<% end %>
|
|
102
|
+
|
|
69
103
|
<div id="dropzone-<%= key %>"
|
|
70
104
|
data-<%= zone_attr %>="<%= key %>"
|
|
71
105
|
class="<%= zone_selector_class %> rounded-xl border-2 border-dashed border-subtle min-h-40 p-2 space-y-2 transition-colors duration-150 sm:min-h-[calc(100vh-220px)]">
|
|
@@ -109,6 +109,69 @@
|
|
|
109
109
|
</div>
|
|
110
110
|
</div>
|
|
111
111
|
|
|
112
|
+
<%# ---- The ENRICHED board chrome (0.28.0) ------------------------------ %>
|
|
113
|
+
<%# One specimen that exercises the five additive hooks the /tasks pilot surfaced,
|
|
114
|
+
all opt-in: (1) board_state + header_slot chrome home, (2) a coloured count
|
|
115
|
+
chip via count_class, (3) archive_zone exit re-parent, (4) the exit marker,
|
|
116
|
+
(5) a toggle-able column (show_expr) + a per-column kickoff slot + min_width.
|
|
117
|
+
No outer Alpine wrapper: the Show/Hide-archived toggle in header_slot and the
|
|
118
|
+
archived column's show_expr both bind to the SAME studioBoard state. %>
|
|
119
|
+
<% chrome_header = capture do %>
|
|
120
|
+
<div class="flex items-center gap-3 flex-wrap">
|
|
121
|
+
<button type="button"
|
|
122
|
+
class="px-3 py-1.5 rounded-lg text-sm font-medium transition border border-subtle"
|
|
123
|
+
:class="state.showArchived ? 'bg-primary text-white border-primary' : 'bg-surface text-secondary hover:text-heading'"
|
|
124
|
+
@click="toggleState('showArchived')"
|
|
125
|
+
data-test="chrome-archived-toggle">
|
|
126
|
+
<span x-text="state.showArchived ? 'Hide archived' : 'Show archived'">Show archived</span>
|
|
127
|
+
</button>
|
|
128
|
+
<span class="label-upper"><code class="font-mono text-2xs">header_slot</code> chrome · binds to <code class="font-mono text-2xs">state.showArchived</code> — no outer wrapper</span>
|
|
129
|
+
</div>
|
|
130
|
+
<% end %>
|
|
131
|
+
<% chrome_kickoff = capture do %>
|
|
132
|
+
<span class="inline-flex items-center gap-1 rounded border border-primary/30 bg-primary/10 px-1.5 py-0.5 text-2xs font-semibold uppercase tracking-wide text-primary">kickoff slot</span>
|
|
133
|
+
<% end %>
|
|
134
|
+
<%
|
|
135
|
+
# Per-column count_class colours (raw classes, the task_stage_count_classes shape),
|
|
136
|
+
# a kickoff chip + widened min_width on Designed, and a toggle-able Archived column
|
|
137
|
+
# (show_expr) the board re-parents into (archive_zone).
|
|
138
|
+
chrome_columns = [
|
|
139
|
+
{ key: "designed", label: "Designed",
|
|
140
|
+
count_class: "bg-blue-500/10 text-blue-500 border border-blue-500/30",
|
|
141
|
+
kickoff: chrome_kickoff, min_width: "sm:min-w-[15rem]",
|
|
142
|
+
cards: [{ id: "chrome-count-color", zone: "designed", title: "Coloured count chip", repo: "studio-engine", who: "SH" }] },
|
|
143
|
+
{ key: "building", label: "Building",
|
|
144
|
+
count_class: "bg-yellow-500/10 text-yellow-400 border border-yellow-500/30",
|
|
145
|
+
cards: [{ id: "chrome-bindable-col", zone: "building", title: "Bindable columns", repo: "turf-monster", who: "CA" }] },
|
|
146
|
+
{ key: "shipped", label: "Shipped",
|
|
147
|
+
count_class: "bg-emerald-500/10 text-emerald-500 border border-emerald-500/30",
|
|
148
|
+
cards: [{ id: "chrome-exit-marker", zone: "shipped", title: "Exit re-parent marker", repo: "mcritchie", who: "ST" }] },
|
|
149
|
+
{ key: "archived", label: "Archived",
|
|
150
|
+
count_class: "bg-gray-500/10 text-gray-400 border border-gray-500/30",
|
|
151
|
+
show_expr: "state.showArchived",
|
|
152
|
+
cards: [{ id: "chrome-archived-card", zone: "archived", title: "Archived (re-parent target)", repo: "mcritchie", who: "ST" }] }
|
|
153
|
+
]
|
|
154
|
+
%>
|
|
155
|
+
<div class="space-y-3">
|
|
156
|
+
<div class="flex items-center gap-2">
|
|
157
|
+
<span class="badge" style="color: var(--color-primary); border-color: var(--color-primary)">Enriched</span>
|
|
158
|
+
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> · 0.28.0 chrome — coloured counts, toggle-able column, archive re-parent</p>
|
|
159
|
+
</div>
|
|
160
|
+
<%= render "studio/board/board",
|
|
161
|
+
demo: true,
|
|
162
|
+
columns: chrome_columns,
|
|
163
|
+
card_partial: "style/board/demo_card",
|
|
164
|
+
card_as: :card,
|
|
165
|
+
reorder_url: "#",
|
|
166
|
+
move_url: "#",
|
|
167
|
+
move_param: { resource: "task", attr: "stage" },
|
|
168
|
+
group: "studio-board-chrome",
|
|
169
|
+
board_state: { showArchived: false },
|
|
170
|
+
archive_zone: "archived",
|
|
171
|
+
header_slot: chrome_header,
|
|
172
|
+
empty_label: "Drop tasks here" %>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
112
175
|
<%# ---- The static reference sketch ------------------------------------- %>
|
|
113
176
|
<div class="fade-edge-x overflow-x-auto pb-2">
|
|
114
177
|
<div class="flex gap-4 min-w-max">
|
|
@@ -162,9 +225,11 @@
|
|
|
162
225
|
</div>
|
|
163
226
|
<ul class="text-secondary text-sm space-y-1 list-disc list-inside">
|
|
164
227
|
<li>Column layout + horizontal scroll with faded edges</li>
|
|
165
|
-
<li>The <code class="font-mono text-2xs">stage-*</code> badge palette + count chips</li>
|
|
228
|
+
<li>The <code class="font-mono text-2xs">stage-*</code> badge palette + count chips (colour via <code class="font-mono text-2xs">count_class</code>)</li>
|
|
166
229
|
<li>Card chrome (<code class="font-mono text-2xs">.card</code>) and the drag / rank handle</li>
|
|
167
230
|
<li>Empty-column drop state</li>
|
|
231
|
+
<li>Chrome-state home (<code class="font-mono text-2xs">board_state</code> + <code class="font-mono text-2xs">header_slot</code>) — filters/toggles without an outer wrapper</li>
|
|
232
|
+
<li>Toggle-able columns (<code class="font-mono text-2xs">show_expr</code>), a kickoff slot, and archive re-parent (<code class="font-mono text-2xs">archive_zone</code>)</li>
|
|
168
233
|
</ul>
|
|
169
234
|
</div>
|
|
170
235
|
<div class="card p-5 space-y-2">
|
|
@@ -175,7 +240,7 @@
|
|
|
175
240
|
<ul class="text-secondary text-sm space-y-1 list-disc list-inside">
|
|
176
241
|
<li>Which stages become columns</li>
|
|
177
242
|
<li>The card body — fields, chips, avatars, live timers</li>
|
|
178
|
-
<li>
|
|
243
|
+
<li>Filter/toggle logic + header actions, composed onto the shared chrome state</li>
|
|
179
244
|
<li>Live-update wiring (Turbo Streams on the deploy board)</li>
|
|
180
245
|
</ul>
|
|
181
246
|
</div>
|
data/lib/studio/version.rb
CHANGED