From 0412107d86f30d00ac4c650b0fd3e0265104f0e0 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 11 May 2026 11:38:18 +0200 Subject: [PATCH] fix(documents): tighten archive/restore idempotency + document fire-and-forget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-ups from Task 6 code review: 1. applyEntityArchivedSuffix short-circuits when the folder is already archived — prevents archivedAt drift on backfill replay. 2. applyEntityRestoredSuffix short-circuits when the folder was never archived — matches the docstring's "no-op" claim. 3. Inline comment on archiveClient's fire-and-forget hook documents why Task 6 uses void (archive UI doesn't depend on folder sync) while Task 5 uses await (rename should be visible to the next read). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/clients.service.ts | 4 ++++ src/lib/services/document-folders.service.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/lib/services/clients.service.ts b/src/lib/services/clients.service.ts index 8686cd46..ad305eb5 100644 --- a/src/lib/services/clients.service.ts +++ b/src/lib/services/clients.service.ts @@ -557,6 +557,10 @@ export async function archiveClient(id: string, portId: string, meta: AuditMeta) await softDelete(clients, clients.id, id); + // fire-and-forget: archive UI does not depend on the folder suffix + // being stamped before the HTTP response returns. Task 5 (rename + // hook) uses await because the rename should be visible to the + // next read; archive does not. void applyEntityArchivedSuffix(portId, 'client', id).catch((err) => { logger.warn({ err, clientId: id }, 'Failed to apply archived suffix to client folder'); }); diff --git a/src/lib/services/document-folders.service.ts b/src/lib/services/document-folders.service.ts index f23917bb..1ccabdda 100644 --- a/src/lib/services/document-folders.service.ts +++ b/src/lib/services/document-folders.service.ts @@ -600,6 +600,7 @@ export async function applyEntityArchivedSuffix( const newName = folder.name.endsWith(ARCHIVED_SUFFIX) ? folder.name : `${folder.name}${ARCHIVED_SUFFIX}`; + if (newName === folder.name && folder.archivedAt) return; // Already archived, no-op. await db .update(documentFolders) .set({ name: newName, archivedAt: new Date(), updatedAt: new Date() }) @@ -628,6 +629,7 @@ export async function applyEntityRestoredSuffix( const newName = folder.name.endsWith(ARCHIVED_SUFFIX) ? folder.name.slice(0, -ARCHIVED_SUFFIX.length) : folder.name; + if (newName === folder.name && !folder.archivedAt) return; // Wasn't archived, no-op. await db .update(documentFolders) .set({ name: newName, archivedAt: null, updatedAt: new Date() })