fix: prune orphaned folder rows alongside photo rows
prune_missing_photos() previously only deleted Photo rows whose files were gone, leaving every folder row from the old library in the DB — which made the sidebar tree wildly out of sync with the on-disk structure (still showing /photos/2024/, /photos/2026/03/, etc. that no longer exist). Now also drops Folder rows whose path doesn't resolve under a mounted source root, with the same defensive "skip if source root unmounted" guard. The Settings orphan card surfaces both counts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -420,52 +420,59 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
)}
|
||||
|
||||
{/* Orphaned rows (files gone from disk) */}
|
||||
{missingStats && (missingStats.would_delete ?? 0) > 0 && (
|
||||
<div className="mt-3 rounded border border-star/40 bg-star/10 p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="text-xs">
|
||||
<div className="flex items-center gap-1.5 font-medium text-text">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-star" />
|
||||
{missingStats.would_delete} orphaned photo rows
|
||||
{missingStats &&
|
||||
((missingStats.would_delete ?? 0) > 0 ||
|
||||
(missingStats.would_delete_folders ?? 0) > 0) && (
|
||||
<div className="mt-3 rounded border border-star/40 bg-star/10 p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="text-xs">
|
||||
<div className="flex items-center gap-1.5 font-medium text-text">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-star" />
|
||||
Orphaned rows
|
||||
</div>
|
||||
<div className="mt-0.5 text-[10px] text-text-muted">
|
||||
{missingStats.would_delete ?? 0} photos and{' '}
|
||||
{missingStats.would_delete_folders ?? 0} folders
|
||||
point at paths that no longer exist on disk under a
|
||||
mounted source root. Usually means PHOTO_DIRS was
|
||||
repointed at a different library.
|
||||
{missingStats.skipped_unmounted > 0 && (
|
||||
<>
|
||||
{' '}
|
||||
{missingStats.skipped_unmounted} more rows are
|
||||
under unmounted roots and will not be touched.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-0.5 text-[10px] text-text-muted">
|
||||
Files no longer exist on disk under a mounted source
|
||||
root. Usually means PHOTO_DIRS was repointed at a
|
||||
different library.
|
||||
{missingStats.skipped_unmounted > 0 && (
|
||||
<>
|
||||
{' '}
|
||||
{missingStats.skipped_unmounted} more rows are
|
||||
under unmounted roots and will not be touched.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ActionButton
|
||||
loading={busy['prune-missing']}
|
||||
destructive
|
||||
onClick={() => {
|
||||
if (
|
||||
!confirm(
|
||||
`Delete ${missingStats.would_delete} photo rows whose files are missing? ` +
|
||||
'This cannot be undone.'
|
||||
<ActionButton
|
||||
loading={busy['prune-missing']}
|
||||
destructive
|
||||
onClick={() => {
|
||||
const photos = missingStats.would_delete ?? 0
|
||||
const folders = missingStats.would_delete_folders ?? 0
|
||||
if (
|
||||
!confirm(
|
||||
`Delete ${photos} photo rows and ${folders} folder rows whose paths are missing? ` +
|
||||
'This cannot be undone.'
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
runAction(
|
||||
'prune-missing',
|
||||
() => library.maintenance.pruneMissing(),
|
||||
'Orphans pruned',
|
||||
(r) => `${r.deleted ?? 0} rows deleted`
|
||||
)
|
||||
}}
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Prune
|
||||
</ActionButton>
|
||||
return
|
||||
runAction(
|
||||
'prune-missing',
|
||||
() => library.maintenance.pruneMissing(),
|
||||
'Orphans pruned',
|
||||
(r) =>
|
||||
`${r.deleted ?? 0} photos + ${r.deleted_folders ?? 0} folders deleted`
|
||||
)
|
||||
}}
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Prune
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Per-worker breakdown */}
|
||||
{workerStatus && workerStatus.workers.length > 0 && (
|
||||
|
||||
@@ -233,6 +233,8 @@ export interface RegenerateResult {
|
||||
export interface MissingStats {
|
||||
would_delete?: number
|
||||
deleted?: number
|
||||
would_delete_folders?: number
|
||||
deleted_folders?: number
|
||||
skipped_unmounted: number
|
||||
dry_run: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user