Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 1 | import { Laptop } from "lucide-react"; |
| 2 | import { Link } from "react-router"; | |
| 3 | ||
| 4 | import type { DesktopBackend, DesktopDiagnosis, DesktopRunner } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { BACKEND_LABEL, type DesktopRunnerAction, desktopRunner, useDesktopRunner } from "../lib/desktop-runner"; | |
| 7 | import { EmptyState, TimeAgo } from "./ui"; | |
| 8 | import { Badge } from "./ui/badge"; | |
| 9 | import { Button } from "./ui/button"; | |
| 10 | import { Card } from "./ui/card"; | |
| 11 | ||
| 12 | /** A diagnosis's tone, as a dot. */ | |
| 13 | const DOT: Record<DesktopDiagnosis["tone"], string> = { | |
| 14 | ok: "bg-success", | |
| 15 | working: "bg-info", | |
| 16 | off: "bg-faint", | |
| 17 | warning: "bg-warn", | |
| 18 | problem: "bg-danger", | |
| 19 | }; | |
| 20 | ||
| 21 | type Row = { | |
| 22 | machine: string; | |
| 23 | person: string | null; | |
| 24 | backend: DesktopBackend; | |
| 25 | version: string | null; | |
| 26 | diagnosis: DesktopDiagnosis; | |
| 27 | /** This computer's fixes, which only work here. */ | |
| 28 | actions: DesktopRunnerAction[]; | |
| 29 | here: boolean; | |
| 30 | reportedAt: string | null; | |
| 31 | }; | |
| 32 | ||
| 33 | /** The desktops to list: this computer, live, first; then the others, as their apps last said, or closed. */ | |
| 34 | export function desktopRows(desktops: DesktopRunner[], here: ReturnType<typeof useDesktopRunner>, slug: string): Row[] { | |
| 35 | const rows: Row[] = []; | |
| 36 | if (here && here.workspace === slug) { | |
| 37 | rows.push({ | |
| 38 | machine: here.machine, | |
| 39 | person: null, | |
| 40 | backend: here.backend, | |
| 41 | version: here.version, | |
| 42 | diagnosis: here.diagnosis, | |
| 43 | actions: here.diagnosis.actions, | |
| 44 | here: true, | |
| 45 | reportedAt: null, | |
| 46 | }); | |
| 47 | } | |
| 48 | for (const desktop of desktops) { | |
| 49 | if (rows.some((row) => row.here && row.machine.toLowerCase() === desktop.machine.toLowerCase())) continue; | |
| 50 | rows.push({ | |
| 51 | machine: desktop.machine, | |
| 52 | person: desktop.person, | |
| 53 | backend: desktop.backend, | |
| 54 | version: desktop.version, | |
| 55 | diagnosis: desktop.open ? desktop.diagnosis : desktop.closed, | |
| 56 | actions: [], | |
| 57 | here: false, | |
| 58 | reportedAt: desktop.reportedAt, | |
| 59 | }); | |
| 60 | } | |
| 61 | return rows; | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * The workspace's desktops: each person's computer with the g1t desktop | |
| 66 | * app, whose runner runs while g1t is open there. What each says, why it | |
| 67 | * isn't running and the fix, is the app's own words (lib/desktop-runner.ts). | |
| 68 | */ | |
| 69 | export function DesktopRunners({ desktops, slug }: { desktops: DesktopRunner[] | null; slug: string }) { | |
| 70 | const here = useDesktopRunner(); | |
| 71 | if (!desktops) return <p className="text-sm text-muted">The desktops can't be read right now.</p>; | |
| 72 | const rows = desktopRows(desktops, here, slug); | |
| 73 | if (rows.length === 0) { | |
| 74 | return ( | |
| 75 | <EmptyState title="No desktops yet"> | |
| 76 | The <Link to="/download" className="underline underline-offset-2">g1t desktop app</Link> runs this workspace's self-hosted jobs while it's open, sandboxed, with nothing to set up. | |
| 77 | Open it signed in as an owner of {slug}. | |
| 78 | </EmptyState> | |
| 79 | ); | |
| 80 | } | |
| 81 | return ( | |
| 82 | <Card asChild divided> | |
| 83 | <ul> | |
| 84 | {rows.map((row) => ( | |
| 85 | <li key={row.machine} className="flex items-start gap-3 px-4 py-3"> | |
| 86 | <span className={`mt-1.5 size-2 shrink-0 rounded-full ${DOT[row.diagnosis.tone]}`} aria-hidden /> | |
| 87 | <div className="min-w-0 grow"> | |
| 88 | <div className="flex flex-wrap items-center gap-x-2 gap-y-1"> | |
| 89 | <Laptop size={13} className="text-muted" aria-hidden /> | |
| 90 | <span className="font-medium">{row.machine}</span> | |
| 91 | {row.here && <Badge tone="accent">This computer</Badge>} | |
| 92 | <Badge tone={row.backend === "sandboxed" ? "neutral" : "warn"}>{BACKEND_LABEL[row.backend]}</Badge> | |
| 93 | {row.person && <span className="text-xs text-faint">@{row.person}</span>} | |
| 94 | </div> | |
| 95 | <p className="mt-1 text-sm text-fg">{row.diagnosis.title}</p> | |
| 96 | {row.diagnosis.cause && <p className="mt-0.5 text-sm text-muted">{row.diagnosis.cause}</p>} | |
| 97 | {row.diagnosis.fix && <p className="mt-0.5 text-sm text-fg-soft">{row.diagnosis.fix}</p>} | |
| 98 | {row.actions.length > 0 && ( | |
| 99 | <div className="mt-2 flex flex-wrap gap-2"> | |
| 100 | {row.actions.map((action, at) => ( | |
| 101 | <Button key={action.id} size="xs" variant={at === 0 ? "secondary" : "outline"} onClick={() => void desktopRunner()?.act(action.id).catch(() => {})}> | |
| 102 | {action.label} | |
| 103 | </Button> | |
| 104 | ))} | |
| 105 | </div> | |
| 106 | )} | |
| 107 | </div> | |
| 108 | <div className="shrink-0 text-right text-xs text-faint"> | |
| 109 | {row.version && <div>g1t-runner {row.version}</div>} | |
| 110 | {row.reportedAt && ( | |
| 111 | <div className="mt-0.5"> | |
| 112 | Heard from <TimeAgo at={row.reportedAt} /> | |
| 113 | </div> | |
| 114 | )} | |
| 115 | </div> | |
| 116 | </li> | |
| 117 | ))} | |
| 118 | </ul> | |
| 119 | </Card> | |
| 120 | ); | |
| 121 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.