feat(chores): show when a chores history entry was actually recorded - #169
Conversation
The Devices tab showed Last Updated about seven hours ahead of the truth in US/Pacific. SQLite's CURRENT_TIMESTAMP is always UTC and ignores TZ, and it writes "YYYY-MM-DD HH:MM:SS" with no zone marker. V8 reads a space-separated, offset-less timestamp as local time, so every consumer landed an offset away from the stored instant. The digits look plausible, which is why it survived. Container and host timezone configuration cannot fix this; the backend already has the correct TZ and still writes UTC, as specified. Timestamps now become ISO-8601 with an explicit Z at the API boundary, through one helper, rather than each caller knowing the storage convention. Storage is unchanged, so no migration and no mixed formats in ORDER BY. Three consumers were reading the ambiguous form: - GET /api/devices — the Last Updated column, the reported symptom. - getDeviceUpdateTimeMs — feeds the Last-Modified header on three device endpoints, which advertised a time in the future west of UTC. ETag drives the 304, so this was wrong rather than harmful. - GET /api/connections/google/status — "Connected <time>", same offset. Found by sweeping for other columns written by CURRENT_TIMESTAMP. Unusable values return null rather than the raw string: a client renders null as Unknown, but renders an ambiguous string as a confidently wrong local time. The tests set a non-UTC zone and assert the naive parse is wrong there before asserting the helper is right — in a UTC process, which is where CI runs, both the bug and its fix are invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The history tab showed only the date an entry counts for, so there was no way to tell when a chore was checked off — or that a row dated yesterday was recorded after midnight. chore_history.created_at has always held this; nothing was storing it wrong and no migration is needed. It simply was not surfaced. It is written by CURRENT_TIMESTAMP, so it has the same defect the Devices tab had: UTC with no zone marker, which every consumer reads as local time. Surfacing it without converting would have shipped a fresh instance of the bug this branch is stacked on. All three history endpoints now emit an instant; two of them returned it already via SELECT *, unnoticed. Rendered in the server's timezone rather than the viewer's, so a travelling phone cannot show a time that contradicts the date column beside it. The date is shown only when it differs from the row's own date — repeating it on every row is noise, and the mismatch case is the one a reader would otherwise get wrong. The endpoint test was checked against a deliberately reverted conversion and does fail without it. The formatter's tests fail under a wrong server zone, so they are testing what they claim to. Strings are hardcoded English, matching every other string in this component. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Looks like DB schema test timed out - no schema work in this PR so probably not here... wanna kick off CI again to confirm (I can't). |
jherforth#168 has landed on main as a squash, so this branch's own copy of that commit collided with it. The shared files -- utils/sqliteTime.js and its unit tests -- are byte-identical between the two, so nothing from the review of jherforth#168 is lost by taking either side. The only real conflict was in apiEndpoints.test.js, where both sides append a test to the same place: main's device-updateTime test from jherforth#168 and this branch's chore-history created_at test. Both are kept. server/index.js auto-merged; checked by hand rather than trusted -- one import of the helpers, jherforth#168's four call sites intact, and withIsoCreatedAt applied at all three chore-history endpoints. Verified: server 224/224, client 272/272, translation parity, build clean, and the formatter tests pass under America/Los_Angeles and Asia/Tokyo as well as UTC. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Resolved the conflict and merging. Note of what I did, so nothing lands unannounced. Why it conflicted#168 landed on Before resolving I checked the shared files were not amended during #168's review — What I resolvedThe only real conflict was in
Verification
On the code itself
One thing I did not change: the new "Logged" header and the explanatory line are hardcoded English — but so are |
The chore history tab shows only the date an entry counts for, this adds a column for when it was marked complete.
chore_history.created_athas always held this. Nothing was storing it wrong and no migration is needed; it simply was not surfaced.It is written by
CURRENT_TIMESTAMP, so it carries the same defect the Devices tab had - UTC with no zone marker, read as local by every consumer. Surfacing it without the conversion would ship a fresh instance of that bug, which is why this depends on the fix above. All three history endpoints now emit an instant; two were already returning the ambiguous value throughSELECT *, unnoticed because nothing displayed it.The column is headed Logged rather than "Checked off" because the tab also carries adjustments, spends and bonuses.
Testing. Server suite passes with one new endpoint test; client suite passes with five new formatter tests, which fail under a wrong server timezone — a formatter test that passes in every zone would not be testing the thing that goes wrong.