Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/src/race-announcements.ts
Snapshot at 34e3175
1import type { ProjectState, Race, RaceEntrant } from "./contracts.ts";
2import { receiptWord } from "./receipts.tsx";
3import { entrantReviews, raceEntrants, raceMilestones, raceSplit, raceStart, raceTimeText, timestamp } from "./race-model.ts";
4
5/** Shared with the lane's spoken description, including a failed checks hurdle. */
6export function milestoneDescription(state: ProjectState, race: Race, entrant: RaceEntrant, milestone: (typeof raceMilestones)[number]) {
7 const receipt = milestone.key === "receiptPassedAt" ? entrantReviews(state, race, entrant).at(-1)?.receipt : undefined;
8 const failure = !!receipt && ["failed", "timed-out", "infrastructure-error"].includes(receipt.status);
9 const at = failure ? receipt!.finishedAt : entrant[milestone.key];
10 const reached = timestamp(entrant[milestone.key]) !== undefined || failure;
11 const label = failure ? `Checks ${receiptWord(receipt!.status).label.toLowerCase()}` : milestone.label;
12 const start = raceStart(race);
13 const time = raceTimeText(at, start);
14 return {
15 at, label, reached,
16 split: raceSplit(at, start),
17 description: reached ? `${label}${time ? `, ${time}` : ""}` : "Not yet",
18 };
19}
20
21export function raceAnnouncementSnapshot(state: ProjectState, race: Race) {
22 const milestones = new Map<string, { signature: string; message: string }>();
23 for (const entrant of raceEntrants(race)) {
24 for (const milestone of raceMilestones) {
25 const event = milestoneDescription(state, race, entrant, milestone);
26 if (!event.reached) continue;
27 milestones.set(`${entrant.repoName}:${milestone.key}`, {
28 signature: `${event.at}:${event.label}`,
29 message: `${entrant.agent}: ${event.label}${event.split ? `, +${event.split}` : ""}`,
30 });
31 }
32 }
33 const time = raceSplit(race.finishedAt, raceStart(race));
34 return {
35 milestones,
36 finished: race.status === "finished",
37 finish: race.winner
38 ? `Race finished. ${race.winner} won${time ? ` in ${time}` : ""}.`
39 : "Race finished.",
40 };
41}
42
43/** Compare captured values so a mutated response or clock tick cannot repeat a milestone. */
44export function raceAnnouncementChanges(previous: ReturnType<typeof raceAnnouncementSnapshot>, current: ReturnType<typeof raceAnnouncementSnapshot>) {
45 const messages: string[] = [];
46 for (const [key, event] of current.milestones) {
47 if (previous.milestones.get(key)?.signature !== event.signature) messages.push(event.message);
48 }
49 if (current.finished && !previous.finished) messages.push(current.finish);
50 return messages.join(". ");
51}
52