diff --git a/src/lib/due.ts b/src/lib/due.ts index b67fe67..219fd2a 100644 --- a/src/lib/due.ts +++ b/src/lib/due.ts @@ -29,12 +29,6 @@ export function shiftDays(key: string, days: number): string { return localKey(date); } -/** Milliseconds until the next local midnight, with a little slack past it. */ -export function untilMidnight(now = new Date()): number { - const next = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); - return Math.max(1000, next.getTime() - now.getTime() + 500); -} - export function daysFromToday(key: string, from = today()): number { return Math.round((fromKey(key).getTime() - fromKey(from).getTime()) / 86_400_000); } diff --git a/src/vault/store.svelte.ts b/src/vault/store.svelte.ts index aaa8fec..fff629a 100644 --- a/src/vault/store.svelte.ts +++ b/src/vault/store.svelte.ts @@ -4,7 +4,7 @@ import { isArchivePath, toBoardPath } from "../model/archive"; import { parseBoard, sphereOf } from "../model/board"; import { anchorDate, isOpen, STATUS_ORDER } from "../model/types"; import type { Board, Status, Task } from "../model/types"; -import { daysFromToday, fromKey, localKey, shiftDays, untilMidnight } from "../lib/due"; +import { daysFromToday, fromKey, localKey, shiftDays } from "../lib/due"; import { laneKey, parseLaneKey } from "../lib/keys"; import type { BeaverCalendarSettings, @@ -197,37 +197,35 @@ export class BoardStore { this.settings = settings; } - /** Re-reads the wall clock; a no-op while the day has not turned. */ + /** + * Re-reads the wall clock; a no-op while the day has not turned. A cursor + * left on the old today, and a month showing it, follow the day over. + */ tick(): void { const now = localKey(new Date()); - if (now !== this.today) this.today = now; + const was = this.today; + if (now === was) return; + this.today = now; + if (this.cursorDay === was && this.cursorTask === null) this.cursorDay = now; + if (this.monthKey.slice(0, 7) === was.slice(0, 7)) this.monthKey = now; } /** - * Keeps `today` honest for as long as the view lives: a timer for the - * midnight crossing, and a re-read when the window wakes up, since a - * sleeping laptop fires no timers. + * Keeps `today` honest for as long as the view lives. The clock is polled + * rather than timed to midnight: Chromium's timers stop while a Mac sleeps, + * so a midnight timeout fires hours late, and a window that stayed focused + * through the sleep sends no focus event on waking. Waking still re-reads + * at once where the events do come. */ watchClock(): () => void { - let timer = 0; - const arm = (): void => { - window.clearTimeout(timer); - timer = window.setTimeout(() => { - this.tick(); - arm(); - }, untilMidnight()); - }; - const wake = (): void => { - this.tick(); - arm(); - }; - arm(); - window.addEventListener("focus", wake); - document.addEventListener("visibilitychange", wake); + const tick = (): void => this.tick(); + const timer = window.setInterval(tick, 30_000); + window.addEventListener("focus", tick); + document.addEventListener("visibilitychange", tick); return () => { - window.clearTimeout(timer); - window.removeEventListener("focus", wake); - document.removeEventListener("visibilitychange", wake); + window.clearInterval(timer); + window.removeEventListener("focus", tick); + document.removeEventListener("visibilitychange", tick); }; } diff --git a/tests/due.test.ts b/tests/due.test.ts index 618aace..76fd8fd 100644 --- a/tests/due.test.ts +++ b/tests/due.test.ts @@ -4,7 +4,6 @@ import { dueLabel, dueTone, nextFriday, - untilMidnight, } from "../src/lib/due"; describe("due helpers read the day they are given", () => { @@ -26,12 +25,4 @@ describe("due helpers read the day they are given", () => { expect(nextFriday("2026-08-23")).toBe("2026-08-28"); expect(nextFriday("2026-08-28")).toBe("2026-09-04"); }); - - it("arms the midnight timer for the coming midnight", () => { - const late = new Date(2026, 7, 23, 23, 59, 0); - expect(untilMidnight(late)).toBeGreaterThan(60_000); - expect(untilMidnight(late)).toBeLessThan(62_000); - const early = new Date(2026, 7, 23, 0, 0, 1); - expect(untilMidnight(early)).toBeGreaterThan(23 * 3_600_000); - }); });