feat: preserve tabs on sync disconnect/reconnect

Disconnecting a synced list (Google/Outlook/Synology) now uses a
soft-disconnect: tasks are soft-deleted and the external link is
cleared, but the SomedayList record is kept with its tab assignment
intact. When the user reconnects, the import route finds the list by
title, re-links the external provider, and restores tasks (clearing
deletedAt). Tabs survive the full disconnect/reconnect cycle without
the user needing to drag lists back to tabs.

Also clears deletedAt on task re-import so previously disconnected
tasks become visible again on reconnect.

v1.89.0
This commit is contained in:
mARTin 2026-04-06 18:07:03 +02:00
parent d8d1003e78
commit 0a84bf3114
4 changed files with 22 additions and 5 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.88.2", "version": "1.89.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -121,6 +121,8 @@ export async function DELETE(request: NextRequest) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const id = searchParams.get('id'); const id = searchParams.get('id');
// tasksOnly=true: soft-disconnect (keep list record with tab, just remove tasks + external link)
const tasksOnly = searchParams.get('tasksOnly') === 'true';
if (!id) { if (!id) {
return NextResponse.json( return NextResponse.json(
@ -147,6 +149,16 @@ export async function DELETE(request: NextRequest) {
data: { deletedAt: new Date(), somedayListId: null } data: { deletedAt: new Date(), somedayListId: null }
}); });
if (tasksOnly) {
// Soft-disconnect: keep list record (preserves tab assignment) but clear external link
await prisma.somedayList.update({
where: { id },
data: { externalId: null, externalProvider: null, lastSyncedAt: null }
});
notifyUser(userId, "list-changed", { action: "disconnected" });
return NextResponse.json({ success: true, kept: true });
}
await prisma.somedayList.delete({ await prisma.somedayList.delete({
where: { id } where: { id }
}); });

View File

@ -293,7 +293,8 @@ export async function POST(req: NextRequest) {
where: { id: existingTask.id }, where: { id: existingTask.id },
data: { data: {
somedayListId: somedayList.id, somedayListId: somedayList.id,
lastSyncedAt: new Date() lastSyncedAt: new Date(),
deletedAt: null, // clear soft-delete from a previous disconnect
} }
}); });
externalToLocalId.set(task.externalId, existingTask.id); externalToLocalId.set(task.externalId, existingTask.id);

View File

@ -3672,10 +3672,13 @@ export default function WeeklyView() {
); );
if (!existing) { setUnsyncConfirm(null); return; } if (!existing) { setUnsyncConfirm(null); return; }
try { try {
const res = await fetch(`/api/someday-lists?id=${existing.id}`, { // tasksOnly=true: soft-disconnect — removes tasks but keeps list record with its tab assignment
// so when the user reconnects, the tab is restored automatically without re-dragging
const res = await fetch(`/api/someday-lists?id=${existing.id}&tasksOnly=true`, {
method: "DELETE", method: "DELETE",
}); });
if (res.ok) { if (res.ok) {
// Remove from local state (clean UI); DB record is kept for tab restoration on reconnect
setSomedayLists((prev) => prev.filter((l) => l.id !== existing.id)); setSomedayLists((prev) => prev.filter((l) => l.id !== existing.id));
setImportStatusMsg({ setImportStatusMsg({
type: "success", type: "success",
@ -3683,7 +3686,7 @@ export default function WeeklyView() {
}); });
} }
} catch (error) { } catch (error) {
console.error("Failed to delete list", error); console.error("Failed to unsync list", error);
setImportStatusMsg({ setImportStatusMsg({
type: "error", type: "error",
text: "Failed to stop syncing list.", text: "Failed to stop syncing list.",
@ -3717,7 +3720,8 @@ export default function WeeklyView() {
); );
if (!existing) continue; if (!existing) continue;
try { try {
const res = await fetch(`/api/someday-lists?id=${existing.id}`, { // tasksOnly=true: soft-disconnect — keeps list record with tab for reconnect restoration
const res = await fetch(`/api/someday-lists?id=${existing.id}&tasksOnly=true`, {
method: "DELETE", method: "DELETE",
}); });
if (res.ok) { if (res.ok) {