Habit days are set with an absolute PUT /habits/{id}/days/{date}: send the
amount, the server stores it. Absolute rather than incremental on purpose, so
an offline retry can never double-count.
When notes came along, the same endpoint took an optional note. The first
version did the obvious thing:
row.amount = body.amount
row.note = (body.note or "").strip() or None
Which means ticking a day you’d already written about erased what you wrote.
The app’s tick button sends {"amount": 1} with no note, and None overwrote
the text.
Not sent is not the same as empty
Pydantic records which fields the client actually sent in model_fields_set.
That gives three honest cases:
notenot sent: keep whatever is there.note: ""sent empty: clear it.note: "...": set it.
if "note" in body.model_fields_set:
row.note = (body.note or "").strip() or None
A day that’s only a note
The same change raised a second question: what if you untick a day that has a note? Before, amount zero deleted the row. Now a day can hold just a note (ill, rested): amount zero, so not done, but kept. Clearing both the amount and the note removes it.
Tested as a sequence
The test walks through what a person would do: write a note, tick again, untick, and finally clear everything, checking the stored day after each step. The bug was in the sequence, so the test is one.
AlzyWelzy
Building PaceStreak. GitHub
Related
DesignA trend chart for every habit, using the colours it already had
A small bar row and a keep-rate percentage on every habit's detail page - kept in lime, missed in flame, because the week strip and calendar markers already meant that, and a new chart is the wrong place to invent a third colour convention.
1 min
Security & PrivacyBuilding auth for an app that holds health data
RS256 access tokens held only in memory, rotating refresh tokens with reuse detection, CSRF on cookie transport, TOTP with replay protection, and the gaps we have written down rather than hidden.
4 min
ProductHabits on chosen days, and pausing just one
A habit can now be planned for specific weekdays, and paused on its own while everything else carries on. Both had to mean the same thing to the streak, the reminders and the badge.
2 min