Skip to content

fix(#1079): retry plan generation on transient failures, fix empty-plan UX#1080

Open
lokhor wants to merge 4 commits into
mainfrom
fix-meal-plan-stall-1079
Open

fix(#1079): retry plan generation on transient failures, fix empty-plan UX#1080
lokhor wants to merge 4 commits into
mainfrom
fix-meal-plan-stall-1079

Conversation

@lokhor
Copy link
Copy Markdown
Collaborator

@lokhor lokhor commented Jun 4, 2026

Closes #1079

Problem: After collecting all 5 required slots, users saw "I still need to rebuild your meal plan draft" instead of the generated plan. State collapse: markGenerationFailure() moved session to PLAN_REVIEW with empty days, clearing pendingGenerationKind, so follow-up turns showed confusing recovery copy.

Changes:

  1. Retry loop in generatePlanForReview() — 2 total attempts on blank output or JSON parse failure. No retry on PLAN_VARIETY_REPAIR_FAILED (already has internal retries). markGenerationFailure() only called after all attempts exhausted. Cancellation check before each attempt and before savePlanDraft().

  2. Empty-plan UX recovery — All surfaces (planReviewPrompt(), helpPrompt(), planReviewSuggestions(), activity subtitle, currentPlanReply()) now show actionable recovery copy instead of the confusing "rebuild" message. "Show current plan" removed from suggestions when days are empty.

  3. Stale-draft preservationplanGenerationFailedMessage() checks snapshot.days.isNotEmpty(): when old days exist from a preference-edit flow, message says "Your previous draft is still shown — say 'generate recipes' to try again."

  4. Structured logging — Each retry attempt logs session ID, attempt number, and error code.

  5. Tests — 8 new tests + 1 updated test covering blank retry success, invalid JSON retry success, terminal exhaustion (both blank and invalid JSON), variety repair no-retry, empty-plan activity/suggestions recovery state, and stale-draft visibility after preference-edit failure.

lokhor added 2 commits June 2, 2026 22:37
…roller

When Android TTS drops an onDone/onError callback (audio routing
interference, engine glitch), completePlaybackIfDrained waits forever
with non-empty utteranceIds and never emits SpeakingStopped, leaving
the UI stuck on 'Speaking response...'.

Add a 30-second safety-net timeout that fires after the final chunk is
queued. If TTS callbacks haven't drained all utterances by then, the
timeout force-cleans the playback state and emits SpeakingStopped.

Cancels automatically on natural completion or explicit stop().
…an UX messages

- Add MAX_PLAN_GENERATION_ATTEMPTS=2 retry loop in generatePlanForReview()
- Retry on PLAN_NO_OUTPUT and PLAN_JSON_INVALID; no retry on variety repair
- markGenerationFailure() only after all attempts exhausted
- planGenerationFailedMessage() helper with stale-draft detection
- All empty-plan UX surfaces now show actionable recovery copy
- Structured logging with sessionId per attempt
- 8 new tests + 1 updated test covering retry, recovery, and stale-draft scenarios
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Debug APK ready

Download app-debug.apk

Commit: 35c2a19 - Build #2098

Updated on each push. Removed when PR is merged or closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: meal planning stalls

2 participants