Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-dashboard/src/race-announcements.ts

Snapshot at 34e3175

Containing directory

2,559 bytes · typescriptView raw
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