From 4c56cf121ffcb6418a1f6ec5221e3409527a2c0e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 1 Jun 2026 08:17:54 -0400 Subject: [PATCH] fix(modal): Esc closes from tag input; Provenance cards scroll at ~2.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two operator-flagged UX gaps from 2026-06-01: 1. **Esc trapped inside the tag-entry field** The autofocused TagAutocomplete input made Esc-to-close unreachable because ImageViewer's keydown handler bailed early on `isTextEntry(ev.target)` for every key including Escape. Now Escape closes the modal from text inputs too — except when a Vuetify overlay is open (FandomPicker, autocomplete dropdown, suggestion 3-dot menu), in which case that overlay's own Esc-handling fires instead of closing the whole modal mid-interaction. Detected via `.v-overlay--active`. Arrow keys still gate on isTextEntry so the tag input handles typing without navigating images. 2. **Provenance cards expanded the side panel unbounded** When an image had many ImageProvenance entries the cards stack pushed the Tags section below the fold. Wrapped the cards in a `.fc-prov__cards` container with max-height 270px (≈2.5 cards) and thin overflow scrollbar. Title stays anchored at top; Tags panel sits at a consistent position below regardless of provenance count. --- frontend/src/components/modal/ImageViewer.vue | 11 +++++++++- .../src/components/modal/ProvenancePanel.vue | 20 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/modal/ImageViewer.vue b/frontend/src/components/modal/ImageViewer.vue index 0ea01bc..84cb495 100644 --- a/frontend/src/components/modal/ImageViewer.vue +++ b/frontend/src/components/modal/ImageViewer.vue @@ -92,7 +92,16 @@ let prevBodyOverflow = null // own keystrokes. function onKeyDown(ev) { if (ev.key === 'Escape') { - if (isTextEntry(ev.target)) return + // Escape closes the modal even from inside a text input — that's + // the universal "get me out of here" expectation, and the + // autofocused tag-entry field would otherwise trap focus with no + // visible escape (operator-flagged 2026-06-01). EXCEPTION: when a + // nested Vuetify overlay is open (v-menu autocomplete dropdown, + // FandomPicker v-dialog, per-suggestion 3-dot menu), let that + // overlay's own Esc handling fire instead of closing the whole + // modal mid-interaction. Vuetify marks open overlays with + // `.v-overlay--active`. + if (document.querySelector('.v-overlay--active')) return ev.preventDefault() emit('close') } else if (ev.key === 'ArrowLeft') { diff --git a/frontend/src/components/modal/ProvenancePanel.vue b/frontend/src/components/modal/ProvenancePanel.vue index 1d8e5ee..5c36fd4 100644 --- a/frontend/src/components/modal/ProvenancePanel.vue +++ b/frontend/src/components/modal/ProvenancePanel.vue @@ -10,7 +10,11 @@ density="compact" >{{ state.error }} - +

Attachments

@@ -159,6 +163,18 @@ function openPost(postId, artistId) { color: rgb(var(--v-theme-on-surface)); margin-bottom: 12px; } +.fc-prov__cards { + /* 2.5 cards-worth at the typical collapsed card height (~108px each + incl. 10px gap). Slightly under to ensure the third card's bottom + edge is clipped — the visual cue that there's more below. */ + max-height: 270px; + overflow-y: auto; + /* Hairline scrollbar that doesn't compete with content. */ + scrollbar-width: thin; + scrollbar-color: rgb(var(--v-theme-surface-light)) transparent; + /* Pad-right so the scrollbar gutter doesn't squeeze card borders. */ + padding-right: 4px; +} .fc-prov__card { border: 1px solid rgb(var(--v-theme-surface-light)); border-radius: 6px; padding: 10px 12px; margin-bottom: 10px;