Skip to content

feat(chores): show when a chores history entry was actually recorded - #169

Merged
jherforth merged 3 commits into
jherforth:mainfrom
mrramam:feat/chore-history-logged-time
Sep 14, 2026
Merged

jherforth merged 3 commits into
jherforth:mainfrom
mrramam:feat/chore-history-logged-time

Conversation

@mrramam

@mrramam mrramam commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

**Depends on #168 ** . This branch contains that commit; please merge it first.

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_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 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 through SELECT *, 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.

mrramam and others added 2 commits September 13, 2026 15:47
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>
@mrramam

mrramam commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

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>
@jherforth

Copy link
Copy Markdown
Owner

Resolved the conflict and merging. Note of what I did, so nothing lands unannounced.

Why it conflicted

#168 landed on main as a squash, so this branch's own copy of that commit (b1d78fd) collided with it rather than being recognised as the same work. Nothing was wrong with the branch.

Before resolving I checked the shared files were not amended during #168's review — utils/sqliteTime.js and tests/sqliteTime.test.js are byte-identical between b1d78fd and main, so taking either side loses nothing.

What I resolved

The only real conflict was in apiEndpoints.test.js, where both sides append a test in the same place: main's device-updateTime test from #168, and this branch's chore-history created_at test. Both are kept — they cover different endpoints.

server/index.js auto-merged. I checked it by hand rather than trusting it: one import of the helpers, #168's four call sites intact (getDeviceUpdateTimeMs, /api/devices, and both Google-status fields), and withIsoCreatedAt applied at all three chore-history call sites as your description says.

Verification

  • server 224/224, client 272/272, translation parity, production build clean
  • CI green on the resolved branch
  • The formatter tests pass under America/Los_Angeles and Asia/Tokyo as well as UTC — worth confirming given your point that a formatter test which passes in every zone isn't testing the thing that breaks

On the code itself

formatLoggedAt is a nice piece of work. Rendering in the server's timezone via the existing getServerTimezoneSync seam is the right call — a phone in another zone can't contradict the Date column beside it. And showing the day only when it differs from the row date targets exactly the case a reader would misread: a chore dated yesterday, checked off after midnight.

One thing I did not change: the new "Logged" header and the explanatory line are hardcoded English — but so are User, Title, Date, Clams and Actions. ChoreHistoryTab has no useTranslation at all, so yours are consistent with the component rather than a regression. Translating the whole tab is a separate job. Good catch carrying data-label through, so the column works in the stacked mobile layout too.

@jherforth
jherforth merged commit 49051ed into jherforth:main Sep 14, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in HomeGlow Kanban Sep 14, 2026
@mrramam
mrramam deleted the feat/chore-history-logged-time branch September 14, 2026 17:02
@jherforth jherforth added the enhancement New feature or request label Sep 15, 2026
@jherforth jherforth added this to the 1.9 milestone Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants