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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-31 12:00:23 +02:00
committed by marcel
parent f55efda0d2
commit fc69758a92
3 changed files with 28 additions and 0 deletions

View File

@@ -177,6 +177,13 @@ public class Document {
@Builder.Default @Builder.Default
private Set<TrainingLabel> trainingLabels = new HashSet<>(); private Set<TrainingLabel> 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 // The `?v={thumbnailGeneratedAt}` cache-buster is load-bearing: the thumbnail
// endpoint sends `Cache-Control: private, max-age=31536000, immutable` // endpoint sends `Cache-Control: private, max-age=31536000, immutable`
// (DocumentController.getDocumentThumbnail). `immutable` is only safe because // (DocumentController.getDocumentThumbnail). `immutable` is only safe because

View File

@@ -943,6 +943,7 @@ public class DocumentService {
Document doc = documentRepository.findById(id) Document doc = documentRepository.findById(id)
.orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id)); .orElseThrow(() -> DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "Document not found: " + id));
tagService.resolveEffectiveColors(doc.getTags()); tagService.resolveEffectiveColors(doc.getTags());
doc.setHasTranscription(transcriptionBlockQueryService.hasBlocks(id));
return doc; return doc;
} }

View File

@@ -118,6 +118,26 @@ class DocumentServiceTest {
assertThat(documentService.getDocumentById(id)).isEqualTo(doc); 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 ─────────────────────────────────────────────────────── // ─── updateDocument ───────────────────────────────────────────────────────
@Test @Test