feat(watch): vertical PoC — record, upload, playback end-to-end

Completes the 5-second-record-and-upload flow:
- RecordingViewModel orchestrates recorder, countdown, and upload
- MediaRecorder wrapper produces 16kHz/AAC-LC m4a (Whisper-compatible)
- Compose RecordingScreen renders the state pyramid
  (Idle / RequestingPermission / Recording / Uploading / Success / Error)
- UiState + pure reducer with full JVM unit-test coverage
- run.sh "test" subcommand wraps the unit and instrumented tiers
  (set SKIP_INSTRUMENTED=1 to bypass the on-device tier)

Verified end-to-end: emulator captures "Hallo, hallo", lands at
tmpdata/<user>/<case>/*.m4a, Whisper transcribes, Ollama classifies,
document.md is written — full pipeline in ~5s.

Known: MockWebServer-based UploadClientTest hangs in setUp on the
Wear OS 34 emulator (likely SELinux socket policy). Deferred — the
real-server end-to-end already validates the wire protocol.
This commit is contained in:
2026-04-23 16:30:57 +02:00
parent 8306dd8c47
commit ed993a5c6d
8 changed files with 444 additions and 109 deletions
@@ -81,9 +81,12 @@ class UploadClientTest {
}
private fun copyAssetToTempFile(assetName: String): File {
val ctx = InstrumentationRegistry.getInstrumentation().context
val tmp = File.createTempFile("upload-test-", ".m4a", ctx.cacheDir)
ctx.assets.open(assetName).use { input ->
val instrumentation = InstrumentationRegistry.getInstrumentation()
// Assets are packaged with the test APK; cache dir lives under the app APK.
val testCtx = instrumentation.context
val appCtx = instrumentation.targetContext
val tmp = File.createTempFile("upload-test-", ".m4a", appCtx.cacheDir)
testCtx.assets.open(assetName).use { input ->
tmp.outputStream().use { output -> input.copyTo(output) }
}
return tmp