feat: Add scheduled_tasks.lock file
The newly created `scheduled_tasks.lock` file tracks scheduled tasks for Claude, ensuring efficient and organized task management.
This commit is contained in:
+11
-7
@@ -117,7 +117,9 @@ UI-unabhängige Tests laufen auf der JVM (keine Emulator-Runtime). Importe von `
|
||||
|
||||
#### 1a. Pixel Watch App (Wear OS / Kotlin) — primäres Entwicklungsziel
|
||||
|
||||
**Ist-Stand (2026-04-23):** Vertikaler PoC end-to-end lauffähig auf Wear-OS-34-AVD **und auf realer Pixel Watch 2** (Wear OS 5, ADB-over-WiFi-Pairing). Record-Button → 5 s MediaRecorder (MPEG-4/AAC-LC 16 kHz 64 kbps mono) → Multipart-`POST /api/upload` mit `X-API-Key` → Server schreibt `.m4a`, Whisper transkribiert, Ollama klassifiziert, `document.md` entsteht. Manuell verifiziert mit "Hallo, hallo" → korrekte Transkription + `oneliner.kind = empty` (Silence-Rule). Architektur-Unbekannte aus der vertikalen Scheibe (Audio-Codec-Kompatibilität, Cleartext-HTTP-Policy, Emulator-Mic, Wire-Vertrag) sind **gemessen**, nicht mehr spekulativ. Toolchain unterstützt beide Targets parallel: `build.gradle.kts` liest Gradle-CLI-Property `-Pdoctate.serverUrl=…` vor `local.properties` (damit Emulator auf `10.0.2.2:3000` und Watch auf `http://<laptop-lan-ip>:3000` ohne Dateitausch wechseln); `network_security_config.xml` whitelistet die LAN-IP zusätzlich zu `10.0.2.2` für Cleartext (Produktions-HTTPS bleibt via `base-config` gesperrt); `run.sh` hat ein `watch`/`emulator`-Target-Prefix plus `connect`/`devices`-Subkommandos und cacht die zuletzt gepairte Watch-Serial in `.watch_serial`. Der PoC hat aber **bewusst** nicht: Fallliste, Marker-Dateien, `unsynced/`-Queue, Foreground Service, Retry-Backoff, Oneliner-Polling, Bluetooth-Headset, Neu/Fortsetzen-Flow — diese Features bleiben laut Plan zu bauen.
|
||||
**Ist-Stand (2026-04-24):** Vertikaler PoC end-to-end lauffähig auf Wear-OS-34-AVD **und auf realer Pixel Watch 2** (Wear OS 5, ADB-over-WiFi-Pairing). Record-Button → 5 s MediaRecorder (MPEG-4/AAC-LC 16 kHz 64 kbps mono) → Multipart-`POST /api/upload` mit `X-API-Key` → Server schreibt `.m4a`, Whisper transkribiert, Ollama klassifiziert, `document.md` entsteht. Manuell verifiziert mit "Hallo, hallo" → korrekte Transkription + `oneliner.kind = empty` (Silence-Rule). Architektur-Unbekannte aus der vertikalen Scheibe (Audio-Codec-Kompatibilität, Cleartext-HTTP-Policy, Emulator-Mic, Wire-Vertrag) sind **gemessen**, nicht mehr spekulativ. Toolchain unterstützt beide Targets parallel: `build.gradle.kts` liest Gradle-CLI-Property `-Pdoctate.serverUrl=…` vor `local.properties` (damit Emulator auf `10.0.2.2:3000` und Watch auf `http://<laptop-lan-ip>:3000` ohne Dateitausch wechseln); `network_security_config.xml` whitelistet die LAN-IP zusätzlich zu `10.0.2.2` für Cleartext (Produktions-HTTPS bleibt via `base-config` gesperrt); `run.sh` fordert jetzt ein explizites `watch`/`emulator`-Target-Prefix (früherer stiller `(none)`-Fallback auf das erste Device entfernt), exportiert `ANDROID_SERIAL` zusätzlich zu `android.injected.device.serial` für exakte ADB-Selektion, cacht die zuletzt gepairte Watch-Serial in `.watch_serial` und ergänzt `connect`/`devices`-Subkommandos.
|
||||
|
||||
Seit dem 2026-04-24 gebaut (alles noch auf `CaseStoreStub`-Basis — in-memory Singleton mit Demo-Seed, keine Marker-Dateien): **Fallliste als `ScalingLazyColumn`** (`CaseListScreen`, reverse-chronologisch, Auto-Scroll zum neuesten Eintrag, Auto-Centering deaktiviert, `CaseRow` mit minHeight/Padding/Text-Ellipsis), **Tile-Service** (`DoctateTileService` mit drei Tap-Regionen), **Complication-Service** (`DoctateComplicationService`, simple SHORT_TEXT), **Navigation** (`AppNav` mit `NavCommand`, `MainActivity` auf `singleTask` für Tile/Complication-Intents), **Recording-Flow aufgetrennt** (`CaseListScreen → CaseDetailScreen → RecordingScreen`; „● Neu" überspringt Detail, Tap auf Liste öffnet Detail, Tap auf Detail-Record öffnet Recording), **Tap-to-edit-Oneliner** im CaseDetailScreen (Wear-OS-System-Input-Picker, de-DE-gepinnt, Manual-Flag latcht gegen simulierten LLM-Burst), **Zeit-Formatierung** (`TimeFormat.kt`: „Gerade eben" / „Vor X Minuten" / „Heute HH:mm" / „Gestern HH:mm" / „dd.MM.yy HH:mm" via `java.time`). Der PoC hat aber **bewusst** weiterhin nicht: Marker-Dateien + `unsynced/`-Queue (persistente Quelle der Fallliste), Foreground Service, Retry-Backoff, Server-seitiges Oneliner-Polling, Bluetooth-Headset, Neu/Fortsetzen-Flow über echten `CaseStore` — diese Features bleiben laut Plan zu bauen.
|
||||
|
||||
**Funktion:**
|
||||
- Audioaufnahme direkt auf der Watch (MediaRecorder, AAC/m4a)
|
||||
@@ -1220,12 +1222,12 @@ wiremock = "0.6"
|
||||
|
||||
#### 5b — Pixel Watch App (primäres Entwicklungsziel)
|
||||
- [x] Wear OS Projekt in Android Studio (`:app-wear`) — `watch/wearos/` Gradle-Projekt (AGP 9.2, Kotlin 2.2.10, Compose BOM 2024.09, minSdk 30, targetSdk 36). **Single `:app` statt `:app-wear` + `:core-*`.**
|
||||
- [x] Jetpack Compose UI — PoC-UI (`RecordingScreen`) rendert den Record-Flow. Fallliste + Tile + Complication sind geplant, aber noch nicht gebaut.
|
||||
- [ ] Activity-Hauptscreen: Fallliste als `ScalingLazyColumn` + EdgeButton „● Neu" (funktional-analog zum Desktop-Client). Tap auf Eintrag → Fortsetzen; Tap auf EdgeButton → neuer Fall.
|
||||
- [ ] Singleton-`CaseStore` in Kotlin (Pendant zum Rust-`CaseStore` in `doctate-client-core`): Snapshot-Flow, `create_local` / `mark_activity` / `merge_server_snapshot` / `reconcile_with_server_snapshot`. Gemeinsame Datenquelle für Activity + TileService.
|
||||
- [ ] Tile-Service (`DoctateTileService` via androidx.wear.tiles): ProtoLayout mit drei Tap-Regionen — Mitte (OneLiner → Fortsetzen), oben-rechts (☰ Fälle → Activity/Liste), EdgeButton (● Neu → Activity/Recording). Refresh via `getUpdater().requestUpdate(...)` nach jedem `CaseStore`-Merge.
|
||||
- [ ] Complication-Service (`ComplicationDataSourceService`): `SHORT_TEXT` oder `SMALL_IMAGE`, Tap → MainActivity mit `open=list`. Kein OneLiner-Text im Slot.
|
||||
- [ ] Nach Stop: Screen bleibt auf aktuellem Fall (→ „Fortsetzen" direkt sichtbar)
|
||||
- [x] Jetpack Compose UI — PoC-UI umfasst jetzt `CaseListScreen` + `CaseDetailScreen` + `RecordingScreen` plus `AppNav`/`NavCommand`-Navigation. Nur die Datenquelle ist noch Stub (siehe unten).
|
||||
- [~] Activity-Hauptscreen: Fallliste als `ScalingLazyColumn` + EdgeButton „● Neu" (funktional-analog zum Desktop-Client). Tap auf Eintrag → Fortsetzen; Tap auf EdgeButton → neuer Fall. — UI-Scaffold gebaut (reverse-chronologisch, Auto-Scroll zum neuesten, Auto-Centering deaktiviert, `CaseRow` mit minHeight/Padding/Ellipsis); Tap auf Eintrag öffnet jetzt erst `CaseDetailScreen` (siehe Client-Architektur → Watch-Recording-Flow), „● Neu" überspringt den Detail-Schritt. Datenquelle noch `CaseStoreStub`.
|
||||
- [~] Singleton-`CaseStore` in Kotlin (Pendant zum Rust-`CaseStore` in `doctate-client-core`): Snapshot-Flow, `create_local` / `mark_activity` / `merge_server_snapshot` / `reconcile_with_server_snapshot`. Gemeinsame Datenquelle für Activity + TileService. — **PoC: `CaseStoreStub`** (in-memory Singleton, Demo-Seed) speist Activity + Tile + Complication aus derselben Quelle. Snapshot-Flow + Merge-Asymmetrie zum Rust-Pendant stehen noch aus, ebenso die Bindung an Marker-Dateien.
|
||||
- [~] Tile-Service (`DoctateTileService` via androidx.wear.tiles): ProtoLayout mit drei Tap-Regionen — Mitte (OneLiner → Fortsetzen), oben-rechts (☰ Fälle → Activity/Liste), EdgeButton (● Neu → Activity/Recording). Refresh via `getUpdater().requestUpdate(...)` nach jedem `CaseStore`-Merge. — PoC gebaut (Tap-Regionen: continue-case / list / new-case). Refresh-Trigger nach Merge fehlt, solange der echte `CaseStore` fehlt.
|
||||
- [~] Complication-Service (`ComplicationDataSourceService`): `SHORT_TEXT` oder `SMALL_IMAGE`, Tap → MainActivity mit `open=list`. Kein OneLiner-Text im Slot. — PoC: `DoctateComplicationService` (SHORT_TEXT „Doctate", Tap → Fallliste).
|
||||
- [ ] Nach Stop: Screen bleibt auf aktuellem Fall (→ „Fortsetzen" direkt sichtbar) — aktuell: Stop poppt `RecordingScreen` zurück auf `CaseDetailScreen` (oder auf die Liste, wenn „● Neu" den Detail-Schritt übersprungen hat). Der „Fortsetzen"-EdgeButton sitzt dort, kostet aber einen Zusatz-Tap gegenüber der ursprünglichen Plan-Skizze.
|
||||
- [ ] Post-Stop-Burst-Polling: 2 s Intervall, 60 s Budget, dann Abstieg ins reguläre Intervall; triggert Tile-Refresh bei Oneliner-Treffer
|
||||
- [x] MediaRecorder-Integration via `:core-audio` — in `audio/AudioRecorder.kt` (MPEG-4/AAC-LC, 16 kHz mono, 64 kbps, `context.cacheDir`). Landet bei künftigem Multi-Modul-Split in `:core-audio`.
|
||||
- [ ] `AudioSource.MIC` → `VOICE_RECOGNITION` umstellen — Wear-OS-DSP (Noise-Suppression, AGC) liefert saubereres Signal bei niedrigerem Rohpegel. Geht nur zusammen mit serverseitigem Replay-Gain (siehe Phase 4), weil VR allein ~9 dB leiser ist als MIC. PoC am 2026-04-23 bestätigt: VR + Replay-Gain klingt deutlich besser als MIC + Replay-Gain (letzteres verstärkt auch Rauschen mit).
|
||||
@@ -1435,3 +1437,5 @@ Alle Einträge beziehen sich auf den Ist-Stand im Repository. Die ursprüngliche
|
||||
| Watch-Instrumented-Test-Hang | Nicht im Plan | `UploadClientTest` (MockWebServer-basiert) **hängt in `@Before setUp()`** auf Wear-OS-34-AVD. JVM-Unit-Tests laufen. Real-Server-E2E validiert denselben Vertrag strenger. | Vermutlich Wear-SELinux-Policy gegen Loopback-Socket-Bind aus der Instrumentation-APK-Prozess. Priorität niedrig, weil der echte Server-Upload funktioniert — Mock-Test ist nur Regressions-Absicherung fürs Refactoring. `SKIP_INSTRUMENTED=1 ./run.sh test` überspringt den on-device-Tier. |
|
||||
| Watch-UI-Surface-Aufteilung | Plan (alt): „ein Screen pro Eintrag, Swipe hoch/runter zwischen Fällen", konzeptionell als Tile-Paginierung gedacht | **Drei native Wear-OS-Surfaces** in getrennten Rollen: Activity (`ScalingLazyColumn` + EdgeButton „● Neu") = Fallliste + Recording; Tile (ProtoLayout mit drei Tap-Regionen: OneLiner-Mitte / ☰ Fälle / EdgeButton „● Neu") = Glance auf den aktuellen Fall; Complication (`SHORT_TEXT`/`SMALL_IMAGE`, Tap → Liste) = optionaler Fast-Launch vom Watchface | Tile-Paginierung ist technisch unmöglich: ProtoLayout ist nicht scrollbar, Long-Press und vertikaler Swipe sind System-Gesten (Tile-Edit-Karussell / Quick-Panel) und von Apps nicht abfangbar. Die einzige App-seitige Interaktion auf einem Tile ist Tap auf `Clickable`-Regionen — mehrere pro Tile sind HIG-konform und von System-Tiles etabliert. Activity + Tile + Complication teilen sich einen Singleton-`CaseStore`-Snapshot (Kotlin-Pendant zum Rust-`CaseStore`), damit sie nie divergieren; Tile-Refresh via `getUpdater().requestUpdate()` nach jedem Merge. |
|
||||
| Watch-Core in Kotlin (nicht Rust) | „Es war geplant, die Business-Logik aller Clients in Rust zu entwickeln" (Diskussionsstand) — technisch machbar via UniFFI-Bindings auf `doctate-client-core` | **Watch-`CaseStore` in Kotlin nachgebaut**, strukturell 1:1 zum Rust-Pendant (Snapshot-Flow, Merge-/Reconcile-Asymmetrie, Sync-Flag-Semantik) | Für MVP (PoC → Watch-App-Abschluss) wiegt der zusätzliche Build-Stack (NDK, Cross-Compile für `aarch64-linux-android` + `armv7-linux-androideabi`, APK-Größe) schwerer als der Code-Sharing-Nutzen — es gibt genau *einen* Kotlin-Consumer. Natürlicher Einzug-Moment ist der Start der **Handy-App** (zweiter Kotlin-Consumer → erster echter Duplikations-Druck); bis dahin wird die Kotlin-Portierung diszipliniert strukturgleich zum Rust-Original geführt, damit ein späterer UniFFI-Swap kein Refactoring der Call-Sites erzwingt. |
|
||||
| Watch-Recording-Flow | Plan: Einstieg über EdgeButton „● Neu" *oder* Tap auf Listen-Eintrag → direkt Aufnahme; Stop → zurück auf den aktiven Fall | **Drei separate Screens**: `CaseListScreen` → `CaseDetailScreen` (bei Tap auf einen Fall) → `RecordingScreen` (bei Tap auf den Detail-Record-Button). „● Neu" bleibt Direkt-Einstieg (überspringt Detail). `RecordingScreen` ist radikal reduziert (live `mm:ss`-Counter, Stop-EdgeButton, 300 s Safety-Cap via `elapsedSeconds`-Reducer, `FLAG_KEEP_SCREEN_ON` via `view.keepScreenOn`). Swipe-right oder Back = Discard ohne Bestätigung (`DisposableEffect.onDispose` ist der einzige Discard-Pfad). | Ein Tap auf einen Listeneintrag darf nicht stumm das Mikrofon öffnen — der Arzt soll erst den Fall (Datum + Oneliner) sehen und explizit „Record" drücken. Der State-Machine-Split kommt mit zwei neuen Invarianten: (1) `finalizeAndUpload` läuft in `applicationScope`, damit der Pop nach Stop den Upload nicht kill — `AtomicBoolean finalizationStarted` serialisiert Stop/Discard/Auto-Stop gegen Doppelspiel; (2) `formatTime` ist nach `TimeFormat.kt` extrahiert, damit Liste und Detail-Header dieselbe Relativ-/Absolut-Formatierung („Gerade eben" / „Heute HH:mm" / „dd.MM.yy HH:mm") zeigen. |
|
||||
| Watch-Oneliner-Manual-Override | Plan (Zeile 1384 ff.): Oneliner wird ausschließlich serverseitig am Batch-Ende aus allen Transkripten regeneriert — der Arzt beeinflusst ihn nur über das Diktat („Bezeichnung: …") | **PoC auf der Watch**: Tap auf den Oneliner im `CaseDetailScreen` öffnet den Wear-OS-System-Input-Picker (voice/keyboard/handwriting, fest auf `de-DE` gepinnt). Ein Doctor-Edit latched ein Manual-Flag am `CaseEntry`, das den simulierten LLM-Burst blockiert; ein erneutes Manual-Edit gewinnt wieder. | Medizinische Oneliner-Typos und Fall-Bezeichnungen sollen ohne Umweg übers Diktat korrigierbar sein. „Doctor wins" ist die neue Invariante gegenüber der serverseitigen Regen-Logik. **Offene Entwurfsentscheidung für Phase 5b:** wie wird das Manual-Flag zum Server synchronisiert, damit Browser-UI, Desktop-Client und künftiger Handy-Client es respektieren (kandidierende Varianten: eigenes Upload-Feld `{oneliner, manual: true}`, oder dedizierter `PUT /api/oneliner/{case_id}` mit API-Key-Auth — konsistent zum bestehenden API-Key-Erfassungs-Vertrag). Aktuell arbeitet der PoC nur gegen den `CaseStoreStub`, das Wire-Protokoll zum Server fehlt. |
|
||||
|
||||
Reference in New Issue
Block a user