fix fold wakeup bug

This commit is contained in:
pj committed 2026-09-03 16:21:30 +05:30
1 parent 9e7d3414d9
commit c35202674d
5 files changed
+66 -26

No files matched your search

+14 -23
View File
@@ -1,15 +1,15 @@
// The wall clock, at the two granularities anything on the grid cares about: the minute the now
// line moves on, and the hour the fit holds open around it.
//
// Both schedule off the clock rather than off an interval, so a tab that was asleep and a machine
// that woke up catch up on the next tick instead of drifting further out every hour. A timer is
// still a timer, though: one set before the lid closed can fire long after its wall-clock
// deadline, and a hidden window's timers run late or not at all. So the window coming back, as a
// focus or a visibility change, is read as a tick of its own.
// Both are read off the clock once a minute rather than counted down to. A timer's deadline is
// measured in time the machine spent awake, so one set for the top of the hour before the lid
// closed is still waiting out the rest of that hour when it opens, however late it is by then.
// Reading once a minute puts a minute on how far behind either can be; the window coming back, as
// a focus or a visibility change, is read as a tick of its own so it is usually less.
//
// They are separate hooks because they are separate rerenders. The minute belongs to the one
// component that draws the line; putting the fit on that tick would solve the whole axis sixty
// times an hour to answer a question that changes once.
// component that draws the line. The hour is read as often but only changes once an hour, and a
// state set to the value it already holds rerenders nothing.
import { useEffect, useState } from "react";
@@ -22,20 +22,12 @@ function hourStart(ms: number): number {
return d.getTime();
}
/** The next one. `setMinutes(60)` rolls the date over for us, daylight saving and all. */
function nextHourStart(ms: number): number {
const d = new Date(ms);
d.setMinutes(60, 0, 0);
return d.getTime();
}
function useTick<T>(read: (now: number) => T, next: (now: number) => number): T {
function useTick<T>(read: (now: number) => T): T {
const [value, setValue] = useState(() => read(Date.now()));
useEffect(() => {
let timer = 0;
const schedule = () => {
const now = Date.now();
timer = window.setTimeout(tick, Math.max(0, next(now) - now));
timer = window.setTimeout(tick, MINUTE - (Date.now() % MINUTE));
};
const tick = () => {
window.clearTimeout(timer);
@@ -53,25 +45,24 @@ function useTick<T>(read: (now: number) => T, next: (now: number) => number): T
document.removeEventListener("visibilitychange", woke);
window.removeEventListener("focus", woke);
};
// Both callbacks are module-level functions in every caller, so there is nothing to rebind.
}, [read, next]);
// `read` is a module-level function in every caller, so there is nothing to rebind.
}, [read]);
return value;
}
const now = (ms: number) => ms;
const nextMinute = (ms: number) => ms + MINUTE - (ms % MINUTE);
/** Now, refreshed on every wall-clock minute. */
export function useMinuteTick(): number {
return useTick(now, nextMinute);
return useTick(now);
}
/**
* The top of the hour it is now, refreshed when the clock turns over into the next one.
* The top of the hour it is now, read every minute and changed once an hour.
*
* Returning the hour rather than the instant is what makes this cheap to depend on: the value is
* identical for a whole hour, so a memo keyed on it recomputes once, when the answer changed.
*/
export function useHourStart(): number {
return useTick(hourStart, nextHourStart);
return useTick(hourStart);
}