diff --git a/backend/app/routers/nextcloud.py b/backend/app/routers/nextcloud.py
index 1461088..bd53543 100644
--- a/backend/app/routers/nextcloud.py
+++ b/backend/app/routers/nextcloud.py
@@ -333,14 +333,18 @@ async def delete_nextcloud_source_root(
)
)
# Folders have a self-referential parent_id FK with no
- # ON DELETE rule. A flat DELETE of the whole subtree trips
- # `folders_parent_id_fkey` because postgres checks the
- # constraint per row, regardless of insertion / list order.
- # NULL the parent_id on every folder we're about to delete
- # first so the chain breaks cleanly.
+ # ON DELETE rule. NULL parent_id on every folder that points
+ # *into* our delete set — that includes children within this
+ # source root AND any folder under a different SourceRoot whose
+ # path happens to nest inside this one (e.g. a 'Leóns 1st Year'
+ # SourceRoot at `.../Taco and Muli - 2024 onward/Leóns 1st Year`
+ # has folder rows whose parent_id points at folder rows under
+ # 'Taco and Muli - 2024 onward'). Without this, deleting the
+ # outer SourceRoot trips folders_parent_id_fkey from the inner
+ # SourceRoot's still-live rows.
await db.execute(
update(Folder)
- .where(Folder.id.in_(folder_ids))
+ .where(Folder.parent_id.in_(folder_ids))
.values(parent_id=None)
)
await db.execute(delete(Folder).where(Folder.id.in_(folder_ids)))
diff --git a/frontend/src/components/dialogs/SettingsDialog.tsx b/frontend/src/components/dialogs/SettingsDialog.tsx
index 1c7c40e..0ff1c8e 100644
--- a/frontend/src/components/dialogs/SettingsDialog.tsx
+++ b/frontend/src/components/dialogs/SettingsDialog.tsx
@@ -1594,9 +1594,17 @@ function NextcloudIntegrationCard() {
variant="ghost"
disabled={removeRoot.isPending}
onClick={() => removeRoot.mutate(r.id)}
- title="Remove this library"
+ title={
+ removeRoot.isPending && removeRoot.variables === r.id
+ ? 'Removing — cascades through every photo + folder, may take a few seconds'
+ : 'Remove this library'
+ }
>
-
+ {removeRoot.isPending && removeRoot.variables === r.id ? (
+
+ ) : (
+
+ )}
))}