Adds the directory, gitignore rule, and checked-in template. Real profile
files (e.g. brummel-dev.sh) live alongside but are gitignored as they
contain plaintext api_keys.
refs #3
Mirror of the 2026-05-05 Wear-OS fix: port the canonical client sync
spec (project_client_sync_semantics.md) into the Rust desktop client.
Before this change, server_sync::run_loop had a `UploadOutcome::Terminal`
branch that deleted the m4a + sidecar pair on any non-2xx + non-5xx
response (401, 413, 415, parse drift, audio read failures) and never
called mark_synced — so the marker stayed `synced_to_server=false` as a
non-removable ghost in the desktop UI. Same shape as the Wear-OS bug.
Changes:
- `UploadOutcome` reduced to two variants (`Succeeded` | `Transient`).
The Terminal arm in `run_loop` is gone; `delete_pair` only fires from
the success path.
- `post_upload` now classifies every failure mode as `Transient`:
4xx incl. 401, parse-ack drift on 200, file-read errors, multipart
build errors, and network errors. Backoff retries forever.
- New `CaseStore::cleanup_orphaned_unsynced(pending_case_ids, cutoff)`:
removes markers that are unsynced AND have no waiting upload pair AND
are older than a small grace window. Self-heals pre-spec ghosts.
- `run_startup_cleanup` gains a third sweep that calls
`cleanup_orphaned_unsynced` with `DEFAULT_ORPHAN_MARKER_GRACE = 5 min`,
matching the Wear-OS StartupCleanup contract.
- Tests inverted/added: `post_upload_transient_on_401`,
`post_upload_transient_on_2xx_unparseable_body`,
`former_terminal_401_keeps_pair_and_retries`, four new
`cleanup_orphaned_unsynced_*` cases, plus `sweeps_orphan_unsynced_marker`
and `keeps_unsynced_marker_with_pending_pair` integration tests.
Followup (out of scope): `UploadEvent::Failed.will_retry` can now only
be `true` and is vestigial. Removing it touches every Failed-event
consumer in app.rs/UI; left for a separate cleanup commit.
Treat every non-Success upload response as Transient so audio survives
expired sessions, 4xx replies, and unparseable 2xx bodies — the watch
keeps retrying instead of silently dropping the recording. Drop the
UploadResult.Terminal variant entirely and the SyncWorkerLoop branch
that consumed it.
Also reap orphaned unsynced markers on app start: a syncedToServer=false
marker without a matching pending pair (audio gone) and older than a
5-minute grace window is inconsistent — the user couldn't discard it,
and no other path could clean it up. Eliminates the 2026-05-03 21:08
ghost-marker class.
Test fixture (InMemoryCaseStore), SyncWorkerLoopTest, UploadClientTest,
and StartupCleanupTest updated to reflect the new contract.
The /web/ prefix predated the /api/ split; today it just clutters every URL
without disambiguating anything. All 16 browser routes move to the apex
(/cases, /login, /magic, /events, /audio/...). The 6 /api/* routes are
unchanged. A new /->>/cases redirect closes the apex 404.
The open-redirect guard in magic.rs and case_actions.rs flips from a
positive whitelist (starts_with("/web/")) to a deny-list: same-origin path,
not protocol-relative, not under /api/, no \. The /api/ exclusion is now
load-bearing and covered by tests.
Pre-production: no transition redirects.
Add Fachrichtung to prompt and refine medical terms.
Added Fachrichtung to the system prompt, instructing the LLM to consider
the medical specialty for precise terminology.
Also, added "Abdomensonographie" to the `medical_terms.txt` file.
The tile layout has been refactored to display a list of recent cases
instead of the currently active case. This change includes:
- Removing the "current case" view and its associated logic.
- Implementing a new `caseListContent` function to render a list of up
to 8 recent cases.
- Modifying the main `buildTileLayout` to integrate the new case list
and a prominent "Neu" (New) button.
- Updating the styling and click handling to reflect the new layout and
functionality.
Use the current case's oneliner text as the title for the remote input
intent. This ensures consistency with the CaseListScreen's display when
no oneliner is present, showing a "…" placeholder. This improves the
user experience by making the system input picker's title match what the
user just selected.
Increase `max_completion_tokens` for `gpt_oss_120b` from 8192 to 16384
to prevent incomplete responses.
Navigate to the detail screen before the recording screen when starting
a new case, ensuring correct back navigation.
Replace stale references in 17 Kotlin files:
- doctate-client-core::* -> doctate-desktop::*
- doctate-client-core/src/* -> clients/desktop/src/*
- doctate-common/src/* -> common/src/*
The doctate-common:: crate path stays valid (crate name unchanged).
PendingStoreTest.kt is in src/test/, so the test mirror is also
updated.
Verification:
- grep for "doctate-client-core" / "doctate-common/src/" in
clients/wearos/ returns 0 hits.
- gradlew :app:testDebugUnitTest BUILD SUCCESSFUL (23s) -- also
retroactively confirms stage-2's directory move did not break
any Gradle relative-path assumptions.
- cargo clippy --all-targets -- -D warnings clean in both
server/ and clients/desktop/ workspaces.
This concludes the four-stage server-vs-clients build-world split:
- stage 1 (39cc666): dissolve doctate-client-core
- stage 2 (0c66fc6): rename to common/, clients/desktop, clients/wearos
- stage 3 (7b4fe87): split into standalone workspaces
- stage 4 (this): update wearos anchors
docs/projektplan.md to be updated separately.
Add [workspace] / [workspace.package] / [workspace.dependencies]
blocks to server/Cargo.toml and clients/desktop/Cargo.toml so each
becomes a self-contained workspace with its own Cargo.lock.
Remove the root Cargo.toml and Cargo.lock; the repo now hosts
three independent build worlds: server, clients/desktop, experiments.
Convert common/Cargo.toml to concrete versions (was workspace-
inherited from the root). common/ is consumed via path-dep from
all three worlds, but is not itself a workspace -- consumers
unify versions via their own lockfiles. Add common/Cargo.lock to
.gitignore per Cargo lib-only-crate convention.
Verification:
- server/Cargo.lock has 0 eframe/egui/winit entries
- cargo build --release --locked passes in server/
- test split: server 385 + desktop 89 + common 34 = 508 total
- bin output: clients/desktop/target/debug/doctate (named "doctate")
Move three directories into their target topology:
- doctate-common/ -> common/
- client-desktop/ -> clients/desktop/
- watch/wearos/ -> clients/wearos/
Update doctate-common path-dependency in 3 Cargo.toml files
(server, clients/desktop, experiments) and the workspace
members list in the root Cargo.toml. Crate names unchanged
(doctate-server, doctate-common, doctate-desktop).
Workspace still in single-Cargo.lock form; isolation into
standalone workspaces follows in stage 3. All 508 tests pass,
experiments standalone-workspace also resolves the new path.