feat(provenance+tags): collapse provenance descriptions; purge retired-kind tags
Operator-flagged 2026-05-28, two asks. **1. Provenance posts ate the panel.** Each post card rendered its full description (180px scroll box) inline, so a few posts pushed everything else off-screen. ProvenancePanel now collapses the description by default behind a per-post "Show description ▾ / Hide ▴" toggle (state keyed by provenance_id, reset when the viewed image changes). Cards stay compact — platform/date/title/meta/actions — and the operator expands only the descriptions they want. **2. Purge tags of retired/system kinds.** The IR migration left `archive`/`post`/`artist`-kind tags (e.g. `BlenderKnight:Hannah_BJ_Loops`) that FC no longer creates — the tag input only makes character/fandom/series/general, and provenance + artists are their own systems now. (meta/rating were already hard-deleted by alembic 0023.) - cleanup_service.purge_tags_by_kind(kinds=PURGEABLE_TAG_KINDS) — counts (dry_run) or deletes tags whose kind ∈ (archive, post, artist). CASCADE clears the image_tag / alias / allowlist / etc. rows. - POST /api/admin/tags/purge-retired-kinds (Tier-A, dry-run preview returns per-kind counts + sample names — the preview IS the verification of exactly what'll be deleted before committing). - Tag Maintenance card gets a second section: "Preview retired-kind tags" → per-kind breakdown + sample → "Delete N retired-kind tag(s)". Tests: dry-run counts by kind (general survives), commit deletes only the retired kinds (general + character survive, retired count → 0). NOTE: a dry-run preview will show exactly which kinds/counts are present. If the operator's noisy tags turn out to be `general` (e.g. an IR `source:patreon` that fell back to general during migration), they won't be caught by the kind purge — the preview makes that visible so we can decide on a name-based pass separately.
This commit is contained in:
@@ -37,9 +37,13 @@
|
||||
<a href="#" @click.prevent="openPost(e.post.id)">
|
||||
View images from this post
|
||||
</a>
|
||||
<a
|
||||
v-if="e.post.description_html"
|
||||
href="#" @click.prevent="toggleDesc(e.provenance_id)"
|
||||
>{{ expanded[e.provenance_id] ? 'Hide description ▴' : 'Show description ▾' }}</a>
|
||||
</div>
|
||||
<div
|
||||
v-if="e.post.description_html"
|
||||
v-if="e.post.description_html && expanded[e.provenance_id]"
|
||||
class="fc-prov__desc" v-html="e.post.description_html"
|
||||
/>
|
||||
</article>
|
||||
@@ -74,7 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, watch } from 'vue'
|
||||
import { computed, reactive, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useModalStore } from '../../stores/modal.js'
|
||||
import { useProvenanceStore } from '../../stores/provenance.js'
|
||||
@@ -84,6 +88,16 @@ const modal = useModalStore()
|
||||
const prov = useProvenanceStore()
|
||||
const router = useRouter()
|
||||
|
||||
// Per-post description collapse state (keyed by provenance_id). Default
|
||||
// collapsed so multiple posts don't each eat ~180px of the panel — the
|
||||
// operator flagged the descriptions consuming a lot of real estate
|
||||
// 2026-05-28. Reset when the viewed image changes.
|
||||
const expanded = reactive({})
|
||||
function toggleDesc(id) { expanded[id] = !expanded[id] }
|
||||
watch(() => modal.currentImageId, () => {
|
||||
for (const k of Object.keys(expanded)) delete expanded[k]
|
||||
})
|
||||
|
||||
watch(
|
||||
() => modal.currentImageId,
|
||||
(id) => { if (id != null) prov.loadForImage(id) },
|
||||
|
||||
@@ -44,6 +44,46 @@
|
||||
@click="onCommit"
|
||||
>Delete {{ preview.count }} unused tag(s)</v-btn>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-5" />
|
||||
|
||||
<p class="fc-muted text-body-2 mb-4">
|
||||
Purge tags with retired/system kinds FC no longer uses
|
||||
(<code>archive</code>, <code>post</code>, <code>artist</code>) —
|
||||
IR-migration leftovers like
|
||||
<code>BlenderKnight:Hannah_BJ_Loops</code>. Provenance and
|
||||
artists are their own systems now, so these tag rows are pure
|
||||
noise. Removes them from every image.
|
||||
</p>
|
||||
|
||||
<v-btn
|
||||
color="accent" variant="flat" rounded="pill"
|
||||
prepend-icon="mdi-magnify"
|
||||
:loading="loadingKindPreview"
|
||||
class="mb-3"
|
||||
@click="onKindPreview"
|
||||
>Preview retired-kind tags</v-btn>
|
||||
|
||||
<div v-if="kindPreview">
|
||||
<p class="text-body-2 mb-2">
|
||||
<strong>{{ kindPreview.count }}</strong> tag(s) of retired kinds.
|
||||
<span v-for="(n, k) in kindPreview.by_kind" :key="k" class="fc-muted">
|
||||
{{ k }}: {{ n }}
|
||||
</span>
|
||||
</p>
|
||||
<div v-if="kindPreview.sample_names?.length" class="fc-name-grid mb-3">
|
||||
<span v-for="n in kindPreview.sample_names" :key="n" class="fc-name">
|
||||
{{ n }}
|
||||
</span>
|
||||
</div>
|
||||
<v-btn
|
||||
color="error" variant="flat" rounded="pill"
|
||||
prepend-icon="mdi-delete-sweep"
|
||||
:disabled="!kindPreview.count"
|
||||
:loading="kindCommitting"
|
||||
@click="onKindCommit"
|
||||
>Delete {{ kindPreview.count }} retired-kind tag(s)</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -57,6 +97,9 @@ const store = useAdminStore()
|
||||
const preview = ref(null)
|
||||
const loadingPreview = ref(false)
|
||||
const committing = ref(false)
|
||||
const kindPreview = ref(null)
|
||||
const loadingKindPreview = ref(false)
|
||||
const kindCommitting = ref(false)
|
||||
|
||||
async function onPreview() {
|
||||
loadingPreview.value = true
|
||||
@@ -76,6 +119,25 @@ async function onCommit() {
|
||||
committing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onKindPreview() {
|
||||
loadingKindPreview.value = true
|
||||
try {
|
||||
kindPreview.value = await store.purgeRetiredKindTags({ dryRun: true })
|
||||
} finally {
|
||||
loadingKindPreview.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onKindCommit() {
|
||||
kindCommitting.value = true
|
||||
try {
|
||||
await store.purgeRetiredKindTags({ dryRun: false })
|
||||
kindPreview.value = { count: 0, by_kind: {}, sample_names: [] }
|
||||
} finally {
|
||||
kindCommitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user