Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/src/lane.tsx
Snapshot at 34e3175
1import { Tooltip } from "@cloudflare/kumo";
2import type { CSSProperties } from "react";
3import type { ProjectState } from "./contracts.ts";
4
5export interface AgentIdentity {
6 number: number;
7 silk: 1 | 2 | 3;
8}
9/** Enrollment rank, never response/claim/race order. Legacy agents precede dated agents. */
10export function agentIdentities(agents: ProjectState["agents"]): Map<string, AgentIdentity> {
11 const sorted = agents.toSorted((a, b) => {
12 if (!a.createdAt && b.createdAt) return -1;
13 if (a.createdAt && !b.createdAt) return 1;
14 return (
15 (Date.parse(a.createdAt ?? "") || 0) - (Date.parse(b.createdAt ?? "") || 0) ||
16 a.name.localeCompare(b.name)
17 );
18 });
19 return new Map(
20 sorted.map((agent, index) => [
21 agent.name,
22 {
23 number: index + 1,
24 silk: ((index % 3) + 1) as 1 | 2 | 3,
25 },
26 ]),
27 );
28}
29export function silkStyle(identity?: AgentIdentity): CSSProperties {
30 return {
31 "--lane-silk": identity ? `var(--race-silk-${identity.silk})` : "var(--text-color-kumo-subtle)",
32 } as CSSProperties;
33}
34export function Saddlecloth({ identity }: { identity?: AgentIdentity }) {
35 // Unknown agents get no invented rank. Numbers always accompany a known silk.
36 return (
37 <span
38 className="race-saddlecloth race-cloth"
39 style={silkStyle(identity)}
40 >
41 <span aria-hidden="true">{identity?.number ?? "โ"}</span>
42 <span className="sr-only">{identity ? `Agent number ${identity.number}` : "Agent number unavailable"}</span>
43 </span>
44 );
45}
46export type LaneMarkKind =
47 | "claim"
48 | "push"
49 | "review"
50 | "passed"
51 | "failed"
52 | "interrupted"
53 | "approved"
54 | "promoted";
55export const markGlyph: Record<LaneMarkKind, string> = {
56 claim: "โ",
57 push: "โ",
58 review: "โ",
59 passed: "โ",
60 failed: "โ",
61 interrupted: "!",
62 approved: "โ",
63 promoted: "โ",
64};
65export function LaneMarker({ kind }: { kind: LaneMarkKind | "unreached" | "reached" }) {
66 const hurdle = kind === "passed" || kind === "failed" || kind === "interrupted";
67 const reached = kind !== "unreached" && kind !== "failed" && kind !== "interrupted";
68 return (
69 <span className={`lane-marker lane-marker-${kind} race-marker ${reached ? "reached" : ""} ${kind === "promoted" ? "promoted" : ""} ${hurdle ? `hurdle ${kind}` : ""}`} aria-hidden="true">
70 {kind === "unreached" ? "โ" : kind === "reached" ? "โ" : markGlyph[kind]}
71 </span>
72 );
73}
74
75/** FH-055-compatible API, shared with the fork graph. */
76export function saddlecloth(state: ProjectState, agent: string): number | undefined {
77 return agentIdentities(state.agents).get(agent)?.number;
78}
79export function laneSilkStyle(state: ProjectState, agent: string): CSSProperties {
80 const style = silkStyle(agentIdentities(state.agents).get(agent));
81 return {
82 ...style,
83 "--entrant-silk": style["--lane-silk" as keyof CSSProperties],
84 } as CSSProperties;
85}
86export function LaneRunner({
87 entrant,
88 state,
89}: {
90 entrant: { agent: string; repoName: string };
91 state: ProjectState;
92}) {
93 return (
94 <span className="race-runner" style={laneSilkStyle(state, entrant.agent)}>
95 <Saddlecloth identity={agentIdentities(state.agents).get(entrant.agent)} />
96 <span className="race-runner-copy">
97 <strong>{entrant.agent}</strong>
98 <Tooltip content={entrant.repoName} render={<code className="caption race-fork" />}>
99 {entrant.repoName}
100 </Tooltip>
101 </span>
102 </span>
103 );
104}
105
106/** Decorative SVG lane path; callers supply only proven endpoints. */
107export function LaneLine({ path }: { path: string }) {
108 return <path className="lane-line" d={path} aria-hidden="true" />;
109}
110/** Table-based race connector: only consecutive recorded milestones may connect. */
111export function LaneConnector({ reached }: { reached: boolean }) {
112 return reached ? <span className="lane-connector" aria-hidden="true" /> : null;
113}
114