feat(showcase): IR-parity R-key shuffle + stagger entry animation; fix(cleanup): min-dim Delete swallowed crypto.subtle TypeError on plain HTTP
**showcase R-key + entry animation**
Restores two behaviors lost during the FC-2 IR→Vue port. Operator-flagged
2026-05-27.
- ShowcaseView listens for keydown 'r'/'R' on window. Triggers
`store.shuffle()`. Skips when an input/textarea/contenteditable is
focused or a Vuetify overlay is open (the dialog/menu sets
`.v-overlay--active` on the body).
- MasonryGrid gains an opt-in `animateFromIndex` prop (default
`Number.POSITIVE_INFINITY` = off). When set, items with index ≥ the
threshold animate in with a stagger fade-in: 12px translateY,
0.25s ease, 60ms per item, capped by `prefers-reduced-motion`.
Stagger uses original-items-array index (resolved via an `idxById`
Map) so the reading order is preserved even after the masonry
distributes items across columns.
- ShowcaseView watches `store.images.length`: shrink-or-zero baseline
⇒ `animateFromIndex=0` (animate everything on initial load /
shuffle); grow ⇒ baseline=prevCount (animate only the appended
tail on infinite-scroll). Other MasonryGrid consumers (ArtistView's
Gallery tab) don't pass the prop, so they keep their current
no-animation behavior.
Direct port of IR's `app/static/js/showcase.js` keyboard handler +
`app/static/style.css` itemFadeIn keyframe.
**min-dim Delete: crypto.subtle TypeError fix**
The Delete button on the Cleanup → Minimum Dimensions card was
silently no-op'ing. Root cause: `crypto.subtle` is Secure-Context-gated
(undefined on plain-HTTP origins per the homelab posture). The card's
`onDeleteClick` computed the Tier-C confirm token via
`crypto.subtle.digest('SHA-256', ...)`, which threw TypeError before
`showModal.value = true`. The promise rejected, the click handler had
no `.catch`, the modal never opened — exactly the operator's reported
symptom.
Same shape as the v26.05.26.0 `navigator.clipboard` fix on the
ErrorDetailModal Copy button.
Fix: backend `/api/cleanup/min-dimension/preview` now returns
`confirm_token` (the canonical `delete-min-dim-<sha8>` string) in its
response. Frontend reads it from the preview response and feeds the
8-char suffix to DestructiveConfirmModal's `runId` prop — no
client-side crypto needed. Single source of truth.
Integration test `test_min_dimension_preview_returns_count` pinned to
also assert `body["confirm_token"]` matches the server-side compute.
This commit is contained in:
@@ -66,6 +66,10 @@ async def test_min_dimension_preview_returns_count(client, db, tmp_path):
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert body["count"] == 1
|
||||
# Preview hands the canonical Tier-C confirm token back so the
|
||||
# frontend doesn't have to recompute SHA-256 client-side
|
||||
# (crypto.subtle is Secure-Context-gated; FC runs over plain HTTP).
|
||||
assert body["confirm_token"] == _sha256_min_dim_token(200, 200)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user