feat: Sprachnotizen in Synchronisierung, Warteschlange und Abmelden (T7 L5 Task 18)
Backend Image / build-and-push (push) Successful in 7m6s
Frontend Image / build-and-push (push) Successful in 15m27s
Manifests / validate (push) Successful in 16s

Wartende Sprachnotizen werden nach Einträgen und Fotos hochgeladen, je
Notiz ein fester Idempotency-Key; endgültige 4xx markieren die Notiz
mit festem deutschem Text, 401/408/429/5xx/Netz warten auf den nächsten
Lauf, „wird bearbeitet“ wird übersprungen. Warteschlangen-Sheet mit
Abschnitt „Sprachnotizen“ (Verwerfen je Notiz nur für den Besitzer,
alle verwerfen mit Rückfrage, Hinweis bei zu vielen offenen Notizen),
Offline-Band zählt nur wartende Notizen, Abmelden fragt bei wartenden
Notizen nach. Zählen liest keine Audiodaten.

Gates: Frontend lint, typecheck, 2886 Tests.

Refs #27

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-16 11:10:14 +02:00
1 parent 0c7147f626
commit e614ded967
18 files changed
+1201 -30

No files matched your search

@@ -66,6 +66,32 @@ describe("LogoutConfirmDialog", () => {
expect(screen.getByRole("button", { name: "Abbrechen" })).toBeVisible();
});
it("asks to discard waiting voice notes and says they cannot be restored", async () => {
vi.mocked(summarizeQueue).mockResolvedValue({ own: 3, failed: 0, foreign: 0, voice: 2 });
renderDialog();
const dialog = await screen.findByRole("dialog", { name: "Abmelden?" });
expect(dialog).toHaveTextContent("3 Einträge sind noch nicht gesendet.");
expect(dialog).toHaveTextContent("Darunter 2 Sprachnotizen");
expect(dialog).toHaveTextContent("nicht wiederherstellen");
expect(logoutMock).not.toHaveBeenCalled();
await userEvent.click(
screen.getByRole("button", { name: "Abmelden und Einträge löschen" }),
);
await waitFor(() => expect(logoutMock).toHaveBeenCalledWith({ discardQueue: true }));
});
it("does not mention recordings when there are none", async () => {
vi.mocked(summarizeQueue).mockResolvedValue({ own: 1, failed: 0, foreign: 0 });
renderDialog();
expect(await screen.findByRole("dialog", { name: "Abmelden?" })).not.toHaveTextContent(
"Sprachnotiz",
);
});
it("deletes the entries only after the destructive button was pressed", async () => {
vi.mocked(summarizeQueue).mockResolvedValue({ own: 1, failed: 0, foreign: 0 });
renderDialog();