Habits Feature Guide
Guide to the Habits module — recurring habit scheduling with flexible frequencies, streak tracking, completion management, and reminders.
Overview
The Habits module lets users define recurring habits (daily, weekly, monthly, etc.) anchored to a specific datetime. Each habit tracks a streak, supports subtasks, and fires reminders. Completion state is date-scoped — marking a habit done on a given date does not affect other occurrences. The server computes the nextOccurrence for each habit based on its frequency and anchor datetime.
Architecture
The shared Habit model carries the nextOccurrence() computation logic so all platforms calculate recurrence consistently without a server round-trip.
Data Models
A habit with no explicit
HABIT_REMINDERrow still gets a “habit starting soon” push when its anchor occurrence falls withinUserSettings.defaultHabitReminderOffsetMinutes(default 15,0to disable). See Default reminder offsets.
Frequency Enum
| Value | Recurrence |
|---|---|
DAILY | Every day at the anchor time |
WEEKLY | Same day of week as the anchor |
BI_WEEKLY | Every two weeks, parity-matched to the anchor week |
MONTHLY | Same day of month, clamped to month-end |
YEARLY | Same month + day |
ONE_TIME | No recurrence — appears once |
Next Occurrence Calculation
nextOccurrence(referenceDate) is computed client-side from the anchor dateTime + frequency. Key rules:
DAILY: advance anchor by 1 day until ≥ referenceDateWEEKLY: advance by 7 daysBI_WEEKLY: advance by 14 days, week-parity preservedMONTHLY: same day of next month, clamped if month is shorter (e.g. Jan 31 → Feb 28)YEARLY: same date next year
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /habits | List habits (date range, pagination, withOverdue) |
POST | /habits | Create habit (dateTime required, frequency validated) |
GET | /habits/{id} | Get habit detail (date query param required) |
PATCH | /habits/{id} | Update habit |
DELETE | /habits/{id} | Delete habit |
PATCH | /habits/{id}/complete | Mark complete for a given dateTime |
PATCH | /habits/{id}/uncomplete | Undo completion for a given dateTime |
dateTime parameters use dd/MM/yyyy HH:mm format.
State Machine
Completion is scoped per occurrence date — marking done does not globally flag the habit, only the specific dateTime instance.
Client Behavior
Completion emits a domain event so the Dashboard and Calendar widgets refresh without a manual pull-to-refresh.
Key Files
| File | Purpose |
|---|---|
shared/.../models/Habit.kt | Habit model + nextOccurrence() recurrence logic |
shared/.../models/HabitReminder.kt | Reminder model |
habits/habits_domain/.../repository/HabitRepository.kt | Repository interface |
habits/habits_data/.../repository/HabitRepositoryImpl.kt | Repository implementation |
habits/habits_presentation/.../viewmodel/HabitViewModel.kt | ViewModel with MVI state |
server/.../routing/HabitRouting.kt | 7 REST endpoints with date validation |
Testing
- Unit:
nextOccurrence()edge cases — month-end clamping, bi-weekly parity, leap years,ONE_TIMEhabits after their date. - Integration: complete/uncomplete toggling per date; verify streak increments correctly on consecutive completions.
- Server: test
withOverduefiltering; habits past their date that are undone should surface when requested.
Troubleshooting
Habit not appearing for today
- Check that
dateTimeanchor is in the past or matches today. - Verify
frequencyis notONE_TIMEwith a past date. - Confirm the
startDate/endDaterange in the request covers today.
Streak not incrementing
- Streak only increments when
completeis called on consecutive occurrences with no gap. - Verify
lastDoneAtis being persisted — gaps reset the streak to 1.
Next occurrence wrong
- Check timezone offset —
dateTimeis stored and compared in the user’s local timezone. BI_WEEKLYparity is computed from the original anchor date; if the anchor was changed the parity may shift.
API Endpoints
All endpoints require JWT authentication and are prefixed with /api/v1. See server/README.md for full reference.
| Method | Path | Description |
|---|---|---|
GET | /habits | List habits (filterable by date, projectId, date range) |
POST | /habits | Create a habit |
GET | /habits/{id} | Get habit by ID |
PATCH | /habits/{id} | Update habit |
DELETE | /habits/{id} | Delete habit |
PATCH | /habits/{id}/complete | Mark habit complete for a datetime |
PATCH | /habits/{id}/uncomplete | Mark habit incomplete for a datetime |
Related Docs
- Domain & models — shared domain model reference
- UI & UX — habit UI flows and completion patterns
- Testing & QA — testing strategy