Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Heldt 4df5b0ec08 Prompt to reload when a new version is available
The service worker used to skipWaiting() on install and claim clients on
activate, so a new build's assets swapped in silently and a long-open tab
kept running stale JS. Switch to the standard update flow: the worker now
waits until the page sends it a SKIP_WAITING message, and the page shows a
"A new version is available — Reload / Later" banner when a new worker
reaches "installed" while one is already controlling the tab (that
controller check suppresses the first-install prompt).

Reload posts SKIP_WAITING and reloads on controllerchange (guarded against
a reload loop and against the initial clients.claim on a fresh install);
Later dismisses until the next update. Since browsers only auto-check on
navigation, also poll registration.update() hourly and on visibilitychange.
Bump the cache to v9 so the old cache is cleaned up on activate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:30:30 +00:00
+14 -26
View File
@@ -394,20 +394,23 @@
return total; return total;
} }
// Current state derived from the *latest* sleep event. The single source of function isCurrentlyAsleep(events) {
// truth for both the big clock and the "Currently" row. For two boundary const sorted = [...events].sort((a, b) => a.at - b.at);
// events sharing the same `at` (common once "now" events are minute-floored), let asleep = false;
// the one logged later (higher updatedAt) wins, so the tie resolves the same for (const e of sorted) {
// way everywhere it's read. if (e.type === "sleep-start") asleep = true;
else if (e.type === "sleep-end") asleep = false;
}
return asleep;
}
// Current state derived from the *latest* sleep event. Used by the live
// counter at the top of the page.
function currentSleepState(events) { function currentSleepState(events) {
let latest = null; let latest = null;
for (const e of events) { for (const e of events) {
if (e.type !== "sleep-start" && e.type !== "sleep-end") continue; if (e.type !== "sleep-start" && e.type !== "sleep-end") continue;
if (!latest || if (!latest || e.at > latest.at) latest = e;
e.at > latest.at ||
(e.at === latest.at && (e.updatedAt || 0) > (latest.updatedAt || 0))) {
latest = e;
}
} }
if (!latest) return { state: null, since: 0 }; if (!latest) return { state: null, since: 0 };
return { return {
@@ -600,7 +603,7 @@
const row = document.getElementById("currently-row"); const row = document.getElementById("currently-row");
const currently = document.getElementById("currently"); const currently = document.getElementById("currently");
if (currentSleepState(events).state === "asleep") { if (isCurrentlyAsleep(events)) {
row.hidden = false; row.hidden = false;
currently.textContent = "😴 Asleep"; currently.textContent = "😴 Asleep";
} else { } else {
@@ -1274,10 +1277,6 @@
let pendingType = null; let pendingType = null;
let notePhotoBlob = null; // pending blob for the dialog (not yet committed) let notePhotoBlob = null; // pending blob for the dialog (not yet committed)
let notePhotoURL = null; // current preview object URL let notePhotoURL = null; // current preview object URL
// Whether the user has manually touched the date/time fields. While false the
// dialog logs at the exact current millisecond rather than the minute-floored
// input, so a just-logged event doesn't look up to ~59s old.
let noteTimeEdited = false;
function clearNotePhoto() { function clearNotePhoto() {
notePhotoBlob = null; notePhotoBlob = null;
@@ -1292,7 +1291,6 @@
function openNoteDialog(type) { function openNoteDialog(type) {
pendingType = type; pendingType = type;
noteInput.value = ""; noteInput.value = "";
noteTimeEdited = false;
const now = Date.now(); const now = Date.now();
noteDate.value = toDateInput(now); noteDate.value = toDateInput(now);
noteTime.value = toTimeInput(now); noteTime.value = toTimeInput(now);
@@ -1306,9 +1304,6 @@
} }
function noteDialogAt() { function noteDialogAt() {
// Untouched time → stamp the exact current instant (sub-minute accurate).
// Once the user picks a time, honor the input (minute precision is fine).
if (!noteTimeEdited) return Date.now();
const parsed = fromDateTimeInputs(noteDate.value, noteTime.value); const parsed = fromDateTimeInputs(noteDate.value, noteTime.value);
return Number.isFinite(parsed) ? parsed : Date.now(); return Number.isFinite(parsed) ? parsed : Date.now();
} }
@@ -1317,13 +1312,6 @@
const now = Date.now(); const now = Date.now();
noteDate.value = toDateInput(now); noteDate.value = toDateInput(now);
noteTime.value = toTimeInput(now); noteTime.value = toTimeInput(now);
noteTimeEdited = false; // "Now" means log at the current instant again
});
[noteDate, noteTime].forEach(el => {
const markEdited = () => { noteTimeEdited = true; };
el.addEventListener("change", markEdited);
el.addEventListener("input", markEdited);
}); });
notePhotoBtn.addEventListener("click", () => notePhotoInput.click()); notePhotoBtn.addEventListener("click", () => notePhotoInput.click());