One calm pending row on Home, failures in Activity, safe retries
What changed and why
Home could show "Catching up on earlier audio" and a separate "Processing… taking longer than usual · Try Again" card at the same time for the same backlog. The card was driven by a fixed 2-minute client timer, so ordinary slow work looked like a failure.
- Home: one calm "on the way" row. Conversations still processing, recordings syncing, and server-side pending work (
GET /v1/pending-work) all fold into it. There are no per-conversation processing cards and no client-side timeout warning. A just-captured conversation stays visible under a folder or starred filter. - Failures: finalization dead-letters write one attention item per failed generation, in the same transaction. Activity shows them in a new Needs attention section with Try Again / Retry all. Recordings that failed on the phone link to Offline Sync from there. The bell lights for new recaps (as before) or, in the warning color, for unseen attention items. Seen state is stored on the server, so the phone and the Mac agree.
- Retry safety:
- Reprocessing runs at most once at a time per conversation generation. Concurrent callers join the active attempt, and later requests start a new attempt.
- Failed attempts can be retried, and an abandoned attempt can be reclaimed after a 30-minute lease.
- An
Idempotency-Keyreplays its original attempt. The existingPOST /v1/conversations/{id}/reprocessuses the same claim, so phone, Mac, and Retry all can't double-pay.
- Removed: the retired
ProcessingConversationWidget,processing_timeout.dart, the unreachable processing page, and their orphaned strings.
This ships ungated, by product decision. The app tolerates a backend without the new endpoints (404 is treated as empty), so app and backend can roll out in either order.
Backend contract and limits: backend/docs/pending-work.md.
Line-Count-Exception: backend/database/conversation_finalization_jobs.py | 2652 -> 2658 | attention item must be written inside the existing dead-letter transaction
Line-Count-Exception: backend/routers/conversations.py | 2318 -> 2355 | reprocess must claim before paid work at the existing endpoint; claim logic itself lives in database/processing_attention.py
Product invariants affected
none
How it was verified
All results below were measured on this branch in the task worktree.
make preflight: 44 checks passed.- App:
TZ=UTC bash app/test.sh(full hermetic suite) → 5891 passed, 16 skipped, 0 failed. This ran before the final cleanup commit. After it, the affected suites were re-run: Activity (17), Process Now (4), Home pending-row and new pending-work tests. - App:
app/scripts/analyze_ratchet.shpassed.python3 scripts/l10n.py check→ ok, 49 locales. - Backend focused pytest:
test_pending_work.py,test_reprocess_tombstone_guard.py,test_reprocess_app_selection.py,test_conversation_finalization_jobs.py,test_live_capture_lifecycle.py→ all passed.- That covers exactly-once item creation, resolution, concurrent claim (one paid call), sequential reprocess (both run), different
app_id, key replay, retry after failure, and lease reclaim.
- Backend: typecheck 0 errors, Black clean, dev/prod runtime_env validators passed.
- Not verified: live behavior on a device or against a deployed backend, and Firestore contention against an emulator.
Tests
- App:
app/test/services/pending_work_store_test.dart,app/test/widgets/pending_work_test.dart,app/test/widgets/home_processing_pending_row_test.dart(replaces the processing-card placement test),app/test/pages/activity/activity_test.dart. - Backend:
backend/tests/unit/test_pending_work.py,backend/tests/unit/test_reprocess_tombstone_guard.py.
UI evidence
UI-Evidence: none -- no device capture was taken. Home and Activity states are covered by widget tests; the design mockups were reviewed before implementation.
Failure class (fixes)
Failure-Class: none
🤖 Generated with Claude Code