Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

54 linesCodeBlameRaw
1/**
2 * Home that updates live: which of its repositories' changes it shows,
3 * and which rows are new since it last drew. Pure; the hook that uses
4 * them is lib/use-home-live.ts.
5 *
6 * Home watches the workspace's projects (the first HOME_WATCH_MAX, as the
7 * digest reads them) over the live socket, as a repository page
8 * watches its own (lib/repo-live.ts), and refreshes when something it
9 * lists moves: a push to a default branch, a pull request or review, an
10 * issue or comment, a run starting or finishing, a deployment or release,
11 * a session starting or ending. Not a branch's pushes or a commit's
12 * checks: a busy workspace sends those all the time, and nothing on Home
13 * shows them. Its loader reads many services, so it refreshes at most
14 * every HOME_GAP_MS.
15 */
16import type { RepoChange } from "@g1t/contracts";
17
18import { branchOf } from "./repo-live.ts";
19
20/** The most projects Home watches (a person watches at most 50, other tabs' pages too). */
21export const HOME_WATCH_MAX = 25;
22/** However many changes arrive, Home refreshes at most this often. */
23export const HOME_GAP_MS = 5000;
24
25/** A project Home watches: its id, `owner/name` and default branch. */
26export type HomeRepo = { id: string; path: string; defaultBranch: string | null };
27
28const FAMILIES = ["pull", "review", "issue", "comment", "workflow", "run", "deployment", "deployment_status", "release", "package"];
29const KINDS = ["session.started", "session.ended", "queue.changed"];
30
31/** Whether a change moves something Home shows. */
32export function homeShows(change: RepoChange, repos: readonly HomeRepo[]): boolean {
33 if (change.kind === "git.push") {
34 const repo = repos.find((one) => one.id === change.repo_id);
35 const branch = branchOf(change.ref);
36 // A default branch's push lands; a tag's is a release.
37 return branch == null || !repo?.defaultBranch || branch === repo.defaultBranch;
38 }
39 return FAMILIES.includes(change.kind.split(".")[0] ?? "") || KINDS.includes(change.kind);
40}
41
42/** What a list on the page remembers between draws: the keys it has shown, under which scope. */
43export type Seen = { scope: string; keys: ReadonlySet<string> };
44
45/**
46 * The rows of `keys` that were not on the list when it last drew, and
47 * what to remember now. On a first draw, or once the scope changed (the
48 * span switched), none are new: only what arrives while it is open is.
49 */
50export function arrivedOf(seen: Seen | null, scope: string, keys: readonly string[]): { arrived: ReadonlySet<string>; seen: Seen } {
51 if (!seen || seen.scope !== scope) return { arrived: new Set(), seen: { scope, keys: new Set(keys) } };
52 const arrived = new Set(keys.filter((key) => !seen.keys.has(key)));
53 return { arrived, seen: { scope, keys: new Set([...seen.keys, ...keys]) } };
54}