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.
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 1 | /** |
| 2 | * A person's last year on their profile: a square a day, a column a week, | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 3 | * shaded in the accent by how much they did (commits pushed to a default |
| 4 | * branch, issues and pull requests opened, reviews given) on repositories | |
| 5 | * the viewer can see. The layout is | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 6 | * lib/contribution-calendar.ts; the counts come from the work service's |
| 7 | * `contributions`, which never counts work the viewer could not open. | |
| 8 | * | |
| 9 | * One hint serves every square: it follows the pointer, so a year of days | |
| 10 | * costs one tooltip, not 365. | |
| 11 | */ | |
| 12 | import { type PointerEvent, useEffect, useMemo, useRef, useState } from "react"; | |
| 13 | ||
| 14 | import { type Level, buildCalendar, dayLabel, totalLabel } from "../lib/contribution-calendar"; | |
| 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. | 15 | import { Card } from "./ui/card"; |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 16 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; |
| 17 | ||
| 18 | /** A square's side and the space between squares, in pixels. */ | |
| 19 | const CELL = 10; | |
| 20 | const GAP = 3; | |
| 21 | const STEP = CELL + GAP; | |
| 22 | ||
| 23 | /** Each level's shade: the raised gray for nothing, then the accent, stronger with more. */ | |
| 24 | const SHADES: Record<Level, string> = { | |
| 25 | 0: "bg-raised", | |
| 26 | 1: "bg-accent/25", | |
| 27 | 2: "bg-accent/45", | |
| 28 | 3: "bg-accent/70", | |
| 29 | 4: "bg-accent", | |
| 30 | }; | |
| 31 | ||
| 32 | const WEEKDAYS = ["", "Mon", "", "Wed", "", "Fri", ""]; | |
| 33 | ||
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 34 | type Hovered = { date: string; count: number; commits: number; left: number; top: number }; |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 35 | |
| 36 | export function ContributionCalendar({ | |
| 37 | days, | |
| 38 | total, | |
| 39 | today, | |
| 40 | from, | |
| 41 | }: { | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 42 | days: readonly { date: string; count: number; commits?: number }[]; |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 43 | total: number; |
| 44 | /** `YYYY-MM-DD`, UTC: the last day shown. */ | |
| 45 | today: string; | |
| 46 | /** The first day counted; a year before `today` when absent. */ | |
| 47 | from?: string; | |
| 48 | }) { | |
| 49 | const calendar = useMemo(() => buildCalendar(days, today, from), [days, today, from]); | |
| 50 | const scroller = useRef<HTMLDivElement>(null); | |
| 51 | const [hovered, setHovered] = useState<Hovered | null>(null); | |
| 52 | ||
| 53 | // On a narrow screen the calendar scrolls inside its card; start at today. | |
| 54 | useEffect(() => { | |
| 55 | const element = scroller.current; | |
| 56 | if (element) element.scrollLeft = element.scrollWidth; | |
| 57 | }, []); | |
| 58 | ||
| 59 | const over = (event: PointerEvent<HTMLDivElement>) => { | |
| 60 | const cell = (event.target as HTMLElement).closest<HTMLElement>("[data-date]"); | |
| 61 | if (!cell) return setHovered(null); | |
| 62 | const date = cell.dataset.date!; | |
| 63 | if (hovered?.date === date) return; | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 64 | setHovered({ date, count: Number(cell.dataset.count ?? 0), commits: Number(cell.dataset.commits ?? 0), left: cell.offsetLeft, top: cell.offsetTop }); |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 65 | }; |
| 66 | ||
| 67 | const heading = totalLabel(total); | |
| 68 | 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. | 69 | <Card asChild tone="plain" className="bg-surface/50 px-4 pt-3.5 pb-3"> |
| 70 | <section aria-label="Contributions"> | |
| 71 | <h2 className="text-sm font-medium">{heading}</h2> | |
| 72 | <div ref={scroller} className="mt-3 overflow-x-auto overflow-y-hidden pb-1 [scrollbar-width:thin]"> | |
| 73 | <div className="flex w-max text-[0.625rem] leading-none text-faint"> | |
| 74 | {/* The weekdays, under the months' row. */} | |
| 75 | <div aria-hidden="true" className="mr-1.5 flex shrink-0 flex-col" style={{ gap: GAP, paddingTop: 16 }}> | |
| 76 | {WEEKDAYS.map((label, index) => ( | |
| 77 | <span key={index} className="flex items-center" style={{ height: CELL }}> | |
| 78 | {label} | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 79 | </span> |
| 80 | ))} | |
| 81 | </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. | 82 | <div |
| 83 | role="img" | |
| 84 | aria-label={heading} | |
| 85 | className="relative" | |
| 86 | onPointerOver={over} | |
| 87 | // A tap's pointer leaves as the finger lifts: its hint stays until the next tap elsewhere. | |
| 88 | onPointerLeave={(event) => event.pointerType === "mouse" && setHovered(null)} | |
| 89 | > | |
| 90 | <div aria-hidden="true" className="relative h-4" style={{ width: calendar.weeks.length * STEP - GAP }}> | |
| 91 | {calendar.months.map((month) => ( | |
| 92 | <span key={`${month.week}:${month.label}`} className="absolute top-0" style={{ left: month.week * STEP }}> | |
| 93 | {month.label} | |
| 94 | </span> | |
| 95 | ))} | |
| 96 | </div> | |
| 97 | <div className="flex" style={{ gap: GAP }}> | |
| 98 | {calendar.weeks.map((week, column) => ( | |
| 99 | <div key={column} className="flex flex-col" style={{ gap: GAP }}> | |
| 100 | {week.map((day, row) => | |
| 101 | day ? ( | |
| 102 | <span | |
| 103 | key={row} | |
| 104 | data-date={day.date} | |
| 105 | data-count={day.count} | |
| 106 | data-commits={day.commits} | |
| 107 | className={`block rounded-[2px] ${SHADES[day.level]} ${hovered?.date === day.date ? "ring-1 ring-fg/60" : ""}`} | |
| 108 | style={{ width: CELL, height: CELL }} | |
| 109 | /> | |
| 110 | ) : ( | |
| 111 | <span key={row} className="block" style={{ width: CELL, height: CELL }} /> | |
| 112 | ), | |
| 113 | )} | |
| 114 | </div> | |
| 115 | ))} | |
| 116 | </div> | |
| 117 | {/* The one hint, over whichever square the pointer is on. */} | |
| 118 | <Tooltip open={hovered != null} onOpenChange={(open) => !open && setHovered(null)}> | |
| 119 | <TooltipTrigger asChild> | |
| 120 | <span | |
| 121 | aria-hidden="true" | |
| 122 | className="pointer-events-none absolute" | |
| 123 | style={{ left: hovered?.left ?? 0, top: hovered?.top ?? 0, width: CELL, height: CELL }} | |
| 124 | /> | |
| 125 | </TooltipTrigger> | |
| 126 | <TooltipContent side="top">{hovered ? dayLabel(hovered.count, hovered.date, hovered.commits) : null}</TooltipContent> | |
| 127 | </Tooltip> | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 128 | </div> |
| 129 | </div> | |
| 130 | </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. | 131 | <div aria-hidden="true" className="mt-2 flex items-center justify-end gap-1 text-[0.6875rem] text-faint"> |
| 132 | <span className="mr-1">Less</span> | |
| 133 | {([0, 1, 2, 3, 4] as Level[]).map((level) => ( | |
| 134 | <span key={level} className={`block rounded-[2px] ${SHADES[level]}`} style={{ width: CELL, height: CELL }} /> | |
| 135 | ))} | |
| 136 | <span className="ml-1">More</span> | |
| 137 | </div> | |
| 138 | </section> | |
| 139 | </Card> | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 140 | ); |
| 141 | } |