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

@@ -7,9 +7,10 @@ import {
} from "./nav-config";
const ALL_ITEMS = [...NAV_GROUPS, ADMIN_GROUP].flatMap((group) => group.items);
const ALL_PERMISSIONS = ALL_ITEMS.flatMap((item) =>
item.permission === undefined ? [] : [item.permission].flat(),
);
const ALL_PERMISSIONS = ALL_ITEMS.flatMap((item) => [
...(item.permission === undefined ? [] : [item.permission].flat()),
...(item.alsoRequires ?? []),
]);
function hrefs(permissions: string[]): string[] {
return visibleNavGroups({ permissions })
@@ -72,6 +73,17 @@ describe("nav-config", () => {
expect(item?.counter).toBe("suggestions");
});
it("shows „Sprachnotizen“ only with voice-captures:use AND inspections:create", () => {
expect(hrefs([])).not.toContain("/voice");
expect(hrefs(["voice-captures:use"])).not.toContain("/voice");
expect(hrefs(["inspections:create"])).not.toContain("/voice");
const work = visibleNavGroups({
permissions: ["voice-captures:use", "inspections:create"],
}).find((group) => group.label === "Arbeit");
const item = work?.items.find((entry) => entry.href === "/voice");
expect(item?.label).toBe("Sprachnotizen");
});
it("shows Prüfbereitschaft only with compliance:read", () => {
expect(hrefs([])).not.toContain("/compliance");
expect(hrefs(["compliance:read"])).toContain("/compliance");