feat: Aufnahmeseite und Liste der Sprachnotizen (T7 L5 Task 19)

/voice/new: großer Aufnahmeknopf mit Zeit, Hinweis bei 150 s, Stopp bei
180 s; eine automatisch beendete Aufnahme liegt sofort in der
Warteschlange des Geräts (Ruling 13); Abbrechen und Neu aufnehmen
fragen nach; Hilfe bei gesperrtem Mikrofon; Fokus bleibt bei jedem
Zustandswechsel an einem Bedienelement; Volk und Datum während der
Aufnahme gesperrt, ?date= auf die letzten 7 Tage begrenzt.
/voice: wartende Notizen des Geräts zuerst, dann die des Servers mit
Status, „Prüfen“ nur bei fertigen, Verwerfen mit Rückfrage.
Einstiege über Schnellanlage, Menü (nur mit voice-captures:use und
inspections:create), Volksseite und Durchsicht. Bei ausgeschalteter
Aufnahme verschwinden die Einstiege; jeder HTTP-Fehler der
Konfiguration zählt als aus, nur fehlendes Netz als unbekannt.
Fehlertexte gleich mit dem Backend (Test).

Gates: Frontend lint, typecheck, 2979 Tests, build.

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:49:56 +02:00
1 parent 462ac46d1e
commit e0be687b7c
33 files changed
+3460 -19

No files matched your search

@@ -96,6 +96,20 @@ describe("uploadPendingVoice", () => {
expect(notes.every((n) => n.lastError === undefined)).toBe(true);
});
it("keeps a 403 VOICE_RECORDING_DISABLED as a definitive refusal on the note", async () => {
const note = await pending.queueVoiceNote(input());
uploadVoiceCapture.mockRejectedValueOnce(
new ApiError(403, "off", { code: "VOICE_RECORDING_DISABLED" }),
);
const result = await upload.uploadPendingVoice("user-1", never);
expect(result.failed).toBe(1);
const [kept] = await pending.listPendingVoice("user-1");
expect(kept.id).toBe(note.id);
expect(kept.lastError?.status).toBe(403);
});
it("a 409 'still being processed' skips that note and goes on", async () => {
await pending.queueVoiceNote(input());
await pending.queueVoiceNote(input("user-1", "colony-2"));