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.
| 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. | 1 | import { Check, Copy, LoaderCircle } from "lucide-react"; |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 2 | import { type ComponentProps, Fragment, type ReactNode, useState } from "react"; |
| 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. | 3 | import { Link, type LinkProps, NavLink, useLocation, useNavigation } from "react-router"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 4 | |
| 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. | 5 | import { cn } from "../../lib/cn"; |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 6 | import { linkPaths } from "../../lib/compute"; |
| Merge branch 'worktree-agent-a985c01f43538af01' | 7 | import { type Submission, isPending } from "../../lib/pending"; |
| 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. | 8 | import { Button, type ButtonSize, type ButtonVariant, buttonVariants } from "./button"; |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 9 | |
| 10 | export function Field({ | |
| 11 | label, | |
| 12 | hint, | |
| 13 | children, | |
| 14 | }: { | |
| 15 | label: string; | |
| 16 | hint?: string; | |
| 17 | children: ReactNode; | |
| 18 | }) { | |
| 19 | return ( | |
| 20 | <label className="block"> | |
| 21 | <span className="mb-1.5 block text-sm font-medium text-muted"> | |
| 22 | {label} | |
| 23 | </span> | |
| 24 | {children} | |
| 25 | {hint && <span className="mt-1.5 block text-xs text-faint">{hint}</span>} | |
| 26 | </label> | |
| 27 | ); | |
| 28 | } | |
| 29 | ||
| 30 | const CONTROL = | |
| 31 | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"; | |
| 32 | ||
| 33 | /** | |
| 34 | * Attributes for a field that is not part of signing in. Password managers | |
| 35 | * guess from names such as "name", "title" and "username" and offer to | |
| 36 | * fill a login into them; these tell 1Password, LastPass, Bitwarden and | |
| 37 | * Dashlane to leave the field alone. A field that states what it takes with | |
| 38 | * `autoComplete` is left to them. | |
| 39 | */ | |
| 40 | export function notACredential(autoComplete?: string) { | |
| 41 | if (autoComplete) return {}; | |
| 42 | return { | |
| 43 | autoComplete: "off", | |
| 44 | "data-1p-ignore": true, | |
| 45 | "data-lpignore": "true", | |
| 46 | "data-bwignore": true, | |
| 47 | "data-form-type": "other", | |
| 48 | }; | |
| 49 | } | |
| 50 | ||
| 51 | export function Input(props: ComponentProps<"input">) { | |
| 52 | return <input {...notACredential(props.autoComplete)} {...props} className={CONTROL} />; | |
| 53 | } | |
| 54 | ||
| 55 | export function Textarea(props: ComponentProps<"textarea">) { | |
| 56 | return ( | |
| 57 | <textarea | |
| 58 | {...notACredential(props.autoComplete)} | |
| 59 | {...props} | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 60 | className={`${CONTROL} font-mono placeholder:font-sans`} |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 61 | /> |
| 62 | ); | |
| 63 | } | |
| 64 | ||
| 65 | /** One tab in the row under a repository's or a workspace's header. */ | |
| 66 | export function TabLink({ | |
| 67 | to, | |
| 68 | also, | |
| 69 | end, | |
| 70 | icon, | |
| 71 | count, | |
| 72 | children, | |
| 73 | }: { | |
| 74 | to: string; | |
| 75 | /** Another path prefix under which this tab is the current one. */ | |
| 76 | also?: string; | |
| 77 | end?: boolean; | |
| 78 | icon: ReactNode; | |
| 79 | count?: number; | |
| 80 | children: ReactNode; | |
| 81 | }) { | |
| 82 | const { pathname } = useLocation(); | |
| 83 | return ( | |
| 84 | <NavLink | |
| 85 | to={to} | |
| 86 | end={end} | |
| 87 | className={({ isActive }) => | |
| 88 | `-mb-px flex items-center gap-2 border-b-2 px-3 pb-3 text-sm whitespace-nowrap transition-colors ${ | |
| 89 | isActive || (also && pathname.startsWith(also + "/")) | |
| 90 | ? "border-accent font-medium text-fg" | |
| 91 | : "border-transparent text-muted hover:text-fg" | |
| 92 | }` | |
| 93 | } | |
| 94 | > | |
| 95 | {icon} | |
| 96 | {children} | |
| 97 | {count != null && count > 0 && ( | |
| 98 | <span className="rounded-full bg-raised px-1.5 py-px text-xs text-muted"> | |
| 99 | {count} | |
| 100 | </span> | |
| 101 | )} | |
| 102 | </NavLink> | |
| 103 | ); | |
| 104 | } | |
| 105 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 106 | /** Soon, beside something that is coming: the same small pill in the sidebar and in tabs. */ |
| 107 | export function SoonPill() { | |
| 108 | return ( | |
| 109 | <span className="shrink-0 rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line"> | |
| 110 | Soon | |
| 111 | </span> | |
| 112 | ); | |
| 113 | } | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 114 | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 115 | /** |
| 116 | * Whether the submission `fields` names is still working (lib/pending.ts): | |
| 117 | * the page's own navigation, or `fetcher`'s when the form is a fetcher's. | |
| 118 | */ | |
| 119 | export function usePending(fields?: Record<string, string | null | undefined>, fetcher?: Submission): boolean { | |
| 120 | const navigation = useNavigation(); | |
| 121 | return isPending(fetcher ?? navigation, fields); | |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * A form's submit button that says it is working: turned off, with a | |
| 126 | * spinner and `pending` ("Saving…") in place of its words, from the moment | |
| 127 | * it is pressed until the page has loaded what it changed. Its own | |
| 128 | * `name`/`value` say which submission is its; `match` names it otherwise, | |
| 129 | * such as the form's hidden `intent`. `fetcher` when the form is a | |
| 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 | * fetcher's. It is drawn as the Button in ./button, with its `variant` and |
| 131 | * `size`; `icon` says the spinner takes the place of everything inside | |
| 132 | * it. Words with a leading icon should pass `pending`, so the spinner | |
| 133 | * replaces both. | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 134 | */ |
| 135 | export function SubmitButton({ | |
| 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. | 136 | variant, |
| 137 | size, | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 138 | pending, |
| 139 | match, | |
| 140 | fetcher, | |
| 141 | busy, | |
| 142 | icon, | |
| 143 | disabled, | |
| 144 | className, | |
| 145 | children, | |
| 146 | ...props | |
| 147 | }: Omit<ComponentProps<"button">, "type"> & { | |
| 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. | 148 | variant?: ButtonVariant; |
| 149 | size?: ButtonSize; | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 150 | /** The words while it works, such as "Saving…"; its own words when absent. */ |
| 151 | pending?: ReactNode; | |
| 152 | match?: Record<string, string | null | undefined>; | |
| 153 | fetcher?: Submission; | |
| 154 | /** Working for a reason this button cannot see by itself. */ | |
| 155 | busy?: boolean; | |
| 156 | /** An icon button: while it works, the spinner is all it shows. */ | |
| 157 | icon?: boolean; | |
| 158 | }) { | |
| 159 | const own = | |
| 160 | typeof props.name === "string" && props.value != null ? { [props.name]: String(props.value) } : undefined; | |
| 161 | const working = usePending({ ...own, ...match }, fetcher) || Boolean(busy); | |
| 162 | return ( | |
| 163 | <button | |
| 164 | {...props} | |
| 165 | type="submit" | |
| 166 | disabled={disabled || working} | |
| 167 | aria-busy={working || undefined} | |
| 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. | 168 | data-slot="button" |
| 169 | className={cn(buttonVariants({ variant, size }), className)} | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 170 | > |
| 171 | {working ? ( | |
| 172 | <> | |
| 173 | <LoaderCircle size={14} aria-hidden="true" className="shrink-0 animate-spin" /> | |
| 174 | {icon ? null : (pending ?? children)} | |
| 175 | </> | |
| 176 | ) : ( | |
| 177 | children | |
| 178 | )} | |
| 179 | </button> | |
| 180 | ); | |
| 181 | } | |
| 182 | ||
| 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. | 183 | /** A link that looks like the Button in ./button, with its `variant` and `size`. */ |
| 184 | export function ButtonLink({ variant, size, className, ...props }: LinkProps & { variant?: ButtonVariant; size?: ButtonSize }) { | |
| 185 | return <Link data-slot="button" {...props} className={cn(buttonVariants({ variant, size }), className)} />; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 186 | } |
| 187 | ||
| 188 | /** | |
| 189 | * A message with the g1t pages it names as links, such as the billing page | |
| 190 | * a refusal to start compute points to. | |
| 191 | */ | |
| 192 | export function Linked({ text }: { text: string }) { | |
| 193 | return ( | |
| 194 | <> | |
| 195 | {linkPaths(text).map((piece, at) => | |
| 196 | piece.href ? ( | |
| 197 | <Link key={at} to={piece.href} className="underline underline-offset-2"> | |
| 198 | {piece.text} | |
| 199 | </Link> | |
| 200 | ) : ( | |
| 201 | piece.text | |
| 202 | ), | |
| 203 | )} | |
| 204 | </> | |
| 205 | ); | |
| 206 | } | |
| 207 | ||
| 208 | export function ErrorText({ children }: { children: ReactNode }) { | |
| 209 | if (!children) return null; | |
| 210 | if (typeof children !== "string") return <p className="text-sm text-danger">{children}</p>; | |
| 211 | return ( | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 212 | <p className="text-sm text-danger"> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 213 | <Linked text={children} /> |
| 214 | </p> | |
| 215 | ); | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * What a workspace's plan says before someone tries to start compute it | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 220 | * would refuse ("Workflows need a paid workspace or the free trial"), with |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 221 | * where to fix it. |
| 222 | */ | |
| 223 | export function ComputeNote({ note }: { note: string | null | undefined }) { | |
| 224 | return note ? ( | |
| 225 | <p className="text-sm text-muted"> | |
| 226 | <Linked text={note} /> | |
| 227 | </p> | |
| 228 | ) : null; | |
| 229 | } | |
| 230 | ||
| 231 | /** A line of text (usually a command) with a copy button. */ | |
| 232 | export function CopyLine({ | |
| 233 | text, | |
| 234 | prompt, | |
| Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet | 235 | disabled, |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 236 | breakAtSlashes, |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 237 | }: { |
| 238 | text: string; | |
| 239 | prompt?: boolean; | |
| Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet | 240 | /** Shown, so it is clear what will be there, but not yet usable: dimmed, with no copy. */ |
| 241 | disabled?: boolean; | |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 242 | /** |
| 243 | * For a narrow box: an address longer than the line may wrap after a | |
| 244 | * `/`, so all of it shows, rather than scroll. | |
| 245 | */ | |
| 246 | breakAtSlashes?: boolean; | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 247 | }) { |
| 248 | const [copied, setCopied] = useState(false); | |
| 249 | return ( | |
| Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet | 250 | <div |
| 251 | aria-disabled={disabled || undefined} | |
| 252 | className={`group flex items-center gap-3 rounded-lg border border-line bg-surface py-2 pr-2 pl-3.5 font-mono text-[0.8125rem] ${disabled ? "cursor-not-allowed text-faint select-none" : ""}`} | |
| 253 | > | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 254 | {/* Wraps between words rather than hides: a command is no use half |
| 255 | seen. A word itself, an address or a flag, is never broken in | |
| 256 | two, which would change what is copied by eye; one longer than | |
| 257 | the line scrolls sideways instead. */} | |
| 258 | <code className="min-w-0 grow overflow-x-auto whitespace-pre-wrap [scrollbar-width:thin]"> | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 259 | {prompt && <span className="mr-2 text-faint select-none">$</span>} |
| 260 | {text.split(/(\s+)/).map((part, index) => | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 261 | /\S/.test(part) ? ( |
| Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md | 262 | breakAtSlashes ? ( |
| 263 | // Each piece whole, with a chance to wrap after each `/`. | |
| 264 | <span key={index}> | |
| 265 | {part.split(/(?<=\/)(?!\/)/).map((piece, at) => ( | |
| 266 | <Fragment key={at}> | |
| 267 | {at > 0 && <wbr />} | |
| 268 | <span className="whitespace-nowrap">{piece}</span> | |
| 269 | </Fragment> | |
| 270 | ))} | |
| 271 | </span> | |
| 272 | ) : ( | |
| 273 | <span key={index} className="whitespace-nowrap"> | |
| 274 | {part} | |
| 275 | </span> | |
| 276 | ) | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 277 | ) : ( |
| 278 | part | |
| 279 | ), | |
| 280 | )} | |
| 281 | </code> | |
| 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. | 282 | <Button |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 283 | type="button" |
| 284 | aria-label="Copy" | |
| Code button: the SSH tab shows the address it will have, disabled, with why it is not on yet | 285 | disabled={disabled} |
| 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. | 286 | variant="ghost" |
| 287 | size="inline" | |
| 288 | className="p-1.5 text-faint" | |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 289 | onClick={() => { |
| 290 | void navigator.clipboard.writeText(text); | |
| 291 | setCopied(true); | |
| 292 | setTimeout(() => setCopied(false), 1500); | |
| 293 | }} | |
| 294 | > | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 295 | {copied ? <Check size={14} className="text-success" /> : <Copy size={14} />} |
| 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. | 296 | </Button> |
| The docs' Copy page menu keeps its own spacing, with an icon per item | 297 | </div> |
| 298 | ); | |
| 299 | } | |
| 300 | ||
| 301 | const UNITS: [string, number][] = [ | |
| 302 | ["d", 86_400_000], | |
| 303 | ["h", 3_600_000], | |
| 304 | ["m", 60_000], | |
| 305 | ]; | |
| 306 | ||
| 307 | /** Compact relative time such as "5m ago". */ | |
| 308 | export function TimeAgo({ at }: { at: string | number }) { | |
| 309 | // Timestamps are RFC 3339 strings; a few older ones are still numbers. | |
| 310 | const elapsed = Date.now() - new Date(at).getTime(); | |
| 311 | const unit = UNITS.find(([, size]) => elapsed >= size); | |
| 312 | const label = unit ? `${Math.floor(elapsed / unit[1])}${unit[0]} ago` : "just now"; | |
| 313 | return ( | |
| 314 | // The server and the browser render at slightly different times. | |
| 315 | <time dateTime={new Date(at).toISOString()} suppressHydrationWarning> | |
| 316 | {label} | |
| 317 | </time> | |
| 318 | ); | |
| 319 | } | |
| 320 | ||
| 321 | export function EmptyState({ | |
| 322 | title, | |
| 323 | children, | |
| 324 | }: { | |
| 325 | title: string; | |
| 326 | children?: ReactNode; | |
| 327 | }) { | |
| 328 | return ( | |
| 329 | <div className="rounded-lg border border-dashed border-line px-6 py-12 text-center"> | |
| 330 | <p className="font-medium">{title}</p> | |
| 331 | {children && <div className="mt-1.5 text-sm text-muted">{children}</div>} | |
| 332 | </div> | |
| 333 | ); | |
| 334 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.