guideCity/backend/app/api/routes
vrubelroman cd428d0fb7 Persist listened places, fix restart-on-reopen bug, add stats/reset
Five behavior changes from live testing feedback:

1. Narration no longer leads with the place title — the body text
   already opens with the name, so it was read twice.

2. "Done" is now derived from a persisted set of listened place IDs
   (UserPrefsDataStore.listenedPlaceIds), not an in-memory index set
   that got wiped on every load. A place that's ever been narrated to
   completion — in the guide list or the detail screen — is never
   auto-narrated again, including across the 60s re-scan and app
   restarts. Auto-advance now skips straight to the next unlistened
   place instead of walking sequentially. Settings has a new "reset
   listened places" button.

3. TtsManager is now keyed by place id: calling speak() for the place
   that's already playing just re-attaches the onDone callback instead
   of restarting via QUEUE_FLUSH. Fixes opening a place's detail
   screen while the guide list is already narrating it restarting
   playback from the beginning. Marking a place "listened" now also
   lives in TtsManager itself (on natural onDone, not onError/onStop),
   so it's correct regardless of which screen was driving playback.

4. Settings now shows "Listened: X of Y (Z%)" against the total place
   count for the city.

5. Search radius changed from 10km to 2km (client default in
   PlacesRepository/MapViewModel, and the backend's own default for
   consistency) — but the result count is uncapped, same as before;
   every place within the radius is returned regardless of how many
   that is.

Caught a real bug while testing the "skip listened" change: the new
listenedPlaceIds collector ran in a separate coroutine that hadn't
necessarily delivered its first value before the initial loadNearby()
call, so freshly-loaded listened state could be missed on cold start.
Fixed by awaiting listenedPlaceIds.first() synchronously before the
first load, with a separate .drop(1) collector for later changes
(e.g. the reset button). Also hardened GuideViewModelTest with
try/finally around viewModelScope.cancel() — a failing assertion was
skipping cleanup and turning into a 5+ minute hang instead of a fast
failure, since the 60s re-scan loop was never cancelled.

Verified: testDebugUnitTest passes (4/4), assembleDebug produces a
working APK, sent to Telegram. Backend pytest still passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 07:44:12 +00:00
..
__init__.py Add FastAPI backend with PostGIS-backed nearby search 2026-07-09 18:31:29 +00:00
cities.py Add structured logging throughout the backend 2026-07-09 20:10:10 +00:00
health.py Add FastAPI backend with PostGIS-backed nearby search 2026-07-09 18:31:29 +00:00
nearby.py Persist listened places, fix restart-on-reopen bug, add stats/reset 2026-07-10 07:44:12 +00:00
places.py Add structured logging throughout the backend 2026-07-09 20:10:10 +00:00