- desktop/ module compiles the Android app's portable sources directly
(shared CustomShonarProvider, parsers, folder logic — no copies);
JVM settings store (0600 JSON) replaces DataStore/Keystore
- Engine screen: spawns the local backend+worker on demand, health-polls,
auto-provisions a private local account (no login UI)
- Library: folder picker, audio list, report-written badges, search across
.transcript.md reports; Detail: transcribe with per-file model override,
job polling, Plaud-style report next to the audio; Settings: model
registry (default, per-model download)
- scripts/transcribe_folder.py: batch-transcribe a folder via the API
- Reports + shared-parser unit tests green (gradlew test)
Backend:
- PUT /recordings/{id}/transcript and /summary — user-edited, versioned;
the pipeline never clobbers user versions
- Model registry (tiny/base/small/medium/large-v3), global default via
PUT /models/default (future rows only), per-recording override on
finalize/upload-session (finalize wins), GET /api/v1/models
- processing_jobs gain stage/progress; faster-whisper model cache +
fail-fast on unavailable models; migration m8models000001
- 10 model tests + 5 transcript-edit tests (backend suite 64 green, ruff clean)
Android:
- detail/{id} route: transcript/summary/status tabs, tap-to-seek, speed
control, edit dialogs, title rename via CustomShonarProvider AI methods
- pure AiContent parsing/sync mapping; honest empty states for
local-only/unsynced rows; 19 new unit tests (Android suite green)
sweep_stale re-enqueued every queued/running job every 5 minutes with
undifferentiated enqueue_job ids, so a long transcription accumulated a
duplicate copy per sweep; arq's default max_jobs=10 then ran them all
concurrently in one worker (each a multi-GB local whisper pass) and the
worker ballooned to 11 GB + 500% CPU, swapping the box.
- transport_enqueue: deterministic _job_id (type:recording) so arq
refuses duplicate copies while one is queued/running; stale result key
is dropped first so deliberate re-runs of finished work still enqueue.
- sweep_stale: running jobs started within STALE_RUNNING_AFTER (2h) are
live work, not crash relics, and are left alone.
- WorkerSettings.max_jobs = 1: one whisper pass per worker process;
scale via more worker processes.
- regression test: sweep leaves a live running job, requeues an orphan.
- server_url setting deleted (no code referenced it; it predated the
provider pivot and misled users into pasting Nextcloud URLs into a
field nothing reads). The provider-selection flow (P2) replaces it.
- Wi-Fi-only / charging-only uploads now carry a description stating
they apply once a sync provider is connected.
Verified: 47 unit tests green, APK builds, on-device (Pixel 8 Pro)
Settings > Network shows no SHONAR server URL field and the new
descriptions render.
ISOLATE (nothing deleted):
- moved HA module (ha/, ui/devices/, HA client tests), e2e scripts, and HA
docs under deferred/home-assistant/ with a README explaining status + how
to revive; complete snapshot preserved on branch deferred/home-assistant
REMOVE FROM ACTIVE PRODUCT:
- HomeScreen: Devices card + route gone; MainActivity nav updated
- ShonarApplication: haRepository removed
- BuiltInSettings: Home Assistant category/settings removed from defaults
- SettingsManagerTest: secret tests rewritten around a user-created
SECRET-type setting (no built-in secret ships)
- Manifest + URL-validation test fixture wording neutralized
- README/ROADMAP: HA marked deferred with pointer to preserved branch
ADD (design, per product direction):
- docs/server-providers.md: ShonarProvider interface, Room data model,
auth ladder (OIDC/PKCE -> Nextcloud login-flow-v2 -> token paste),
sync strategy, provider-selection UX (Nextcloud default; Start9/Umbrel
as platform-probe + explicit service binding, never universal APIs;
custom SHONAR server; local-only), TLS TOFU pinning policy,
no-secret-logging rules, provider contract test strategy, phased plan P0-P7
VERIFY: 19 Android unit tests green, APK builds, on-device launch OK
(consent dialog renders; no Devices entry). Backend unchanged (26 tests).
Generic settings architecture (data-driven, no per-setting UI code):
- SettingDefinition (id/name/description/category/type/default/min/max/
choices/editable/sensitive/requiresRestart/visibleIf) x 8 types:
boolean/string/number/select/multi-select/color/url/secret
- SettingsManager: validation, reset, custom CRUD, export/import
(all-or-nothing with per-key rejection reasons; custom definitions
travel in the export), secrets routed to EncryptedSharedPreferences
(AndroidKeyStore master key) and excluded from export by default
- Settings screen renders controls from the type; search; add/edit/delete
dialogs for custom settings; import/export dialogs
- Built-ins: General / Home Assistant / Appearance / Network / Advanced
Home Assistant integration (official REST + WebSocket APIs only):
- HomeAssistantClient: GET /api/, /api/states, /api/states/{id},
POST /api/services/{domain}/{service}, WS /api/websocket
(auth -> subscribe_events state_changed) with exponential-backoff
reconnect; safe HaError types that never contain the token
- HaRepository: single service layer between UI and client; optimistic
toggles reconciled by WS; poll fallback from refresh-interval setting
- Devices screen: discovery list, search, live states, toggles
(light/switch/fan/input_boolean/humidifier)
- Cleartext permitted for LAN http:// URLs (same stance as the official
HA companion app; TLS verification untouched); consent dialog on first
launch; Record button present but inert until M4 (honest label)
- Tests: 29 unit tests (19 settings incl. secret routing, import
validation, persistence; 10 HA via MockWebServer incl. 401 handling,
unreachable, WS handshake + event + auth_invalid). All green.
Docs: docs/home-assistant.md (install, token creation, storage,
troubleshooting, endpoint table); README + ROADMAP updated honestly
(live e2e against a real HA server still in progress).
Users expect Swagger at /docs; the app had mounted them under /api/*.
Also normalize user-facing product name to "SHONAR" in the app title and
root route (Python package stays shonar), and regenerate the shared
OpenAPI contract.