mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(day): hold a step until the layer shows the previous one
A step writes ?day= with replaceState, past Next's router, which adopts the write a render later. Since 16.4 that render can land after a second quick step and write the first step's URL back, so [ then ] pressed in a row ended a day short. A step that finds the layer still showing another day than the URL now waits, and publishOpenDay makes it in one write as soon as the layer catches up. Never past today still holds for the sum.
This commit is contained in:
1 parent
5902401ab8
commit
c496e390be
2 files changed
+61
-13
No files matched your search
@@ -109,6 +109,25 @@ describe("day layer history", () => {
|
||||
expect(browser.current()).toContain("day=2026-01-05");
|
||||
});
|
||||
|
||||
it("holds a step while the layer still shows another day, then makes it", async () => {
|
||||
// The layer publishes the day it shows only once Next has adopted the
|
||||
// URL. A step written before that can be put back by Next's own write,
|
||||
// so it waits for the layer and goes in one write when it catches up.
|
||||
const { openDay, publishOpenDay, stepDay } =
|
||||
await import("../day-layer-controller");
|
||||
openDay("2026-01-03");
|
||||
publishOpenDay("2026-01-03");
|
||||
expect(stepDay(-1, TODAY)).toBe("2026-01-02");
|
||||
// The layer still shows the 3rd: these two wait.
|
||||
expect(stepDay(1, TODAY)).toBeNull();
|
||||
expect(stepDay(1, TODAY)).toBeNull();
|
||||
expect(browser.current()).toContain("day=2026-01-02");
|
||||
publishOpenDay("2026-01-02");
|
||||
expect(browser.current()).toContain("day=2026-01-04");
|
||||
expect(browser.length()).toBe(2);
|
||||
publishOpenDay(null);
|
||||
});
|
||||
|
||||
it("never steps past today, however often it is asked", async () => {
|
||||
const { openDay, stepDay } = await import("../day-layer-controller");
|
||||
openDay("2026-01-09");
|
||||
|
||||
@@ -101,6 +101,13 @@ export function publishOpenDay(date: DateKey | null): void {
|
||||
if (memory.open === date) return;
|
||||
memory.open = date;
|
||||
emit();
|
||||
// The layer has caught up with the URL: the steps that waited for it go
|
||||
// now, in one write, from the day it shows.
|
||||
const waiting = pendingStep;
|
||||
pendingStep = null;
|
||||
if (waiting !== null && date !== null) {
|
||||
stepDay(waiting.delta, waiting.today);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,17 +164,29 @@ export function openDay(date: DateKey, options: OpenDayOptions = {}): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the open day by `delta` calendar days, without a new history entry.
|
||||
* Null, and nothing written, when no day is open or the step would pass
|
||||
* `today`.
|
||||
* Steps asked for while the layer had not yet shown the previous one. Kept
|
||||
* as one sum, with the `today` of the latest request.
|
||||
*/
|
||||
let pendingStep: { delta: number; today: DateKey } | null = null;
|
||||
|
||||
/**
|
||||
* Move the open day by `delta` calendar days, without a new history entry,
|
||||
* and never past `today`. Null when nothing is written now: no day is open,
|
||||
* the step would pass today, or it waits for the previous one.
|
||||
*
|
||||
* The step starts from the day in the URL at the moment of the step, not
|
||||
* from the day a caller last rendered. The rendered day reaches a caller
|
||||
* through Next's `useSearchParams`, a render of the layer and its
|
||||
* `publishOpenDay`, and Next 16.4 lets that chain trail a `replaceState` by
|
||||
* more than a key repeat: two quick `]` presses then both stepped from the
|
||||
* same day and one of them was lost. `window.location` is written by the
|
||||
* step itself, so it is never behind.
|
||||
* Two things keep quick steps (a key held down, `]` pressed twice) from
|
||||
* losing one.
|
||||
*
|
||||
* The step starts from the day in the URL, not from the day a caller last
|
||||
* rendered, which reaches it through `useSearchParams`, a render and an
|
||||
* effect and can be a step behind.
|
||||
*
|
||||
* And a step does not write while the layer still shows a different day
|
||||
* than the URL. The write goes past Next's router, which adopts it a render
|
||||
* later, and since Next 16.4 that render can land after a second write and
|
||||
* put the first one's URL back: `[` then `]` pressed quickly ended a day
|
||||
* short. So a step that finds the layer behind waits, and `publishOpenDay`
|
||||
* makes it as soon as the layer shows the day the URL names.
|
||||
*/
|
||||
export function stepDay(delta: number, today: DateKey): DateKey | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
@@ -175,9 +194,19 @@ export function stepDay(delta: number, today: DateKey): DateKey | null {
|
||||
new URLSearchParams(window.location.search).get(DAY_QUERY_PARAM),
|
||||
today,
|
||||
);
|
||||
if (from === null) return null;
|
||||
if (delta > 0 && from >= today) return null;
|
||||
const next = shiftDateKey(from, delta);
|
||||
if (from === null) {
|
||||
pendingStep = null;
|
||||
return null;
|
||||
}
|
||||
const total = (pendingStep?.delta ?? 0) + delta;
|
||||
if (memory.open !== null && memory.open !== from) {
|
||||
pendingStep = { delta: total, today };
|
||||
return null;
|
||||
}
|
||||
pendingStep = null;
|
||||
const target = shiftDateKey(from, total);
|
||||
const next = target > today ? today : target;
|
||||
if (next === from) return null;
|
||||
jumpDay(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user