Skip to content

fix(api): daily summary uses DueDate-authoritative due-today (no stale pending habits) - #190

Merged
thomasluizon merged 1 commit into
mainfrom
fix/summary-due-today-parity
Jun 6, 2026
Merged

fix(api): daily summary uses DueDate-authoritative due-today (no stale pending habits)#190
thomasluizon merged 1 commit into
mainfrom
fix/summary-due-today-parity

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

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, LogHabitCommand advances its DueDate to 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 labeled pending unless each child had its own log in today's range. A sub-task resolved on its real occurrence (e.g. a weekly weigh-in whose DueDate has advanced past today) therefore reappeared as pending under the routine.

The fix

  • Thread the authoritative userToday (already computed in GetDailySummaryQueryHandler from the user's timezone) into ISummaryService.GenerateSummaryAsync + AiSummaryService.

  • Replace the pattern check with the same gate the Today list uses (GetHabitScheduleQuery.DetermineOverdueStatus / MapChildren):

    IsHabitDueOnDate(h, userToday) || HasMissedPastOccurrence(h, userToday) || IsDoneInRange(h, dateFrom, dateTo)
    

    The IsDoneInRange clause 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 CapToSentence cap and the model (gpt-4.1-mini) are unchanged.

The habit-selection logic was extracted into a small private SelectScheduledHabits seam so it can be unit-tested directly (mirrors the existing reflection-based test style for BuildHabitSection/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 whose DueDate advanced 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.

GetDailySummaryQueryHandlerTests updated for the new userToday parameter.

Validation

  • dotnet build Orbit.slnx0 errors.
  • dotnet test tests/Orbit.Infrastructure.Tests971/971 passed.
  • dotnet test tests/Orbit.Application.Tests1742/1742 passed.

Follow-up to the #182/#186 humanize-summary work; no tracked issue.

🤖 Generated with Claude Code

…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>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@thomasluizon
thomasluizon merged commit 63f60f5 into main Jun 6, 2026
4 checks passed
@thomasluizon
thomasluizon deleted the fix/summary-due-today-parity branch June 6, 2026 02:41
thomasluizon added a commit that referenced this pull request Jun 27, 2026
…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>
thomasluizon added a commit that referenced this pull request Jun 27, 2026
…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>
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.

1 participant