feat(settings): tidy Cleanup tab into sectioned compact tiles (pass 1)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m14s

The Cleanup + Maintenance sections had ~17 full-width stacked cards with long
descriptions — a hunt to scan. Operator wants compact, sectioned, scannable tiles
(2026-06-18: keep both tabs, group inside, compact tiles in a grid).

New common/MaintenanceTile.vue: a compact expandable tile (icon + short title +
one-line blurb collapsed; click the header to expand the full controls/preview/
result inline; keyboard-accessible button + focus ring;  tints the
icon,  keeps a running task expanded).

Cleanup tab (this pass) restructured into 3 sections — Import-filter audits
(Min dimensions, Transparency, Single-color) / Duplicates & posts (Bare posts,
Duplicate posts, Deduplicate videos, Gated-post previews) / Tags (Unused, Legacy,
Reset content tagging, Standardize casing) — each a responsive grid of tiles.
PostMaintenanceCard split into 2 tiles, TagMaintenanceCard into 4. Moved
VideoDedupCard + GatedPurgeCard from the Maintenance tab here (both are
destructive content cleanup). All card logic unchanged — only the chrome.

Maintenance tab tiling is pass 2 (TODO noted in MaintenancePanel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 23:48:18 -04:00
parent eff64275fc
commit 3b435dc0ba
10 changed files with 697 additions and 533 deletions
@@ -3,54 +3,57 @@
duration + aspect (the same clip re-encoded / pulled from multiple
sources). Preview first, then apply (destructive: removes the redundant
copies, keeping the highest-resolution one). -->
<v-card>
<v-card-title>Deduplicate videos</v-card-title>
<v-card-text>
<p class="text-body-2 mb-3">
Finds videos of the same artist that are the same content across
re-encodes (matching duration + aspect ratio) and keeps the
highest-resolution copy. <strong>Preview</strong> first to see what would
be removed; <strong>Apply</strong> re-points each post to the kept copy,
then deletes the redundant videos and their files. The first run also
computes durations for older videos, so it may take a while.
</p>
<MaintenanceTile
icon="mdi-content-duplicate"
title="Deduplicate videos"
blurb="Collapse re-encoded duplicate videos, keeping the best copy."
destructive
:open="applying || previewing"
>
<p class="text-body-2 mb-3">
Finds videos of the same artist that are the same content across
re-encodes (matching duration + aspect ratio) and keeps the
highest-resolution copy. <strong>Preview</strong> first to see what would
be removed; <strong>Apply</strong> re-points each post to the kept copy,
then deletes the redundant videos and their files. The first run also
computes durations for older videos, so it may take a while.
</p>
<div class="d-flex align-center flex-wrap" style="gap: 12px;">
<v-btn
color="primary" variant="tonal" rounded="pill"
:loading="previewing" :disabled="applying" @click="preview"
>
<v-icon start>mdi-magnify</v-icon> Preview
</v-btn>
<v-btn
color="error" rounded="pill"
:loading="applying"
:disabled="previewing || !canApply"
@click="confirmOpen = true"
>
<v-icon start>mdi-content-duplicate</v-icon> Apply
</v-btn>
</div>
<v-alert
v-if="summary" :type="summaryType" variant="tonal" class="mt-4"
density="comfortable"
<div class="d-flex align-center flex-wrap" style="gap: 12px;">
<v-btn
color="primary" variant="tonal" rounded="pill"
:loading="previewing" :disabled="applying" @click="preview"
>
<span v-if="applied">
Removed {{ summary.deleted }} redundant video(s) across
{{ summary.groups }} group(s); re-pointed {{ summary.relinked_posts }}
post link(s); reclaimed {{ humanBytes(summary.reclaim_bytes) }}.
</span>
<span v-else-if="summary.redundant > 0">
{{ summary.redundant }} redundant video(s) across {{ summary.groups }}
group(s) {{ humanBytes(summary.reclaim_bytes) }} reclaimable. Click
<strong>Apply</strong> to remove them (keeping the best copy).
</span>
<span v-else>No duplicate videos found.</span>
</v-alert>
<v-icon start>mdi-magnify</v-icon> Preview
</v-btn>
<v-btn
color="error" rounded="pill"
:loading="applying"
:disabled="previewing || !canApply"
@click="confirmOpen = true"
>
<v-icon start>mdi-content-duplicate</v-icon> Apply
</v-btn>
</div>
<QueueStatusBar queue="maintenance_long" queue-label="Maintenance" />
</v-card-text>
<v-alert
v-if="summary" :type="summaryType" variant="tonal" class="mt-4"
density="comfortable"
>
<span v-if="applied">
Removed {{ summary.deleted }} redundant video(s) across
{{ summary.groups }} group(s); re-pointed {{ summary.relinked_posts }}
post link(s); reclaimed {{ humanBytes(summary.reclaim_bytes) }}.
</span>
<span v-else-if="summary.redundant > 0">
{{ summary.redundant }} redundant video(s) across {{ summary.groups }}
group(s) {{ humanBytes(summary.reclaim_bytes) }} reclaimable. Click
<strong>Apply</strong> to remove them (keeping the best copy).
</span>
<span v-else>No duplicate videos found.</span>
</v-alert>
<QueueStatusBar queue="maintenance_long" queue-label="Maintenance" />
<v-dialog v-model="confirmOpen" max-width="440">
<v-card>
@@ -68,13 +71,14 @@
</v-card-actions>
</v-card>
</v-dialog>
</v-card>
</MaintenanceTile>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useMaintenanceTask } from '../../composables/useMaintenanceTask.js'
import MaintenanceTile from '../common/MaintenanceTile.vue'
import QueueStatusBar from './QueueStatusBar.vue'
const confirmOpen = ref(false)