fix(api): daily summary uses DueDate-authoritative due-today (no stale pending habits) - #190
Conversation
…e pending habits) A fresh AI daily summary (fromCache:false) re-listed habits as still-pending that the user had already logged, even though the Today screen showed them done. The summary decided "due today" with a frequency-PATTERN check while the Today list is DueDate-authoritative: after logging a good recurring habit, LogHabitCommand advances its DueDate to the next occurrence, so the Today list hides it but the summary's pattern still fired today. Fix: thread the authoritative userToday into ISummaryService.GenerateSummaryAsync and gate selection with the same predicate the Today list uses -- IsHabitDueOnDate(h, userToday) || HasMissedPastOccurrence(h, userToday) -- keeping habits completed in range so they still appear, labeled done. Children are now gated by the same predicate on their own merit instead of being force-included whenever the parent qualified, which is what surfaced resolved sub-tasks (e.g. a weekly weigh-in) as pending under a due routine. The prompt also celebrates a fully-completed day without inventing tasks and asks for natural, fluent target-language phrasing. Regression tests fail on the old force-included-children logic and pass after. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Clean, focused bug fix. The root cause was well-diagnosed: the summary's frequency-pattern check and the Today list's DueDate-authoritative gate were two different sources of truth, causing logged recurring habits (and their children) to re-appear as pending after LogHabitCommand advanced their DueDate. Threading the already-timezone-correct userToday into SelectScheduledHabits and replacing the pattern check with IsHabitDueOnDate || HasMissedPastOccurrence || IsDoneInRange correctly unifies them. The child-gating fix (each child now qualifies on its own merit rather than inheriting parent eligibility) is the key correctness improvement. Regression tests are precise — they fail on the old force-include logic and pass after — and the interface/signature change is consistent across implementation, handler, and all test call sites.
…ator, feed leak, period 400 (#193, #190) Resolves claude-review findings: add UnblockUserCommandValidator (HIGH); filter blocked users from GetCheersQuery both directions (HIGH); drop opted-out actors from the friend feed (MED); MaximumLength on friend-request handle/referralCode (MED); recap period set now matches the resolver via a shared IsKnownPeriod (single source of truth) + an invalid period returns 400 not 500 at the recap/retrospective endpoints (MED). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing (#261) * feat(gamification): free-tier rebalance + infinite levels + recap (#186, #190) Reversible gamification_free_tier AppFeatureFlag (default off) unlocks streak/XP/level + streak-freeze auto-activation for free users. Infinite levels via 100*L^2 curve, continuous past level 10. New half_year_hero/streak_titan achievements + reserved first_cheer. GET /api/gamification/recap returns metrics-only retrospective + referral shareDeepLink, ungated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(astra): warm persona tone + soften act-immediately framing (#183) New EncouragingToneSection (Order 150) + softened CoreIdentitySection so clarify-first coexists with direct action; destructive/bulk route through existing confirmation-card gating. Reuses shipped resolve + pendingOperations infra; gpt-4.1-mini unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(social): social foundation - friendships, cheers, feed, moderation, handles (#193) 5 entities + User.Handle/SocialOptIn + migration w/ deterministic handle backfill. Endpoints (request/accept/remove/list/feed/cheer/block/report + set-handle + opt-in), SocialAccessGuard, rate limits. OpenAI moderation (fail-open on outage). FriendFeedEvent write-pipeline: streak hook (all users) + achievement hook (Pro) + keyset read. first_cheer wired; FCM pushes; export + deletion purge. Backend + shared only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): setup-checklist flags, bulk-create tags, template-pack support (#187, #189) User onboarding-checklist completion flags (ride profile payload, auto-set from signals) + onboarding achievement hook (#189). BulkCreateHabits accepts per-item tags resolved/created by name for starter packs (#187). Migration AddOnboardingChecklistFlags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): address PR #261 review — block-filter cheers, unblock validator, feed leak, period 400 (#193, #190) Resolves claude-review findings: add UnblockUserCommandValidator (HIGH); filter blocked users from GetCheersQuery both directions (HIGH); drop opted-out actors from the friend feed (MED); MaximumLength on friend-request handle/referralCode (MED); recap period set now matches the resolver via a shared IsKnownPeriod (single source of truth) + an invalid period returns 400 not 500 at the recap/retrospective endpoints (MED). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): #261 round-2 review — block/unblock rate limits, GetFriends validator, ordering, cheer-ownership Rate-limit block/unblock (50/24h); add GetFriendsQueryValidator (UserId NotEmpty); check target existence before BlockedUser.Create; verify a reported CheerId involves the reported user (else CheerNotFound). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bug
A fresh AI daily summary (
fromCache:false) listed habits as still-pending (e.g. "café da manhã", "pesar na balança") even though the user had logged them and the Today screen showed everything done.Root cause: the summary decided "due today" with a frequency-pattern check (
HabitScheduleService.GetScheduledDates(...).Count > 0), while the Today list is DueDate-authoritative. After logging a good recurring habit,LogHabitCommandadvances itsDueDateto the next occurrence. The Today list hides it, but the summary's pattern still fired — and, worse, children of a due routine were force-included regardless of their own schedule and labeledpendingunless each child had its own log in today's range. A sub-task resolved on its real occurrence (e.g. a weekly weigh-in whoseDueDatehas advanced past today) therefore reappeared as pending under the routine.The fix
Thread the authoritative
userToday(already computed inGetDailySummaryQueryHandlerfrom the user's timezone) intoISummaryService.GenerateSummaryAsync+AiSummaryService.Replace the pattern check with the same gate the Today list uses (
GetHabitScheduleQuery.DetermineOverdueStatus/MapChildren):The
IsDoneInRangeclause keeps habits completed today in the summary so they're acknowledged as done (never dropped).Children are gated by the same predicate on their own merit, instead of being force-included whenever the parent qualified.
Prompt: added a rule to warmly celebrate a fully-completed day without inventing/suggesting any remaining task, and an instruction to write natural, fluent, grammatically-correct text in the target language.
The 200-char
CapToSentencecap and the model (gpt-4.1-mini) are unchanged.The habit-selection logic was extracted into a small private
SelectScheduledHabitsseam so it can be unit-tested directly (mirrors the existing reflection-based test style forBuildHabitSection/BuildSummaryPrompt).Tests (fail-before / pass-after)
New regression tests in
AiSummaryServiceTests. Verified red on the old force-included-children logic, green after:SelectScheduledHabits_WeeklyChildResolvedOnItsOccurrence_NotSelected— a weekly child whoseDueDateadvanced past today is no longer selected.SelectScheduledHabits_RoutineWithNonDueChild_DoesNotSurfaceChildAsPending— a monthly child not due today is never listed as a pending sub-task under a due parent.Additional behavior guards (green both ways): daily/weekly good habit logged today stays and is labeled done, a routine whose sub-tasks were all logged today surfaces no pending child, and an unresolved overdue habit still appears.
GetDailySummaryQueryHandlerTestsupdated for the newuserTodayparameter.Validation
dotnet build Orbit.slnx— 0 errors.dotnet test tests/Orbit.Infrastructure.Tests— 971/971 passed.dotnet test tests/Orbit.Application.Tests— 1742/1742 passed.Follow-up to the #182/#186 humanize-summary work; no tracked issue.
🤖 Generated with Claude Code