Files
FabledCurator/frontend/src/views/GalleryView.vue
T
bvandeusen ecac6c4bda
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 24s
CI / frontend-build (push) Successful in 27s
CI / intimp (push) Successful in 3m49s
CI / intapi (push) Successful in 7m57s
CI / intcore (push) Successful in 8m46s
fix(audit-g5): centroid version DB-as-truth + modal as overlay
Closes the last two findings from the 2026-06-02 audit (G5.1 + G5.4).

G5.1 — Centroid version no longer drifts:

CentroidService now reads MLSettings.embedder_model_version (the DB
row tag_and_embed already writes from) for both the centroid model-
version stamp and the drift-detection comparison. Previously the
centroid sites imported MODEL_VERSION from env, so the version stamped
on centroids could disagree with the version stamped on the embeddings
they were built from. By construction those now match, so list_drifted
won't silently miss the env-vs-DB drift case.

embedder.py keeps MODEL_VERSION as an env-driven constant for the
actual model loader — that's a different concern (which weights are
loaded) from the version-stamp that gets persisted alongside data.

G5.4 — Modal is a Pinia-only overlay:

The previous URL↔modal sync in GalleryView and ArtistGalleryTab
leaked the modal across route changes (RouterLink to /artist/<slug>
left the modal mounted on top of the new route) and re-opened it
on history back/forward with stale ?image=N entries.

Now: openImage() just calls modal.open(id) — no URL push.
GalleryView's dead closeImage helper is deleted. A route.name
watcher in App.vue closes the modal whenever the route changes,
which auto-fixes RouterLink-in-modal and back/forward.

Backward-compat: ?image=N is still honored on initial mount as a
one-shot deep-link opener, then router.replace strips the query so
the URL doesn't re-trigger and no extra history entry is added.
Existing bookmarks / shared URLs keep working; new opens stay
Pinia-only.
2026-06-02 18:28:57 -04:00

97 lines
3.2 KiB
Vue

<template>
<v-container fluid class="pt-2 pb-6">
<Teleport to="#fc-nav-actions">
<v-btn
:color="sel.isSelectMode ? 'accent' : undefined"
:variant="sel.isSelectMode ? 'flat' : 'tonal'"
size="small"
@click="sel.isSelectMode ? sel.exitSelectMode() : sel.enterSelectMode()"
>{{ sel.isSelectMode ? 'Done' : 'Select' }}</v-btn>
</Teleport>
<div class="fc-gallery-layout">
<div class="fc-gallery-layout__main">
<PostInfoHeader />
<EmptyState v-if="store.isEmpty" />
<GalleryGrid v-else @open="openImage" />
</div>
<TimelineSidebar v-if="store.images.length > 0" class="fc-gallery-layout__sidebar" />
</div>
<BulkEditorPanel />
</v-container>
</template>
<script setup>
import { onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useGalleryStore } from '../stores/gallery.js'
import { useModalStore } from '../stores/modal.js'
import GalleryGrid from '../components/gallery/GalleryGrid.vue'
import TimelineSidebar from '../components/gallery/TimelineSidebar.vue'
import EmptyState from '../components/gallery/EmptyState.vue'
import PostInfoHeader from '../components/gallery/PostInfoHeader.vue'
import BulkEditorPanel from '../components/gallery/BulkEditorPanel.vue'
import { useGallerySelectionStore } from '../stores/gallerySelection.js'
const store = useGalleryStore()
const modal = useModalStore()
const sel = useGallerySelectionStore()
const router = useRouter()
const route = useRoute()
onMounted(async () => {
const postId = parseInt(route.query.post_id, 10)
const tagId = parseInt(route.query.tag_id, 10)
if (!isNaN(postId)) store.setPostFilter(postId)
else if (!isNaN(tagId)) store.setTagFilter(tagId)
await store.loadInitial()
await store.loadTimeline()
// Audit 2026-06-02: modal is now a Pinia-only overlay (see
// G5.4 design). The previous URL↔modal sync leaked the modal
// across route changes and re-opened it on history back/forward.
// We still honor `?image=N` as a one-shot deep-link opener so
// existing bookmarks / shared URLs keep working; we strip the
// query immediately via router.replace so the URL doesn't
// re-trigger and no history entry is added.
const initial = parseInt(route.query.image, 10)
if (!isNaN(initial)) {
modal.open(initial)
const q = { ...route.query }
delete q.image
router.replace({ query: q })
}
})
watch(() => route.query.tag_id, (q) => {
sel.clear() // result set changed — selected ids are no longer valid
const tagId = parseInt(q, 10)
store.setTagFilter(isNaN(tagId) ? null : tagId)
})
watch(() => route.query.post_id, (q) => {
sel.clear() // result set changed — selected ids are no longer valid
const postId = parseInt(q, 10)
store.setPostFilter(isNaN(postId) ? null : postId)
})
function openImage(id) {
modal.open(id)
}
</script>
<style scoped>
.fc-gallery-layout {
display: flex;
gap: 16px;
align-items: flex-start;
}
.fc-gallery-layout__main { flex: 1; min-width: 0; }
.fc-gallery-layout__sidebar { flex-shrink: 0; }
@media (max-width: 900px) {
.fc-gallery-layout { flex-direction: column-reverse; }
.fc-gallery-layout__sidebar { width: 100%; max-height: 200px; }
}
</style>