Repository navigation
fix: preserve yearly streak gap repairs through recalculation - #818
Merged
Merged
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes: Reviewed all six changed files and traced repair eligibility, persisted recalculation, streak reads/history, and consumers of the shared lookback.
- Shared lookback: Extends streak history to 732 days so yearly-gap repairs retain their completed predecessor through the next annual occurrence.
- Schedule generation: Generates contiguous bounded slices without changing the public schedule range cap or recurrence anchors.
- Regression coverage: Adds annual and every-365-day repair cases, persisted recalculation checks, history continuation, and slice-boundary tests; updates the documented limit.
- Verification: All 297 focused tests passed across scheduling, history, feature explanations, gap repair, streak calculation, automatic freezes, goal consumers, and validation. The full solution test suite was not rerun during this review.
gpt-6.1-sol | 𝕏
There was a problem hiding this comment.
✅ No new critical or high-severity issues found.
Reviewed changes: Reviewed the delta since the previous Pullfrog review, with the complete PR diff and affected consumers as context.
- Separated the limits: Added
StreakRepairWindowDaysfor the 732-day streak calculation, repair, and history paths while retaining the ordinary 365-day consumer and validator limits. - Strengthened boundary coverage: Added Feb 29 repair and persisted-recalculation cases, checked predecessor inclusion at 732 versus 733 days, and pinned the existing repair-request count limit.
- Verified the changes: All 240 focused tests passed across scheduling, history, validation, feature explanations, gap repair, recalculation, automatic freezes, and selected goal consumers. The full solution suite was not rerun during this review.
gpt-6.1-sol | 𝕏
|
thomasluizon
added a commit
that referenced
this pull request
Oct 20, 2026
…e sub-habits and referral Pro days (#746) (#829) * fix: preserve yearly streak gap repairs through recalculation (#818) * fix: preserve yearly streak gap repairs through recalculation * fix: isolate yearly streak repair history window (cherry picked from commit 90c5320) * fix: expose discounted Stripe billed amount per period (#819) * fix: expose discounted Stripe billed amount per period * fix: isolate optional recurring Stripe billing preview (cherry picked from commit 0460a54) * Make sub-habits free across every creation path (ORB-231) (#824) * feat: make sub-habits available to every plan (ORB-231) * test: track free sub-habits in migration model expectations * fix: preserve Pro slip alerts when creating free sub-habits (cherry picked from commit c9d22a6) * Grant atomic referral Pro days (ORB-22) (#823) * feat: grant atomic referral Pro days (ORB-22) * fix: preserve referral transaction and billing context * fix: preserve bonus Pro access across provider changes (cherry picked from commit 7b4e9af) * test: retain combined migration assertions after main carry * fix: include one-time coupons in billed amount preview (#830) (cherry picked from commit 377eca5)
thomasluizon
added a commit
that referenced
this pull request
Oct 20, 2026
* fix: preserve yearly streak gap repairs through recalculation * fix: isolate yearly streak repair history window (cherry picked from commit 90c5320)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Fixes thomasluizon/orbit-tickets#483.
A yearly gap could not be repaired because the completed predecessor fell outside the streak engine's history. Repair eligibility, the response, and subsequent recalculations now share a 732-day window. Two annual intervals retain the predecessor through the next yearly occurrence.
src/Orbit.Application/Common/AppConstants.cswidens the existing shared constant.HabitScheduleService.csgenerates the streak schedule in contiguous slices whose endpoint difference never exceeds the unchangedMaxRangeDaysof 366.ComputeStreakAsOfalready walks the supplied window without another cap, so it now receives the complete wider schedule. The streak feature explanation documents the same limit. Request and response contracts are unchanged; no migration is required. The build regeneratedsrc/Orbit.Api/openapi.jsonwithout a diff.The service tests replace the yearly refusal case with leap-year, ordinary-year, and every-365-day repair cases. A handler test spends the freeze, reads the real streak response, recalculates immediately, the next day, or at the next annual occurrence, saves the user, and reloads the persisted streak from a separate in-memory context. Schedule tests cover the slice boundaries and unchanged public range cap; a history handler test covers the repaired yearly run and its next completion.
Assumptions
to - frommay be 366 days; rejected changingMaxRangeDaysor its endpoint semantics.GetStreakHistoryQueryuses the new 732-day window rather than 365 so its chart retains the repaired yearly predecessor. Goal queries, notifications, freeze batch loading, and validator limits retain 365 days.Blast radius
GetGoalMetricsQuery,GetGoalReviewQuery, andGoalReviewTool: verified their filtered habit-log includes take the shared constant. They now prefetch 732 days without extra round trips; these database filters do not pass through the schedule range cap. Their existing handler/tool tests pass.GetStreakHistoryQuery: its seed uses the same constant and its schedule now spans all slices. The new yearly history test preserves both the predecessor and the recent completion.UserStreakService: calculation, repair evaluation, gap discovery, and recalculation all retain the shared bound. New repair and persisted-recalculation cases pass alongside existing daily and weekly boundary cases.RepairStreakGapCommandValidator: its existing count bound follows the constant; the bank, monthly allowance, domain guards, and service eligibility checks still apply. Existing validator cases pass.GoalProgressReadSyncer,GoalDeadlineNotificationService, andStreakFreezeAutoActivationServiceuse the same wider log-prefetch bound. Automatic freeze evaluation continues throughUserStreakService.EvaluateRepair; its service tests pass.Test evidence
Before implementation:
dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~YearlyGapWhosePredecessorSitsBeyondTheStreakWindow_IsRefused --verbosity quiet: the unchanged test passed, 1/1, with the defect present.dotnet test tests/Orbit.Infrastructure.Tests --no-restore --filter 'FullyQualifiedName~YearlyGapWhosePredecessorSitsBeyondTheOrdinaryWindow_IsRepairable|FullyQualifiedName~RepairedYearlyGap_RecalculationPersistsTheStreak' --verbosity quiet: all 6 strengthened cases failed before the fix. Repeating with--no-build --no-restore --verbosity normalconfirmed null eligibility and rejected handler repairs, rather than fixture failures.dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~StreakSchedule_LongRangeIncludesEveryDateAcrossSliceBoundaries|FullyQualifiedName~Handle_RepairedYearlyGap_RetainsPredecessorAndRecentCompletion' --verbosity normal: 4 failed and 1 passed before the fix. The 366-day boundary passed; longer schedules and repaired yearly history failed.After implementation:
dotnet build Orbit.slnx --verbosity quiet: passed, zero errors.StreakGapRepairTests,UserStreakServiceTests, andStreakFreezeAutoActivationServiceTests: 84 passed, including all 6 formerly failing repair cases.dotnet test tests/Orbit.Domain.Tests --verbosity quiet: exit 0, 767 passed.dotnet test tests/Orbit.Application.Tests --verbosity quiet: exit 0, 5,230 passed.dotnet test tests/Orbit.Infrastructure.Tests --verbosity quiet: exit 0, 3,776 passed.dotnet test tests/Orbit.Analyzers.Tests --no-build --no-restore --verbosity quiet: exit 0, 40 passed.dotnet build Orbit.slnx --nologodotnet test tests/Orbit.Domain.Tests --nologodotnet test tests/Orbit.Application.Tests --nologodotnet test tests/Orbit.Infrastructure.Tests --nologoRegression proof used:
dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~RepairStreakGapCommandValidatorTests --nologoRepairDateCount_RetainsTheOrdinaryLimitfailed before the fix because 365 dates were accepted: exit 1.Yearly and Feb 29 repairs survive persisted recalculation. Range-slicing tests pass. Generated OpenAPI files are unchanged.
Manual steps
None.
NEEDS_DECISION: May the eight formatter-required whitespace hunks remain (recommended), since removing them conflicts with the mandatory pre-commit formatter?