From 2bb8fb896837cf64874f5843e7027bf99ecde333 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 25 Apr 2026 16:14:53 +0200 Subject: [PATCH] fix(bulk-edit): align BulkEditEntry shape with backend DocumentBatchSummary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production bug — the backend serialises the document UUID as `id`, but BulkEditEntry typed it as `documentId`. The runtime cast in /documents/ bulk-edit/+page.svelte was a TypeScript lie: every `entry.documentId` became undefined, the SvelteMap collapsed all selections under the undefined key, and the PATCH fired with `documentIds: []` (which the controller correctly rejected with 400). Field semantics ACs could therefore never fire end-to-end. Renamed `BulkEditEntry.documentId` → `id`. The FileEntry built from each summary still carries both `id` (local map key) and `documentId` (PATCH payload) so the save handler is unchanged. Reported by Elicit (B1) on PR #331. Refs #225 Co-Authored-By: Claude Sonnet 4.6 --- .../document/BulkDocumentEditLayout.svelte | 16 ++++++++++------ .../BulkDocumentEditLayout.svelte.spec.ts | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte index d6f75d22..8aceb7c2 100644 --- a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte +++ b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte @@ -20,8 +20,13 @@ import type { components } from '$lib/generated/api'; type Person = components['schemas']['Person']; +// Mirrors the backend `DocumentBatchSummary` JSON shape one-to-one — the route +// passes the parsed `/api/documents/batch-metadata` response straight in, so +// the field names must match what the backend actually serializes (id, not +// documentId). The FileEntry built from each summary still uses both `id` and +// `documentId` so the save handler can drive the PATCH payload by UUID. export type BulkEditEntry = { - documentId: string; + id: string; title: string; pdfUrl: string; }; @@ -71,15 +76,14 @@ let archiveFolder = $state(''); // has already been resolved into `initialEditEntries`. if (mode === 'edit') { for (const entry of untrack(() => initialEditEntries)) { - const id = entry.documentId; // reuse documentId as the local FileEntry key - files.set(id, { - id, - documentId: entry.documentId, + files.set(entry.id, { + id: entry.id, + documentId: entry.id, title: entry.title, status: 'idle', previewUrl: entry.pdfUrl }); - if (!activeId) activeId = id; + if (!activeId) activeId = entry.id; } } diff --git a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte.spec.ts b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte.spec.ts index 1f96216f..ea7306e2 100644 --- a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte.spec.ts +++ b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte.spec.ts @@ -317,7 +317,7 @@ describe('BulkDocumentEditLayout', () => { describe('BulkDocumentEditLayout — mode="edit"', () => { const editEntry = (i: number) => ({ - documentId: `doc-${i}`, + id: `doc-${i}`, title: `Brief ${i}`, pdfUrl: `/api/documents/doc-${i}/file` });