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.
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1 | /** |
| 2 | * The dependency update file, as a project's Security page shows it: which | |
| 3 | * file was read, its problems with their lines, each `updates` entry with | |
| 4 | * its schedule and last check, the private registries, the update pull | |
| 5 | * requests g1t has open, and ignore conditions set in comments. "Check for | |
| 6 | * updates" posts the `check_updates` intent to its page, the Security | |
| 7 | * section's Dependency updates tab (`routes/repo/security-updates.tsx`). | |
| 8 | */ | |
| 9 | import { | |
| 10 | CalendarClock, | |
| 11 | CircleCheck, | |
| 12 | CircleDot, | |
| 13 | ExternalLink, | |
| 14 | FileCode, | |
| 15 | FileWarning, | |
| 16 | GitMerge, | |
| 17 | GitPullRequest, | |
| 18 | Info, | |
| 19 | KeyRound, | |
| 20 | Loader, | |
| 21 | RefreshCw, | |
| 22 | Wrench, | |
| 23 | EyeOff, | |
| 24 | } from "lucide-react"; | |
| 25 | import { Link, useFetcher } from "react-router"; | |
| 26 | ||
| 27 | import type { UpdatePull, VersionUpdateEntry, VersionUpdatesState } from "@g1t/contracts"; | |
| 28 | ||
| 29 | import { | |
| 30 | DEPENDENCY_UPDATES_DOCS, | |
| 31 | PULL_STATES, | |
| 32 | conditionText, | |
| 33 | entryStatus, | |
| 34 | groupText, | |
| 35 | ignoreText, | |
| 36 | livePulls, | |
| 37 | timeUntil, | |
| 38 | utc, | |
| 39 | } from "../lib/dependency-updates"; | |
| 40 | import { TimeAgo } from "./ui"; | |
| 41 | import { Badge, type BadgeTone } from "./ui/badge"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 42 | import { Button } from "./ui/button"; |
| 43 | import { Card } from "./ui/card"; | |
| Merge branch 'worktree-agent-ae1299e92e4462012' | 44 | import { Hint } from "./ui/hint"; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 45 | |
| 46 | type Done = { ok: boolean; error?: string } | undefined; | |
| 47 | ||
| 48 | const PULL_TONES: Record<UpdatePull["state"], BadgeTone> = { | |
| 49 | requested: "info", | |
| 50 | open: "accent", | |
| 51 | merged: "merged", | |
| 52 | closed: "neutral", | |
| 53 | superseded: "neutral", | |
| 54 | needs_code: "warn", | |
| 55 | failed: "danger", | |
| 56 | }; | |
| 57 | ||
| 58 | /** Text with `code` spans, as the service writes its messages. */ | |
| 59 | function Said({ text }: { text: string }) { | |
| 60 | return ( | |
| 61 | <> | |
| 62 | {text.split("`").map((part, index) => | |
| 63 | index % 2 === 1 ? ( | |
| 64 | <code key={index} className="text-fg-soft"> | |
| 65 | {part} | |
| 66 | </code> | |
| 67 | ) : ( | |
| 68 | part | |
| 69 | ), | |
| 70 | )} | |
| 71 | </> | |
| 72 | ); | |
| 73 | } | |
| 74 | ||
| 75 | function Heading({ children }: { children: React.ReactNode }) { | |
| 76 | return <h4 className="text-xs font-medium tracking-wide text-faint uppercase">{children}</h4>; | |
| 77 | } | |
| 78 | ||
| 79 | /** One `updates` entry: what it covers, when it runs, what it found. */ | |
| 80 | function Entry({ entry, action, canCheck }: { entry: VersionUpdateEntry; action: string; canCheck: boolean }) { | |
| 81 | const check = useFetcher<Done>(); | |
| 82 | const status = entryStatus(entry); | |
| 83 | const checking = check.state !== "idle"; | |
| 84 | const rules: { label: string; values: string[] }[] = [ | |
| 85 | { label: "Groups", values: entry.groups.map(groupText) }, | |
| 86 | { label: "Ignored", values: entry.ignore.map(ignoreText) }, | |
| 87 | { | |
| 88 | label: "Allowed", | |
| 89 | values: entry.allow.map((rule) => | |
| 90 | [rule.dependency, rule.dependencyType, ...rule.updateTypes.map((type) => type.replace("version-update:semver-", ""))] | |
| 91 | .filter(Boolean) | |
| 92 | .join(" · "), | |
| 93 | ), | |
| 94 | }, | |
| 95 | { label: "Assignees", values: entry.assignees }, | |
| 96 | { label: "Reviewers", values: entry.reviewers }, | |
| 97 | ].filter((rule) => rule.values.length > 0); | |
| 98 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 99 | <Card asChild tone="plain" radius="lg" className="p-3"> |
| 100 | <li> | |
| 101 | <div className="flex flex-wrap items-start justify-between gap-3"> | |
| 102 | <div className="min-w-0"> | |
| 103 | <p className="flex flex-wrap items-center gap-2 text-sm"> | |
| 104 | <span className="font-mono font-medium text-fg">{entry.ecosystem}</span> | |
| 105 | <span className="font-mono text-muted wrap-anywhere">{entry.directories.join(", ")}</span> | |
| 106 | {entry.targetBranch && <span className="font-mono text-xs text-faint">→ {entry.targetBranch}</span>} | |
| 107 | <Badge tone={status.tone}>{status.label}</Badge> | |
| 108 | </p> | |
| 109 | <p className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted"> | |
| 110 | {entry.schedule && ( | |
| 111 | <span className="inline-flex items-center gap-1"> | |
| 112 | <CalendarClock size={12} className="shrink-0" /> | |
| 113 | {entry.schedule} | |
| 114 | </span> | |
| 115 | )} | |
| 116 | {entry.nextRunAt && ( | |
| 117 | <Hint label={utc(entry.nextRunAt)}> | |
| 118 | <span>Next check {timeUntil(entry.nextRunAt)}</span> | |
| 119 | </Hint> | |
| 120 | )} | |
| 121 | <span> | |
| 122 | {entry.lastCheckedAt ? ( | |
| 123 | <> | |
| 124 | Last checked <TimeAgo at={entry.lastCheckedAt} /> | |
| 125 | </> | |
| 126 | ) : ( | |
| 127 | "Not checked yet" | |
| 128 | )} | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 129 | </span> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 130 | {entry.supported && <span>Up to {entry.openPullRequestsLimit} open</span>} |
| 131 | </p> | |
| 132 | </div> | |
| 133 | {entry.supported && canCheck && ( | |
| 134 | <check.Form method="post" action={action} className="shrink-0"> | |
| 135 | <input type="hidden" name="intent" value="check_updates" /> | |
| 136 | <input type="hidden" name="entry" value={entry.id} /> | |
| 137 | <Button | |
| 138 | type="submit" | |
| 139 | disabled={checking || entry.openPullRequestsLimit === 0} | |
| 140 | variant="outline" | |
| 141 | size="inline" | |
| 142 | className="px-2.5 py-1 text-xs hover:bg-raised font-normal" | |
| 143 | > | |
| 144 | <RefreshCw size={12} className={checking ? "animate-spin" : ""} /> | |
| 145 | {checking ? "Checking…" : "Check for updates"} | |
| 146 | </Button> | |
| 147 | </check.Form> | |
| 148 | )} | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 149 | </div> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 150 | {check.data?.error && <p className="mt-2 text-xs text-danger">{check.data.error}</p>} |
| 151 | {entry.lastError ? ( | |
| 152 | <p className="mt-2 flex items-start gap-1.5 text-xs text-danger"> | |
| 153 | <FileWarning size={12} className="mt-0.5 shrink-0" /> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 154 | <span className="wrap-anywhere"> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 155 | <Said text={entry.lastError} /> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 156 | </span> |
| 157 | </p> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 158 | ) : ( |
| 159 | entry.lastResult && ( | |
| 160 | <p className="mt-2 flex items-start gap-1.5 text-xs text-muted"> | |
| 161 | <CircleCheck size={12} className="mt-0.5 shrink-0 text-success" /> | |
| 162 | <span className="wrap-anywhere"> | |
| 163 | <Said text={entry.lastResult} /> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 164 | </span> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 165 | </p> |
| 166 | ) | |
| 167 | )} | |
| 168 | {rules.length > 0 && ( | |
| 169 | <dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs"> | |
| 170 | {rules.map((rule) => ( | |
| 171 | <div key={rule.label} className="contents"> | |
| 172 | <dt className="text-faint">{rule.label}</dt> | |
| 173 | <dd className="font-mono text-muted wrap-anywhere">{rule.values.join("; ")}</dd> | |
| 174 | </div> | |
| 175 | ))} | |
| 176 | </dl> | |
| 177 | )} | |
| 178 | {entry.notes.length > 0 && ( | |
| 179 | <ul className="mt-2 space-y-1"> | |
| 180 | {entry.notes.map((note) => ( | |
| 181 | <li key={note} className="flex items-start gap-1.5 text-xs text-faint"> | |
| 182 | <Info size={12} className="mt-0.5 shrink-0" /> | |
| 183 | <span> | |
| 184 | <Said text={note} /> | |
| 185 | </span> | |
| 186 | </li> | |
| 187 | ))} | |
| 188 | </ul> | |
| 189 | )} | |
| 190 | </li> | |
| 191 | </Card> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 192 | ); |
| 193 | } | |
| 194 | ||
| 195 | /** One update pull request g1t has open or is making. */ | |
| 196 | function PullRow({ pull, base }: { pull: UpdatePull; base: string }) { | |
| 197 | const icon = | |
| 198 | pull.state === "needs_code" ? ( | |
| 199 | <Wrench size={13} className="text-warn" /> | |
| 200 | ) : pull.state === "requested" ? ( | |
| 201 | <Loader size={13} className="text-info" /> | |
| 202 | ) : ( | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 203 | <GitPullRequest size={13} className="text-success" /> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 204 | ); |
| 205 | const count = pull.dependencies.length; | |
| 206 | return ( | |
| 207 | <li className="flex items-start gap-2 py-2"> | |
| 208 | <span className="mt-0.5 shrink-0">{icon}</span> | |
| 209 | <div className="min-w-0 flex-1"> | |
| 210 | <p className="text-sm"> | |
| 211 | {pull.pull ? ( | |
| 212 | <Link to={`${base}/pulls/${pull.pull}`} className="hover:underline"> | |
| 213 | {pull.title} <span className="text-muted">#{pull.pull}</span> | |
| 214 | </Link> | |
| 215 | ) : ( | |
| 216 | pull.title | |
| 217 | )} | |
| 218 | </p> | |
| 219 | <p className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted"> | |
| 220 | <Badge tone={PULL_TONES[pull.state]}>{PULL_STATES[pull.state]}</Badge> | |
| 221 | <span>{pull.kind === "security" ? "Security update" : "Version update"}</span> | |
| 222 | <span> | |
| 223 | {count} {count === 1 ? "dependency" : "dependencies"} | |
| 224 | </span> | |
| 225 | {pull.mergeRequestedBy && ( | |
| 226 | <span className="inline-flex items-center gap-1"> | |
| 227 | <GitMerge size={11} /> | |
| 228 | Merges when its checks pass, as @{pull.mergeRequestedBy} asked | |
| 229 | </span> | |
| 230 | )} | |
| 231 | </p> | |
| 232 | {pull.error && <p className="mt-0.5 text-xs text-danger wrap-anywhere">{pull.error}</p>} | |
| 233 | </div> | |
| 234 | </li> | |
| 235 | ); | |
| 236 | } | |
| 237 | ||
| 238 | export function DependencyUpdates({ | |
| 239 | state, | |
| 240 | action, | |
| 241 | base, | |
| 242 | canCheck, | |
| 243 | titled = true, | |
| 244 | }: { | |
| 245 | state: VersionUpdatesState; | |
| 246 | action: string; | |
| 247 | base: string; | |
| 248 | canCheck: boolean; | |
| 249 | /** Its own title and what it is for; off where the page says so above it. */ | |
| 250 | titled?: boolean; | |
| 251 | }) { | |
| 252 | const pulls = livePulls(state.pulls); | |
| 253 | const fileLink = state.path && state.commit ? `${base}/blob/${state.commit}/${state.path}` : null; | |
| 254 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 255 | <Card className="p-4"> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 256 | {titled && ( |
| 257 | <> | |
| 258 | <div className="flex flex-wrap items-center justify-between gap-2"> | |
| 259 | <span className="text-sm font-medium">Dependency updates</span> | |
| 260 | <a | |
| 261 | href={DEPENDENCY_UPDATES_DOCS} | |
| 262 | target="_blank" | |
| 263 | rel="noreferrer" | |
| 264 | className="inline-flex items-center gap-1 text-xs text-fg-soft hover:text-fg hover:underline" | |
| 265 | > | |
| 266 | How to write the file | |
| 267 | <ExternalLink size={10} /> | |
| 268 | </a> | |
| 269 | </div> | |
| 270 | <p className="mt-1 text-sm text-muted"> | |
| 271 | Pull requests that keep dependencies current on a schedule, from a <code className="text-fg-soft">dependabot.yml</code>{" "} | |
| 272 | file (version 2) in <code className="text-fg-soft">.g1t/</code> or <code className="text-fg-soft">.github/</code>. Security | |
| 273 | updates follow the same file. | |
| 274 | </p> | |
| 275 | </> | |
| 276 | )} | |
| 277 | ||
| 278 | <div className={`${titled ? "mt-3 " : ""}flex items-start gap-1.5 text-xs text-muted`}> | |
| 279 | {state.found && state.path ? ( | |
| 280 | <> | |
| 281 | {state.problems.length > 0 ? ( | |
| 282 | <FileWarning size={13} className="shrink-0 text-danger" /> | |
| 283 | ) : ( | |
| 284 | <FileCode size={13} className="shrink-0 text-accent" /> | |
| 285 | )} | |
| 286 | <span> | |
| 287 | Reading{" "} | |
| 288 | {fileLink ? ( | |
| 289 | <Link to={fileLink} className="font-mono text-fg-soft hover:underline"> | |
| 290 | {state.path} | |
| 291 | </Link> | |
| 292 | ) : ( | |
| 293 | <span className="font-mono text-fg-soft">{state.path}</span> | |
| 294 | )} | |
| 295 | {state.readAt && ( | |
| 296 | <> | |
| 297 | , read <TimeAgo at={state.readAt} /> | |
| 298 | </> | |
| 299 | )} | |
| 300 | {state.problems.length === 0 && ( | |
| 301 | <> | |
| 302 | : {state.updates.length} {state.updates.length === 1 ? "entry" : "entries"} | |
| 303 | </> | |
| 304 | )} | |
| 305 | </span> | |
| 306 | </> | |
| 307 | ) : ( | |
| 308 | <> | |
| 309 | <CircleDot size={13} className="shrink-0 text-faint" /> | |
| 310 | <span> | |
| 311 | No <code>dependabot.yml</code> on the default branch. | |
| 312 | </span> | |
| 313 | </> | |
| 314 | )} | |
| 315 | </div> | |
| 316 | {state.ignoredPaths.length > 0 && ( | |
| 317 | <p className="mt-1 flex items-start gap-1.5 text-xs text-warn"> | |
| 318 | <Info size={12} className="mt-0.5 shrink-0" /> | |
| 319 | <span> | |
| 320 | Also found {state.ignoredPaths.map((path) => <code key={path}>{path} </code>)} | |
| 321 | and ignored it: the file under <code>.g1t/</code> is read in its place. | |
| 322 | </span> | |
| 323 | </p> | |
| 324 | )} | |
| 325 | ||
| 326 | {state.problems.length > 0 && ( | |
| 327 | <div className="mt-3 rounded-lg border border-danger/40 bg-danger/5 p-3"> | |
| 328 | <p className="text-xs font-medium text-danger"> | |
| 329 | {state.problems.length === 1 ? "1 problem" : `${state.problems.length} problems`}. Nothing is updated from this file until | |
| 330 | {state.problems.length === 1 ? " it is" : " they are"} fixed. | |
| 331 | </p> | |
| 332 | <ul className="mt-2 space-y-1 text-xs"> | |
| 333 | {state.problems.map((problem) => ( | |
| 334 | <li key={`${problem.line}:${problem.column}:${problem.key}:${problem.message}`} className="flex gap-2"> | |
| 335 | <span className="w-14 shrink-0 font-mono text-faint tabular-nums"> | |
| 336 | {problem.line > 0 ? `Line ${problem.line}` : "File"} | |
| 337 | </span> | |
| 338 | <span className="min-w-0 text-fg/90 wrap-anywhere"> | |
| 339 | {problem.key && <code className="mr-1 text-fg-soft">{problem.key}</code>} | |
| 340 | <Said text={problem.message} /> | |
| 341 | </span> | |
| 342 | </li> | |
| 343 | ))} | |
| 344 | </ul> | |
| 345 | </div> | |
| 346 | )} | |
| 347 | ||
| 348 | {state.updates.length > 0 && ( | |
| 349 | <section className="mt-4"> | |
| 350 | <Heading>Entries</Heading> | |
| 351 | <ul className="mt-2 space-y-2"> | |
| 352 | {state.updates.map((entry) => ( | |
| 353 | <Entry key={entry.id} entry={entry} action={action} canCheck={canCheck && state.problems.length === 0} /> | |
| 354 | ))} | |
| 355 | </ul> | |
| 356 | </section> | |
| 357 | )} | |
| 358 | ||
| 359 | {state.registries.length > 0 && ( | |
| 360 | <section className="mt-4"> | |
| 361 | <Heading>Private registries</Heading> | |
| 362 | <ul className="mt-2 divide-y divide-line/60 text-xs"> | |
| 363 | {state.registries.map((registry) => ( | |
| 364 | <li key={registry.name} className="flex flex-wrap items-center gap-x-3 gap-y-0.5 py-1.5"> | |
| 365 | <span className="font-mono text-fg-soft">{registry.name}</span> | |
| 366 | <span className="text-faint">{registry.kind}</span> | |
| 367 | <span className="min-w-0 font-mono text-muted wrap-anywhere">{registry.url}</span> | |
| 368 | {registry.secrets.length > 0 && ( | |
| 369 | <span className="inline-flex items-center gap-1 text-muted"> | |
| 370 | <KeyRound size={11} /> | |
| 371 | {registry.secrets.join(", ")} | |
| 372 | </span> | |
| 373 | )} | |
| 374 | </li> | |
| 375 | ))} | |
| 376 | </ul> | |
| 377 | </section> | |
| 378 | )} | |
| 379 | ||
| 380 | <section className="mt-4"> | |
| 381 | <Heading>Update pull requests</Heading> | |
| 382 | {pulls.length > 0 ? ( | |
| 383 | <ul className="mt-1 divide-y divide-line/60"> | |
| 384 | {pulls.map((pull) => ( | |
| 385 | <PullRow key={`${pull.branch}:${pull.updatedAt}`} pull={pull} base={base} /> | |
| 386 | ))} | |
| 387 | </ul> | |
| 388 | ) : ( | |
| 389 | <p className="mt-2 text-xs text-muted">None open.</p> | |
| 390 | )} | |
| 391 | </section> | |
| 392 | ||
| 393 | {state.ignores.length > 0 && ( | |
| 394 | <section className="mt-4"> | |
| 395 | <Heading>Ignored in comments</Heading> | |
| 396 | <ul className="mt-2 space-y-1 text-xs"> | |
| 397 | {state.ignores.map((condition) => ( | |
| 398 | <li | |
| 399 | key={`${condition.ecosystem}:${condition.dependency}:${conditionText(condition)}`} | |
| 400 | className="flex flex-wrap items-center gap-x-2 gap-y-0.5" | |
| 401 | > | |
| 402 | <EyeOff size={11} className="shrink-0 text-faint" /> | |
| 403 | <span className="font-mono text-fg-soft">{condition.dependency}</span> | |
| 404 | <span className="text-muted">{conditionText(condition)}</span> | |
| 405 | <span className="text-faint"> | |
| 406 | {condition.ecosystem} · @{condition.by} | |
| 407 | {condition.pull != null && ( | |
| 408 | <> | |
| 409 | {" "} | |
| 410 | in{" "} | |
| 411 | <Link to={`${base}/pulls/${condition.pull}`} className="hover:underline"> | |
| 412 | #{condition.pull} | |
| 413 | </Link> | |
| 414 | </> | |
| 415 | )} | |
| 416 | </span> | |
| 417 | </li> | |
| 418 | ))} | |
| 419 | </ul> | |
| 420 | <p className="mt-2 text-xs text-faint"> | |
| 421 | Comment <code>@g1t unignore <dependency></code> on an update pull request to undo one. | |
| 422 | </p> | |
| 423 | </section> | |
| 424 | )} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 425 | </Card> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 426 | ); |
| 427 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.