diff --git a/frontend/src/lib/components/DocumentRow.svelte b/frontend/src/lib/components/DocumentRow.svelte
index d4e2bd5c..2b656a65 100644
--- a/frontend/src/lib/components/DocumentRow.svelte
+++ b/frontend/src/lib/components/DocumentRow.svelte
@@ -4,13 +4,14 @@ import type { components } from '$lib/generated/api';
import { applyOffsets } from '$lib/search';
import { formatDate } from '$lib/utils/date';
import * as m from '$lib/paraglide/messages.js';
+import { bulkSelectionStore } from '$lib/stores/bulkSelection.svelte';
import ProgressRing from './ProgressRing.svelte';
import ContributorStack from './ContributorStack.svelte';
import DocumentThumbnail from './DocumentThumbnail.svelte';
type DocumentSearchItem = components['schemas']['DocumentSearchItem'];
-let { item }: { item: DocumentSearchItem } = $props();
+let { item, canWrite = false }: { item: DocumentSearchItem; canWrite?: boolean } = $props();
const doc = $derived(item.document);
const titleText = $derived(doc.title || doc.originalFilename);
@@ -55,6 +56,21 @@ function safeTagColor(color: string | null | undefined): string {
+
+ {#if canWrite}
+
+ {/if}
diff --git a/frontend/src/lib/components/DocumentRow.svelte.spec.ts b/frontend/src/lib/components/DocumentRow.svelte.spec.ts
index 274062a6..f7d0b92a 100644
--- a/frontend/src/lib/components/DocumentRow.svelte.spec.ts
+++ b/frontend/src/lib/components/DocumentRow.svelte.spec.ts
@@ -3,6 +3,7 @@ import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import { goto } from '$app/navigation';
import DocumentRow from './DocumentRow.svelte';
+import { bulkSelectionStore } from '$lib/stores/bulkSelection.svelte';
import type { components } from '$lib/generated/api';
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
@@ -10,6 +11,7 @@ vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
afterEach(() => {
cleanup();
vi.mocked(goto).mockClear();
+ bulkSelectionStore.clear();
});
type DocumentSearchItem = components['schemas']['DocumentSearchItem'];
@@ -265,6 +267,45 @@ describe('DocumentRow – tags', () => {
});
});
+// ─── Bulk-selection checkbox ─────────────────────────────────────────────────
+
+describe('DocumentRow – bulk selection checkbox', () => {
+ it('does not render the checkbox when canWrite is false', async () => {
+ render(DocumentRow, { item: makeItem(), canWrite: false });
+ await expect.element(page.getByTestId('bulk-select-checkbox')).not.toBeInTheDocument();
+ });
+
+ it('renders the checkbox when canWrite is true', async () => {
+ render(DocumentRow, { item: makeItem(), canWrite: true });
+ await expect.element(page.getByTestId('bulk-select-checkbox')).toBeInTheDocument();
+ });
+
+ it('checkbox aria-label includes the document title', async () => {
+ const item = makeItem({ document: { ...makeItem().document, title: 'Brief an Anna' } });
+ render(DocumentRow, { item, canWrite: true });
+ await expect
+ .element(page.getByRole('checkbox', { name: /Brief an Anna/i }))
+ .toBeInTheDocument();
+ });
+
+ it('toggling the checkbox calls bulkSelectionStore.toggle', async () => {
+ const item = makeItem({ document: { ...makeItem().document, id: 'doc-42' } });
+ render(DocumentRow, { item, canWrite: true });
+ expect(bulkSelectionStore.has('doc-42')).toBe(false);
+
+ document.querySelector('input[type="checkbox"]')?.click();
+
+ await expect.poll(() => bulkSelectionStore.has('doc-42')).toBe(true);
+ });
+
+ it('checked state mirrors the store', async () => {
+ bulkSelectionStore.add('doc-99');
+ const item = makeItem({ document: { ...makeItem().document, id: 'doc-99' } });
+ render(DocumentRow, { item, canWrite: true });
+ await expect.element(page.getByRole('checkbox')).toBeChecked();
+ });
+});
+
// ─── ProgressRing & ContributorStack ─────────────────────────────────────────
describe('DocumentRow – progress ring and contributors', () => {
diff --git a/frontend/src/routes/DocumentList.svelte b/frontend/src/routes/DocumentList.svelte
index 74478c66..d982a46a 100644
--- a/frontend/src/routes/DocumentList.svelte
+++ b/frontend/src/routes/DocumentList.svelte
@@ -119,7 +119,7 @@ function groupByReceiver(docItems: DocumentSearchItem[]) {
{#each group.items as item (group.label + '-' + item.document.id)}
-
+
{/each}
diff --git a/frontend/src/routes/enrich/+page.server.ts b/frontend/src/routes/enrich/+page.server.ts
index 7cb4ea02..65d86b8f 100644
--- a/frontend/src/routes/enrich/+page.server.ts
+++ b/frontend/src/routes/enrich/+page.server.ts
@@ -19,5 +19,5 @@ export async function load({
const documents = result.response.ok ? (result.data ?? []) : [];
- return { documents };
+ return { documents, canWrite };
}
diff --git a/frontend/src/routes/enrich/+page.svelte b/frontend/src/routes/enrich/+page.svelte
index b2130465..e6457afd 100644
--- a/frontend/src/routes/enrich/+page.svelte
+++ b/frontend/src/routes/enrich/+page.svelte
@@ -1,11 +1,13 @@