Only claim a derived boundary is a source's when boundaryMs treats it that way
The day-precision note ("The source gave a date but no time of day, so this
counts down to that day's server reset...") rendered for any event with
endPrecision "day" and a stated end, with no check on where the date came
from. For a reader's own event that is false three times over: there is no
source (sourceUrl is null, sourceId is "you"), nobody gave a date — for a
repeating occurrence with no stated end the app derived it from the interval
— and it does not count down to a server reset, since boundaryMs only applies
that shift when extractionMethod === "parser". Gated the note on the same
condition so the copy and the countdown agree.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d1ffac4b9f
commit
d433eeee50
@@ -281,7 +281,16 @@ export function EventDetail({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{event.endPrecision === "day" && event.endsAt !== null && (
|
{/* Gated on the same condition `boundaryMs` uses to apply the reset
|
||||||
|
shift, so the copy never claims a countdown the clock is not
|
||||||
|
actually running. A reader's own event is day-precision exactly as
|
||||||
|
often as a parsed one, but `extractionMethod` is "manual" — there
|
||||||
|
is no source to have "given" a date, and `readerInstant` already
|
||||||
|
resolved it to the instant they meant rather than a placeholder
|
||||||
|
`boundaryMs` has to reinterpret. */}
|
||||||
|
{event.endPrecision === "day" &&
|
||||||
|
event.endsAt !== null &&
|
||||||
|
event.extractionMethod === "parser" && (
|
||||||
<p className="mt-3 text-xs leading-relaxed text-faint">
|
<p className="mt-3 text-xs leading-relaxed text-faint">
|
||||||
The source gave a date but no time of day, so this counts down to
|
The source gave a date but no time of day, so this counts down to
|
||||||
that day's {REGION_LABEL[region]} server reset — where these usually land, but a
|
that day's {REGION_LABEL[region]} server reset — where these usually land, but a
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "../src/client/components/CustomForms.tsx";
|
} from "../src/client/components/CustomForms.tsx";
|
||||||
import { YourOwn } from "../src/client/components/YourOwn.tsx";
|
import { YourOwn } from "../src/client/components/YourOwn.tsx";
|
||||||
import { EventRow } from "../src/client/components/EventRow.tsx";
|
import { EventRow } from "../src/client/components/EventRow.tsx";
|
||||||
|
import { EventDetail } from "../src/client/components/EventDetail.tsx";
|
||||||
import { AUTHOR, Colophon, REPO_URL } from "../src/client/components/Colophon.tsx";
|
import { AUTHOR, Colophon, REPO_URL } from "../src/client/components/Colophon.tsx";
|
||||||
import { GameMetaProvider } from "../src/client/state/gameMeta.tsx";
|
import { GameMetaProvider } from "../src/client/state/gameMeta.tsx";
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
CustomEvent,
|
CustomEvent,
|
||||||
type CustomEvents,
|
type CustomEvents,
|
||||||
type CustomGames,
|
type CustomGames,
|
||||||
|
type DisplayEvent,
|
||||||
} from "../src/shared/custom.ts";
|
} from "../src/shared/custom.ts";
|
||||||
import { metaFor } from "../src/shared/games.ts";
|
import { metaFor } from "../src/shared/games.ts";
|
||||||
import { clockFor } from "../src/shared/time.ts";
|
import { clockFor } from "../src/shared/time.ts";
|
||||||
@@ -468,3 +470,81 @@ describe("the sheet says how often", () => {
|
|||||||
expect(cadenceLabel(null)).toBe(null);
|
expect(cadenceLabel(null)).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("the derived-boundary note", () => {
|
||||||
|
const NOW = Date.parse("2026-08-25T12:00:00.000Z");
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
const detailProps = {
|
||||||
|
completed: false,
|
||||||
|
ignored: false,
|
||||||
|
status: undefined,
|
||||||
|
effort: undefined,
|
||||||
|
note: "",
|
||||||
|
region: "europe" as const,
|
||||||
|
now: NOW,
|
||||||
|
daily: false,
|
||||||
|
detectedDaily: false,
|
||||||
|
dailyDays: [],
|
||||||
|
onDaily: noop,
|
||||||
|
onToggleDay: noop,
|
||||||
|
onIgnore: noop,
|
||||||
|
onStatus: noop,
|
||||||
|
onEffort: noop,
|
||||||
|
onNote: noop,
|
||||||
|
onClose: noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
// A parser declining to guess a time of day — the case the note was written
|
||||||
|
// for. `dates.ts` stores the placeholder as 00:00Z, and `boundaryMs` reads
|
||||||
|
// it against the game's own server reset rather than literally.
|
||||||
|
const PARSED: DisplayEvent = {
|
||||||
|
id: "genshin:walpurgisnacht:2026-09-03",
|
||||||
|
game: "genshin",
|
||||||
|
title: "Walpurgisnacht",
|
||||||
|
type: "banner",
|
||||||
|
summary: null,
|
||||||
|
startsAt: "2026-08-20T00:00:00.000Z",
|
||||||
|
startPrecision: "day",
|
||||||
|
endsAt: "2026-09-03T00:00:00.000Z",
|
||||||
|
endPrecision: "day",
|
||||||
|
regionScoped: false,
|
||||||
|
regionEnds: null,
|
||||||
|
sourceUrl: "https://example.com/events",
|
||||||
|
sourceId: "genshin-game8-events",
|
||||||
|
status: "published",
|
||||||
|
confidence: 1,
|
||||||
|
extractionMethod: "parser",
|
||||||
|
version: 1,
|
||||||
|
firstSeenAt: AT,
|
||||||
|
updatedAt: AT,
|
||||||
|
};
|
||||||
|
|
||||||
|
test("present for a parser-sourced day-precision event", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GameMetaProvider value={(id) => metaFor(id, GAMES)}>
|
||||||
|
<EventDetail
|
||||||
|
{...detailProps}
|
||||||
|
row={{ event: PARSED, clock: clockFor(PARSED, detailProps.region, NOW) }}
|
||||||
|
/>
|
||||||
|
</GameMetaProvider>,
|
||||||
|
);
|
||||||
|
expect(html).toContain("server reset");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("absent for a reader's own day-precision event", () => {
|
||||||
|
// False three times over: no source, nobody "gave" a date, and
|
||||||
|
// `boundaryMs` only applies the reset shift for `extractionMethod ===
|
||||||
|
// "parser"` — a reader's own event is "manual" even at day precision.
|
||||||
|
const own = asDisplayEvent(OWN);
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<GameMetaProvider value={(id) => metaFor(id, GAMES)}>
|
||||||
|
<EventDetail
|
||||||
|
{...detailProps}
|
||||||
|
row={{ event: own, clock: clockFor(own, detailProps.region, NOW) }}
|
||||||
|
/>
|
||||||
|
</GameMetaProvider>,
|
||||||
|
);
|
||||||
|
expect(html).not.toContain("server reset");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user