fix(frontend): Weekly view must always send an explicit date to /grade #104

Merged
admin_emi merged 4 commits from fix/weekly-utc-today-date into dev 2026-07-26 06:20:20 +00:00
Owner

The bug

Reported from Kaunas (UTC+3): at ~05:00 local on the 26th, the Weekly tab renders the 25th. The Day Detail page, asked for the same date, is correct.

Cause

runGrade() in frontend/static/app.js deliberately dropped the date= parameter from the /api/v2/grade request whenever the selected date equalled the viewer's local today — an old cache-key optimisation, so the URL would match the dateless URL the cross-view prefetch seeded.

With no date supplied, the backend falls back to its own clock, which is UTC (api_grade's date default in backend/web/app.py). Between local midnight and UTC midnight, any viewer east of UTC asks for today and is served yesterday. Every other date works, because those are sent explicitly. day.js always sends the date, which is exactly why Day Detail stayed correct.

The cache-key "optimisation" was the other half of the same bug rather than a reason to keep it: cache.js's viewFetches() seeded the grade slice under that same dateless URL, stamped fresh for the viewer's local day regardless of which day the server actually resolved — so a same-tab navigation that had already prefetched the cell (e.g. arriving from the Day page) could serve the stale, server-resolved slice straight out of IndexedDB with no live request going out at all.

The fix

  • static/app.jsrunGrade() now builds exactly one URL and always includes date=. dateInput.value can come back empty from a cleared native date field, so it falls back to todayISO() rather than ever sending date= empty (an empty value hits the identical server-side UTC fallback). day.js already guards the same input the same way.
  • static/cache.js — the prefetch is kept working rather than dropped: viewFetches() seeds the grade slice under the dated URL (matching what the view now requests), the /cell bundle request states the client's local today, and seeding uses bundle.today — the date the server actually resolved — so the seeded key stays byte-identical to the per-view request even if the two ever disagree across a midnight boundary.
  • tests/unit/test_app_js_grade_date.py — regression guard. There is no JS/DOM test harness in this repo to drive app.js and assert on the real fetch() URL, so this treats the shipped source as data over the existing served-asset route (GET {B}/app.js) and asserts the structural property: one grade URL, always with an explicit non-empty date=. It fails against the pre-fix source. A source guard, not a substitute for a behavioural test.

Backend needs no change for the correctness fix — /grade already honours an explicit date.

## The bug Reported from Kaunas (UTC+3): at ~05:00 local on the 26th, the **Weekly** tab renders the **25th**. The Day Detail page, asked for the same date, is correct. ## Cause `runGrade()` in `frontend/static/app.js` deliberately **dropped** the `date=` parameter from the `/api/v2/grade` request whenever the selected date equalled the viewer's local today — an old cache-key optimisation, so the URL would match the dateless URL the cross-view prefetch seeded. With no `date` supplied, the backend falls back to its own clock, which is **UTC** (`api_grade`'s `date` default in `backend/web/app.py`). Between local midnight and UTC midnight, any viewer east of UTC asks for today and is served yesterday. Every *other* date works, because those are sent explicitly. `day.js` always sends the date, which is exactly why Day Detail stayed correct. The cache-key "optimisation" was the other half of the same bug rather than a reason to keep it: `cache.js`'s `viewFetches()` seeded the grade slice under that same dateless URL, stamped fresh for the viewer's local day regardless of which day the server actually resolved — so a same-tab navigation that had already prefetched the cell (e.g. arriving from the Day page) could serve the stale, server-resolved slice straight out of IndexedDB with no live request going out at all. ## The fix - **`static/app.js`** — `runGrade()` now builds exactly one URL and always includes `date=`. `dateInput.value` can come back empty from a cleared native date field, so it falls back to `todayISO()` rather than ever sending `date=` empty (an empty value hits the identical server-side UTC fallback). `day.js` already guards the same input the same way. - **`static/cache.js`** — the prefetch is kept working rather than dropped: `viewFetches()` seeds the grade slice under the dated URL (matching what the view now requests), the `/cell` bundle request states the client's local `today`, and seeding uses `bundle.today` — the date the server actually resolved — so the seeded key stays byte-identical to the per-view request even if the two ever disagree across a midnight boundary. - **`tests/unit/test_app_js_grade_date.py`** — regression guard. There is no JS/DOM test harness in this repo to drive `app.js` and assert on the real `fetch()` URL, so this treats the shipped source as data over the existing served-asset route (`GET {B}/app.js`) and asserts the structural property: one grade URL, always with an explicit non-empty `date=`. It fails against the pre-fix source. A source guard, not a substitute for a behavioural test. Backend needs no change for the correctness fix — `/grade` already honours an explicit `date`.
admin_emi added 2 commits 2026-07-26 06:17:29 +00:00
fix(frontend): Weekly view must always send an explicit date to /grade
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 43s
PR build (required check) / gate (pull_request) Successful in 2s
44b02d985a
admin_emi added 1 commit 2026-07-26 06:18:40 +00:00
fix(frontend): keep the /cell prefetch seeding the dated grade URL
All checks were successful
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / build-frontend (pull_request) Successful in 29s
PR build (required check) / gate (pull_request) Successful in 3s
ab4ba54575
admin_emi added 1 commit 2026-07-26 06:19:21 +00:00
test: correct the regression guard's docstring about the test surface
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Successful in 33s
PR build (required check) / gate (pull_request) Successful in 2s
2d45af8d3a
admin_emi merged commit 5b7e30dad9 into dev 2026-07-26 06:20:20 +00:00
admin_emi deleted branch fix/weekly-utc-today-date 2026-07-26 06:20:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Jinemi/thermograph#104
No description provided.