Esc from inside the tag input: ImageViewer's keydown handler bailed early on isTextEntry(ev.target) for every key, so the autofocused TagAutocomplete trapped Escape. Now Escape closes the modal even from text inputs, unless a nested Vuetify overlay is open (.v-overlay--active — menus, FandomPicker, suggestion 3-dot menu) in which case that overlay handles its own Esc.
Provenance cards scroll at ~2.5: post cards wrap into .fc-prov__cards with max-height: 270px; overflow-y: auto; — keeps the Tags section at a consistent position regardless of provenance count; the half-clipped third card hints there's more below.
fb605af — anchored posts view inherits artist_id/platform on scroll
Operator-flagged: clicking a post title from the modal's Provenance card opens /posts?post_id=N&artist_id=A scoped correctly, but the bidirectional infinite-scroll then pulled UNFILTERED global posts. Root cause: loadAround/loadOlder/loadNewer in posts.js only sent {around, cursor, direction} — never the filters. Backend supported them on every path; frontend just wasn't passing.
Fix: loadAround(postId, filters) stores the snapshot; _aroundParams(extra) mixes the filters into all three calls; PostsView.loadAroundAndAnchor passes artist_id + platform from the route query.
94e7d20 — Logs button on failing-sources rollup rows
Each row in the "X sources are failing" rollup gets a Logs text button next to RETRY. Clicking it calls downloadsStore.loadLastForSource(source.id) — two-steps via GET /api/downloads?source_id=N&limit=1 (most recent event) then the existing loadOne(id) for the full detail payload — and opens the same DownloadDetailModal the event-row clicks use. Surfaces stdout/stderr/error/files-written/duration for troubleshooting without leaving the Downloads tab.
Test plan
Open the view modal, focus is in tag input by default → press Esc → modal closes.
Open the modal, click the kind: dropdown (or open suggestion 3-dot menu) → press Esc → only the dropdown/menu closes; modal stays.
Open an image with many provenance entries → cards scroll inside a fixed-height container; Tags panel anchored below.
Click a post title in Provenance → land in /posts?post_id=N&artist_id=A; scroll up/down and confirm only that artist's posts load.
In Subscriptions → Downloads, the failing-sources rollup shows a Logs button on each row; clicking opens the DownloadDetailModal for the most recent event of that source.
## Summary
Three coupled follow-up landings from the modal/posts/downloads UX session on 2026-06-01.
### `4c56cf1` — modal Esc fix + Provenance 2.5-card overflow
- **Esc from inside the tag input**: ImageViewer's keydown handler bailed early on `isTextEntry(ev.target)` for every key, so the autofocused TagAutocomplete trapped Escape. Now Escape closes the modal even from text inputs, unless a nested Vuetify overlay is open (`.v-overlay--active` — menus, FandomPicker, suggestion 3-dot menu) in which case that overlay handles its own Esc.
- **Provenance cards scroll at ~2.5**: post cards wrap into `.fc-prov__cards` with `max-height: 270px; overflow-y: auto;` — keeps the Tags section at a consistent position regardless of provenance count; the half-clipped third card hints there's more below.
### `fb605af` — anchored posts view inherits artist_id/platform on scroll
Operator-flagged: clicking a post title from the modal's Provenance card opens `/posts?post_id=N&artist_id=A` scoped correctly, but the bidirectional infinite-scroll then pulled UNFILTERED global posts. Root cause: `loadAround`/`loadOlder`/`loadNewer` in `posts.js` only sent `{around, cursor, direction}` — never the filters. Backend supported them on every path; frontend just wasn't passing.
Fix: `loadAround(postId, filters)` stores the snapshot; `_aroundParams(extra)` mixes the filters into all three calls; `PostsView.loadAroundAndAnchor` passes `artist_id` + `platform` from the route query.
### `94e7d20` — Logs button on failing-sources rollup rows
Each row in the "X sources are failing" rollup gets a `Logs` text button next to RETRY. Clicking it calls `downloadsStore.loadLastForSource(source.id)` — two-steps via `GET /api/downloads?source_id=N&limit=1` (most recent event) then the existing `loadOne(id)` for the full detail payload — and opens the same `DownloadDetailModal` the event-row clicks use. Surfaces stdout/stderr/error/files-written/duration for troubleshooting without leaving the Downloads tab.
## Test plan
- [ ] Open the view modal, focus is in tag input by default → press Esc → modal closes.
- [ ] Open the modal, click the kind: dropdown (or open suggestion 3-dot menu) → press Esc → only the dropdown/menu closes; modal stays.
- [ ] Open an image with many provenance entries → cards scroll inside a fixed-height container; Tags panel anchored below.
- [ ] Click a post title in Provenance → land in `/posts?post_id=N&artist_id=A`; scroll up/down and confirm only that artist's posts load.
- [ ] In Subscriptions → Downloads, the failing-sources rollup shows a `Logs` button on each row; clicking opens the DownloadDetailModal for the most recent event of that source.
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.
Operator-flagged 2026-06-01: clicking a Provenance post title from the
view modal opens the post in the posts feed scoped to that artist
(`/posts?post_id=N&artist_id=A`), but the older/newer infinite-scroll
loaded UNFILTERED global posts instead of staying in the artist's
stream. Root cause: the posts store's loadAround/loadOlder/loadNewer
sent only `{around, cursor, direction}` — never the `artist_id` /
`platform` filters. Backend `/api/posts` accepts them on every path
(post_feed_service.scroll filters via `Source.artist_id`); the
frontend just wasn't passing them.
Fix:
- `posts.js` `loadAround(postId, filters)` now takes the filter
snapshot and stores it. `_aroundParams(extra)` mixes the active
filters into around/cursor calls.
- `PostsView.vue` `loadAroundAndAnchor` passes `artist_id` +
`platform` from the route query.
Operator-asked 2026-06-01: each row in the "X sources are failing"
rollup needs a button to surface the last run's stdout/stderr/error
without leaving the Downloads tab to find the matching event row.
Wired:
- `downloadsStore.loadLastForSource(sourceId)` two-steps via
`GET /api/downloads?source_id=N&limit=1` (most recent event) then
the existing `loadOne(id)` for the full detail payload. Returns
null if no events exist (toast warns).
- `FailingSourcesCard` row: new text button `Logs` with
`mdi-text-box-search-outline`, per-row spinner via `logLoadingIds`,
emits `view-logs` with the source record.
- `DownloadsTab.onViewFailingLogs` is the handler — same
`DownloadDetailModal` instance the event-row clicks use.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Three coupled follow-up landings from the modal/posts/downloads UX session on 2026-06-01.
4c56cf1— modal Esc fix + Provenance 2.5-card overflowisTextEntry(ev.target)for every key, so the autofocused TagAutocomplete trapped Escape. Now Escape closes the modal even from text inputs, unless a nested Vuetify overlay is open (.v-overlay--active— menus, FandomPicker, suggestion 3-dot menu) in which case that overlay handles its own Esc..fc-prov__cardswithmax-height: 270px; overflow-y: auto;— keeps the Tags section at a consistent position regardless of provenance count; the half-clipped third card hints there's more below.fb605af— anchored posts view inherits artist_id/platform on scrollOperator-flagged: clicking a post title from the modal's Provenance card opens
/posts?post_id=N&artist_id=Ascoped correctly, but the bidirectional infinite-scroll then pulled UNFILTERED global posts. Root cause:loadAround/loadOlder/loadNewerinposts.jsonly sent{around, cursor, direction}— never the filters. Backend supported them on every path; frontend just wasn't passing.Fix:
loadAround(postId, filters)stores the snapshot;_aroundParams(extra)mixes the filters into all three calls;PostsView.loadAroundAndAnchorpassesartist_id+platformfrom the route query.94e7d20— Logs button on failing-sources rollup rowsEach row in the "X sources are failing" rollup gets a
Logstext button next to RETRY. Clicking it callsdownloadsStore.loadLastForSource(source.id)— two-steps viaGET /api/downloads?source_id=N&limit=1(most recent event) then the existingloadOne(id)for the full detail payload — and opens the sameDownloadDetailModalthe event-row clicks use. Surfaces stdout/stderr/error/files-written/duration for troubleshooting without leaving the Downloads tab.Test plan
/posts?post_id=N&artist_id=A; scroll up/down and confirm only that artist's posts load.Logsbutton on each row; clicking opens the DownloadDetailModal for the most recent event of that source.Operator-flagged 2026-06-01: clicking a Provenance post title from the view modal opens the post in the posts feed scoped to that artist (`/posts?post_id=N&artist_id=A`), but the older/newer infinite-scroll loaded UNFILTERED global posts instead of staying in the artist's stream. Root cause: the posts store's loadAround/loadOlder/loadNewer sent only `{around, cursor, direction}` — never the `artist_id` / `platform` filters. Backend `/api/posts` accepts them on every path (post_feed_service.scroll filters via `Source.artist_id`); the frontend just wasn't passing them. Fix: - `posts.js` `loadAround(postId, filters)` now takes the filter snapshot and stores it. `_aroundParams(extra)` mixes the active filters into around/cursor calls. - `PostsView.vue` `loadAroundAndAnchor` passes `artist_id` + `platform` from the route query.