Compare commits

...

4 Commits

Author SHA1 Message Date
Marcel
8c7f3b2e4e test(person-mention): move i18n test to its own describe block
Some checks failed
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / Unit & Component Tests (pull_request) Failing after 3m34s
CI / OCR Service Tests (pull_request) Successful in 37s
CI / Backend Unit Tests (pull_request) Failing after 3m10s
Move `transcription_block_placeholder contains @ mention trigger` out of
`describe('PersonMentionEditor — placeholder behavior')` into a new
`describe('PersonMentionEditor — i18n message content')` block so each
describe group has a single, clear responsibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:16:01 +02:00
Marcel
52178c2f5b refactor(training): extract kurrentLabels helper + clarify query comments
Extract repeated `new java.util.HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION))`
into a `kurrentLabels()` helper in TrainingBlockQueryTest and add `import java.util.HashSet`.

Add clarifying comments on the two person-scoped queries in TranscriptionBlockRepository
explaining that they use `MEMBER OF d.trainingLabels` — aligned with the pre-existing
`findEligibleKurrentBlocks()` pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 15:15:33 +02:00
Marcel
54b4b96411 feat(person-mention): update transcription placeholder with @mention discoverability hint
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m27s
CI / OCR Service Tests (pull_request) Successful in 42s
CI / Backend Unit Tests (pull_request) Failing after 3m9s
CI / Unit & Component Tests (push) Failing after 3m25s
CI / OCR Service Tests (push) Successful in 30s
CI / Backend Unit Tests (push) Failing after 3m8s
Replaces the generic "Type text here..." placeholder in TranscriptionBlock
with copy that teaches the @Name trigger inline (Leonie Voss design review,
issue #370). No new DOM, no new i18n keys — just the three existing
`transcription_block_placeholder` strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 10:35:04 +02:00
Marcel
c905b81fd3 fix(training): use KURRENT_RECOGNITION label for sender-based block queries
Some checks failed
CI / Unit & Component Tests (push) Failing after 3m32s
CI / OCR Service Tests (push) Successful in 1m2s
CI / Backend Unit Tests (push) Failing after 3m20s
CI / Unit & Component Tests (pull_request) Failing after 3m31s
CI / OCR Service Tests (pull_request) Successful in 38s
CI / Backend Unit Tests (pull_request) Failing after 3m4s
scriptType is only set after OCR runs, which can't happen before we have
a trained model. Both sender-based queries now filter on the training label
instead, consistent with findEligibleKurrentBlocks.

Also adds missing test coverage for findManualKurrentBlocksByPerson and
countManualKurrentBlocksByPerson (4 cases + count parity check).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 10:15:25 +02:00
6 changed files with 118 additions and 6 deletions

View File

@@ -60,21 +60,25 @@ public interface TranscriptionBlockRepository extends JpaRepository<Transcriptio
""")
List<TranscriptionBlock> findSegmentationBlocks();
// Uses 'KURRENT_RECOGNITION' MEMBER OF d.trainingLabels — aligned with findEligibleKurrentBlocks()
// which already used this form (changed from d.scriptType = 'KURRENT' in the original queries).
@Query("""
SELECT COUNT(b) FROM TranscriptionBlock b
JOIN Document d ON d.id = b.documentId
WHERE b.source = 'MANUAL'
AND d.sender.id = :personId
AND d.scriptType = 'HANDWRITING_KURRENT'
AND 'KURRENT_RECOGNITION' MEMBER OF d.trainingLabels
""")
long countManualKurrentBlocksByPerson(@Param("personId") UUID personId);
// Uses 'KURRENT_RECOGNITION' MEMBER OF d.trainingLabels — aligned with findEligibleKurrentBlocks()
// which already used this form (changed from d.scriptType = 'KURRENT' in the original queries).
@Query("""
SELECT b FROM TranscriptionBlock b
JOIN Document d ON d.id = b.documentId
WHERE b.source = 'MANUAL'
AND d.sender.id = :personId
AND d.scriptType = 'HANDWRITING_KURRENT'
AND 'KURRENT_RECOGNITION' MEMBER OF d.trainingLabels
""")
List<TranscriptionBlock> findManualKurrentBlocksByPerson(@Param("personId") UUID personId);
}

View File

@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.config.FlywayConfig;
import org.raddatz.familienarchiv.model.*;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
@@ -24,6 +25,7 @@ class TrainingBlockQueryTest {
@Autowired TranscriptionBlockRepository blockRepository;
@Autowired DocumentRepository documentRepository;
@Autowired AnnotationRepository annotationRepository;
@Autowired PersonRepository personRepository;
private UUID kurrentDocId;
private UUID typewriterDocId;
@@ -36,7 +38,7 @@ class TrainingBlockQueryTest {
.title("Kurrent Brief")
.originalFilename("kurrent.pdf")
.status(DocumentStatus.UPLOADED)
.trainingLabels(new java.util.HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION)))
.trainingLabels(kurrentLabels())
.build());
kurrentDocId = kurrentDoc.getId();
@@ -111,8 +113,105 @@ class TrainingBlockQueryTest {
assertThat(result).hasSize(2);
}
// ─── sender-based queries ─────────────────────────────────────────────────
@Test
void findManualKurrentBlocksByPerson_includesBlockFromKurrentLabelledDocument() {
Person sender = personRepository.save(Person.builder().firstName("Karl").lastName("Test").build());
Document doc = documentRepository.save(Document.builder()
.title("Brief von Karl")
.originalFilename("karl.pdf")
.status(DocumentStatus.UPLOADED)
.sender(sender)
.trainingLabels(kurrentLabels())
.build());
UUID annId = annotationRepository.save(annotation(doc.getId())).getId();
blockRepository.save(block(doc.getId(), annId, BlockSource.MANUAL, false));
List<TranscriptionBlock> result = blockRepository.findManualKurrentBlocksByPerson(sender.getId());
assertThat(result).hasSize(1);
}
@Test
void findManualKurrentBlocksByPerson_excludesDocumentWithoutKurrentLabel() {
Person sender = personRepository.save(Person.builder().firstName("Karl").lastName("Test").build());
Document doc = documentRepository.save(Document.builder()
.title("Brief von Karl")
.originalFilename("karl.pdf")
.status(DocumentStatus.UPLOADED)
.sender(sender)
.build());
UUID annId = annotationRepository.save(annotation(doc.getId())).getId();
blockRepository.save(block(doc.getId(), annId, BlockSource.MANUAL, false));
List<TranscriptionBlock> result = blockRepository.findManualKurrentBlocksByPerson(sender.getId());
assertThat(result).isEmpty();
}
@Test
void findManualKurrentBlocksByPerson_excludesOcrBlocks() {
Person sender = personRepository.save(Person.builder().firstName("Karl").lastName("Test").build());
Document doc = documentRepository.save(Document.builder()
.title("Brief von Karl")
.originalFilename("karl.pdf")
.status(DocumentStatus.UPLOADED)
.sender(sender)
.trainingLabels(kurrentLabels())
.build());
UUID annId = annotationRepository.save(annotation(doc.getId())).getId();
blockRepository.save(block(doc.getId(), annId, BlockSource.OCR, false));
List<TranscriptionBlock> result = blockRepository.findManualKurrentBlocksByPerson(sender.getId());
assertThat(result).isEmpty();
}
@Test
void findManualKurrentBlocksByPerson_excludesOtherSender() {
Person karl = personRepository.save(Person.builder().firstName("Karl").lastName("Test").build());
Person anna = personRepository.save(Person.builder().firstName("Anna").lastName("Test").build());
Document doc = documentRepository.save(Document.builder()
.title("Brief von Karl")
.originalFilename("karl.pdf")
.status(DocumentStatus.UPLOADED)
.sender(karl)
.trainingLabels(kurrentLabels())
.build());
UUID annId = annotationRepository.save(annotation(doc.getId())).getId();
blockRepository.save(block(doc.getId(), annId, BlockSource.MANUAL, false));
List<TranscriptionBlock> result = blockRepository.findManualKurrentBlocksByPerson(anna.getId());
assertThat(result).isEmpty();
}
@Test
void countManualKurrentBlocksByPerson_matchesFindResult() {
Person sender = personRepository.save(Person.builder().firstName("Karl").lastName("Test").build());
Document doc = documentRepository.save(Document.builder()
.title("Brief von Karl")
.originalFilename("karl.pdf")
.status(DocumentStatus.UPLOADED)
.sender(sender)
.trainingLabels(kurrentLabels())
.build());
UUID annId = annotationRepository.save(annotation(doc.getId())).getId();
blockRepository.save(block(doc.getId(), annId, BlockSource.MANUAL, false));
blockRepository.save(block(doc.getId(), annId, BlockSource.MANUAL, true));
long count = blockRepository.countManualKurrentBlocksByPerson(sender.getId());
assertThat(count).isEqualTo(2);
}
// ─── helpers ─────────────────────────────────────────────────────────────
private static Set<TrainingLabel> kurrentLabels() {
return new HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION));
}
private DocumentAnnotation annotation(UUID docId) {
return DocumentAnnotation.builder()
.documentId(docId)

View File

@@ -502,7 +502,7 @@
"doc_details_more_receivers": "+{count} weitere",
"transcription_mode_label": "Transkribieren",
"transcription_mode_stop": "Fertig",
"transcription_block_placeholder": "Text hier eingeben...",
"transcription_block_placeholder": "Text eingeben — mit @Name eine Person aus dem Archiv verknüpfen",
"transcription_block_save_saving": "Speichere...",
"transcription_block_save_saved": "Gespeichert",
"transcription_block_save_error": "Nicht gespeichert",

View File

@@ -502,7 +502,7 @@
"doc_details_more_receivers": "+{count} more",
"transcription_mode_label": "Transcribe",
"transcription_mode_stop": "Done",
"transcription_block_placeholder": "Type text here...",
"transcription_block_placeholder": "Type text — use @name to link a person from the archive",
"transcription_block_save_saving": "Saving...",
"transcription_block_save_saved": "Saved",
"transcription_block_save_error": "Not saved",

View File

@@ -502,7 +502,7 @@
"doc_details_more_receivers": "+{count} más",
"transcription_mode_label": "Transcribir",
"transcription_mode_stop": "Listo",
"transcription_block_placeholder": "Escriba el texto aquí...",
"transcription_block_placeholder": "Escriba el texto — use @nombre para vincular a una persona del archivo",
"transcription_block_save_saving": "Guardando...",
"transcription_block_save_saved": "Guardado",
"transcription_block_save_error": "No guardado",

View File

@@ -10,6 +10,7 @@ import { cleanup, render } from 'vitest-browser-svelte';
import { page, userEvent } from 'vitest/browser';
import PersonMentionEditorHost from './PersonMentionEditor.test-host.svelte';
import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
type Person = components['schemas']['Person'];
type PersonMention = components['schemas']['PersonMention'];
@@ -395,6 +396,14 @@ describe('PersonMentionEditor — placeholder behavior', () => {
});
});
// ─── i18n message content ─────────────────────────────────────────────────────
describe('PersonMentionEditor — i18n message content', () => {
it('transcription_block_placeholder contains @ mention trigger for discoverability', () => {
expect(m.transcription_block_placeholder()).toContain('@');
});
});
// ─── Touch target (WCAG 2.2 AA) ──────────────────────────────────────────────
describe('PersonMentionEditor — touch target', () => {