From fc69758a921b58c9c8b6151916f273115bccc187 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 31 May 2026 12:00:23 +0200 Subject: [PATCH] feat(document): add server-computed hasTranscription to detail payload (#697) getDocumentById now populates a transient hasTranscription boolean so the document detail page can gate the transcription entry control at first paint (no client store, no full block fetch, no layout shift). Co-Authored-By: Claude Opus 4.8 --- .../familienarchiv/document/Document.java | 7 +++++++ .../document/DocumentService.java | 1 + .../document/DocumentServiceTest.java | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java b/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java index 7f702763..dc47f061 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java @@ -177,6 +177,13 @@ public class Document { @Builder.Default private Set trainingLabels = new HashSet<>(); + // Not persisted — computed per detail fetch so read-only users can tell at first + // paint whether there is a transcription to read (DocumentService.getDocumentById). + @Transient + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Builder.Default + private boolean hasTranscription = false; + // The `?v={thumbnailGeneratedAt}` cache-buster is load-bearing: the thumbnail // endpoint sends `Cache-Control: private, max-age=31536000, immutable` // (DocumentController.getDocumentThumbnail). `immutable` is only safe because diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java index 8108c997..934f3417 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -943,6 +943,7 @@ public class DocumentService { Document doc = documentRepository.findById(id) .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); tagService.resolveEffectiveColors(doc.getTags()); + doc.setHasTranscription(transcriptionBlockQueryService.hasBlocks(id)); return doc; } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java index 04b84fba..34b5ddf0 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -118,6 +118,26 @@ class DocumentServiceTest { assertThat(documentService.getDocumentById(id)).isEqualTo(doc); } + @Test + void getDocumentById_setsHasTranscriptionTrue_whenBlocksExist() { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Test").build(); + when(documentRepository.findById(id)).thenReturn(Optional.of(doc)); + when(transcriptionBlockQueryService.hasBlocks(id)).thenReturn(true); + + assertThat(documentService.getDocumentById(id).isHasTranscription()).isTrue(); + } + + @Test + void getDocumentById_setsHasTranscriptionFalse_whenNoBlocksExist() { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Test").build(); + when(documentRepository.findById(id)).thenReturn(Optional.of(doc)); + when(transcriptionBlockQueryService.hasBlocks(id)).thenReturn(false); + + assertThat(documentService.getDocumentById(id).isHasTranscription()).isFalse(); + } + // ─── updateDocument ─────────────────────────────────────────────────────── @Test