Skip to content

Commit

Pages: hover hints are the site's tooltip; disabled buttons say why on focus and tap

Commit messages on branches, tags, compare and commits; package digests and badges; the repo tabs' descriptions; and the buttons disabled for want of a permission (Plan it, Turn on deployments, Save, Merge, Unlink, the upkeep switch) now use Hint, the disabled ones through its focusable wrapper. The compact deploy buttons get an aria-label, which their title had stood in for.

syntaqxcommitted Parent03df360Browse files
22 files+237−1810/22 viewed
+9−6
88 import { page } from "../../lib/meta";
99 import { LogText, Notes, StatusIcon, duration, shortRef, standingWord, useJobLog } from "../../components/actions";
1010 import { ErrorText, SubmitButton, TimeAgo, usePending } from "../../components/ui";
11+import { Hint } from "../../components/ui/hint";
1112 import { listArtifacts } from "../../lib/artifacts.server";
1213 import { actions } from "../../lib/services.server";
1314 import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server";
106107 );
107108 // A link to the runners for those who manage them, a label for everyone else.
108109 return runnersPage ? (
109− <Link to={`/${workspace}/-/runners`} className={`${badge} hover:text-fg`} title="Self-hosted runner">
110− {label}
111− </Link>
110+ <Hint label="Self-hosted runner">
111+ <Link to={`/${workspace}/-/runners`} className={`${badge} hover:text-fg`}>
112+ {label}
113+ </Link>
114+ </Hint>
112115 ) : (
113− <span className={badge} title="Self-hosted runner">
114− {label}
115− </span>
116+ <Hint label="Self-hosted runner">
117+ <span className={badge}>{label}</span>
118+ </Hint>
116119 );
117120 }
118121 if (!job.startedAt) return null;
+6−3
44 import type { Route } from "./+types/branches";
55 import { ActiveBranches } from "../../components/branches";
66 import { Avatar, EmptyState, TimeAgo, notACredential } from "../../components/ui";
7+import { Hint } from "../../components/ui/hint";
78 import { readBranches } from "../../lib/branches.server";
89 import { page } from "../../lib/meta";
910 import { deployments, repos, work } from "../../lib/services.server";
9697 <Avatar name={head.author} size={13} />
9798 <span className="shrink-0">{head.author}</span>
9899 <span className="text-faint">·</span>
99− <Link to={`${base}/commit/${head.hash}`} className="min-w-0 truncate hover:text-fg" title={head.message}>
100− {head.message}
101− </Link>
100+ <Hint label={head.message}>
101+ <Link to={`${base}/commit/${head.hash}`} className="min-w-0 truncate hover:text-fg">
102+ {head.message}
103+ </Link>
104+ </Hint>
102105 <span className="shrink-0 text-faint">
103106 · <TimeAgo at={head.at} />
104107 </span>
+6−3
99 import { page } from "../../lib/meta";
1010 import { DiffView } from "../../components/diff-view";
1111 import { Avatar, TimeAgo } from "../../components/ui";
12+import { Hint } from "../../components/ui/hint";
1213 import { Skeleton } from "../../components/ui/skeleton";
1314 import { immutable } from "../../lib/immutable.server";
1415 import { pullForCommit } from "../../lib/provenance.server";
157158 <span className="flex items-center gap-2">
158159 <Avatar name={owner?.username ?? commit.author.name} image={owner?.avatar} size={20} />
159160 {owner ? (
160− <Link to={`/u/${owner.username}`} className="font-medium hover:underline" title={commit.author.name}>
161− {owner.username}
162− </Link>
161+ <Hint label={commit.author.name}>
162+ <Link to={`/u/${owner.username}`} className="font-medium hover:underline">
163+ {owner.username}
164+ </Link>
165+ </Hint>
163166 ) : (
164167 <span className="font-medium">{commit.author.name}</span>
165168 )}
+6−3
66 import type { Route } from "./+types/commits";
77 import { page } from "../../lib/meta";
88 import { Avatar, EmptyState, TimeAgo } from "../../components/ui";
9+import { Hint } from "../../components/ui/hint";
910 import { accounts, repos } from "../../lib/services.server";
1011 import { getViewer, unwrap } from "../../lib/session.server";
1112
7576 {rest && <p className="mt-1 line-clamp-1 text-sm text-muted">{rest}</p>}
7677 <p className="mt-1 text-xs text-faint">
7778 {owner ? (
78− <Link to={`/u/${owner.username}`} className="relative z-10 text-muted hover:text-fg" title={commit.author.name}>
79− {owner.username}
80− </Link>
79+ <Hint label={commit.author.name}>
80+ <Link to={`/u/${owner.username}`} className="relative z-10 text-muted hover:text-fg">
81+ {owner.username}
82+ </Link>
83+ </Hint>
8184 ) : (
8285 commit.author.name
8386 )}{" "}
+6−3
66 import type { Route } from "./+types/compare";
77 import { DiffView } from "../../components/diff-view";
88 import { Avatar, ButtonLink, EmptyState, TimeAgo } from "../../components/ui";
9+import { Hint } from "../../components/ui/hint";
910 import { page } from "../../lib/meta";
1011 import { repos } from "../../lib/services.server";
1112 import { getViewer, unwrap } from "../../lib/session.server";
133134 <li key={commit.hash} className="flex items-center gap-3 px-4 py-2.5 text-sm">
134135 <GitCommitHorizontal size={15} className="shrink-0 text-faint" />
135136 <Avatar name={commit.author} size={16} />
136− <Link to={`${repoBase}/commit/${commit.hash}`} className="min-w-0 grow truncate hover:text-accent" title={commit.message}>
137− {commit.message}
138− </Link>
137+ <Hint label={commit.message}>
138+ <Link to={`${repoBase}/commit/${commit.hash}`} className="min-w-0 grow truncate hover:text-accent">
139+ {commit.message}
140+ </Link>
141+ </Hint>
139142 <span className="hidden shrink-0 text-xs text-muted sm:inline">{commit.author}</span>
140143 <Link to={`${repoBase}/commit/${commit.hash}`} className="shrink-0 font-mono text-xs text-faint hover:text-fg">
141144 {commit.hash.slice(0, 7)}
+29−29
99 import type { Route } from "./+types/deployments";
1010 import { page } from "../../lib/meta";
1111 import { ButtonLink, ComputeNote, EmptyState, ErrorText, SubmitButton, TimeAgo } from "../../components/ui";
12+import { Hint } from "../../components/ui/hint";
1213 import { computeNoteFor } from "../../lib/compute.server";
1314 import { billing, deployments, projects } from "../../lib/services.server";
1415 import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
132133 </p>
133134 ) : (
134135 <Form method="post" className="mt-4">
135− <SubmitButton
136− variant="accent"
137− name="intent"
138− value="enable"
139− pending="Turning on…"
140− disabled={!can.manage_integrations}
141− title={whyNot(can, "manage_integrations")}
142− >
143− <Rocket size={14} />
144− Turn on deployments
145− </SubmitButton>
136+ <Hint label={whyNot(can, "manage_integrations")} disabled={!can.manage_integrations}>
137+ <SubmitButton variant="accent" name="intent" value="enable" pending="Turning on…" disabled={!can.manage_integrations}>
138+ <Rocket size={14} />
139+ Turn on deployments
140+ </SubmitButton>
141+ </Hint>
146142 </Form>
147143 )}
148144 </section>
297293 <Form method="post" className="flex items-center gap-2">
298294 {branch != null && <input type="hidden" name="branch" value={branch} />}
299295 <input type="hidden" name="app" value={app} />
300− <SubmitButton
301− variant="quiet"
302− name="intent"
303− value="redeploy"
304− match={{ app }}
305− icon={compact}
306− pending="Redeploying…"
307− title="Build again from the current head"
308− >
309− <RotateCw size={13} />
310− {!compact && "Redeploy"}
311− </SubmitButton>
312− {up && (
296+ <Hint label="Build again from the current head">
313297 <SubmitButton
314298 variant="quiet"
315299 name="intent"
316− value="take-down"
300+ value="redeploy"
317301 match={{ app }}
318302 icon={compact}
319− pending="Taking down…"
320− title="Take it down now"
303+ pending="Redeploying…"
304+ aria-label={compact ? "Redeploy" : undefined}
321305 >
322− <Trash2 size={13} />
323− {!compact && "Take down"}
306+ <RotateCw size={13} />
307+ {!compact && "Redeploy"}
324308 </SubmitButton>
309+ </Hint>
310+ {up && (
311+ <Hint label="Take it down now">
312+ <SubmitButton
313+ variant="quiet"
314+ name="intent"
315+ value="take-down"
316+ match={{ app }}
317+ icon={compact}
318+ pending="Taking down…"
319+ aria-label={compact ? "Take down" : undefined}
320+ >
321+ <Trash2 size={13} />
322+ {!compact && "Take down"}
323+ </SubmitButton>
324+ </Hint>
325325 )}
326326 </Form>
327327 );
+8−7
77 import { page } from "../../lib/meta";
88 import { ButtonLink, ComputeNote, EmptyState, ErrorText, SubmitButton, TimeAgo } from "../../components/ui";
99 import { Checkbox } from "../../components/ui/checkbox";
10+import { Hint } from "../../components/ui/hint";
1011 import {
1112 Assignee,
1213 AssigneeStack,
279280 <span className="mt-0.5 flex shrink-0 items-center gap-3 text-xs text-muted">
280281 <AssigneeStack people={issue.assignees} />
281282 {issue.pullCount > 0 && (
282− <span
283− className="flex items-center gap-1"
284− title={`${issue.pullCount} pull ${issue.pullCount === 1 ? "request" : "requests"}`}
285− >
286− <GitPullRequest size={13} />
287− {issue.pullCount}
288− </span>
283+ <Hint label={`${issue.pullCount} pull ${issue.pullCount === 1 ? "request" : "requests"}`}>
284+ <span className="flex items-center gap-1">
285+ <GitPullRequest size={13} />
286+ {issue.pullCount}
287+ <span className="sr-only">pull {issue.pullCount === 1 ? "request" : "requests"}</span>
288+ </span>
289+ </Hint>
289290 )}
290291 {issue.commentCount > 0 && (
291292 <span className="flex items-center gap-1">
+21−20
1010 import { page } from "../../lib/meta";
1111 import { type Tab as PageTab, tabsFor } from "../../lib/project-nav";
1212 import { Pill, SoonPill } from "../../components/ui";
13+import { Hint } from "../../components/ui/hint";
1314 import { TabStrip } from "../../components/ui/tab-strip";
1415 import { WatchMenu } from "../../components/notifications";
1516 import { PinButton } from "../../components/pin-button";
144145 return (
145146 <TabStrip label="Views" className="-mb-px gap-1">
146147 {tabs.map((tab) => (
147− <Link
148− key={tab.path}
149− to={`${base}/${tab.path}`}
150− title={tab.about}
151− prefetch="intent"
152− // Current by the tab's own rule: a link's address alone would also
153− // mark Overview current on every page under it.
154− aria-current={current(tab) ? "page" : undefined}
155− data-active={current(tab) || undefined}
156− className={`flex items-center gap-1.5 border-b-2 px-3 pt-1 pb-2.5 text-sm transition-colors ${
157− current(tab)
158− ? "border-accent font-medium text-fg"
159− : tab.soon
160− ? "border-transparent text-faint hover:text-muted"
161− : "border-transparent text-muted hover:text-fg"
162− }`}
163− >
164− {tab.label}
165− {tab.soon && <SoonPill />}
166− </Link>
148+ <Hint key={tab.path} label={tab.about}>
149+ <Link
150+ to={`${base}/${tab.path}`}
151+ prefetch="intent"
152+ // Current by the tab's own rule: a link's address alone would also
153+ // mark Overview current on every page under it.
154+ aria-current={current(tab) ? "page" : undefined}
155+ data-active={current(tab) || undefined}
156+ className={`flex items-center gap-1.5 border-b-2 px-3 pt-1 pb-2.5 text-sm transition-colors ${
157+ current(tab)
158+ ? "border-accent font-medium text-fg"
159+ : tab.soon
160+ ? "border-transparent text-faint hover:text-muted"
161+ : "border-transparent text-muted hover:text-fg"
162+ }`}
163+ >
164+ {tab.label}
165+ {tab.soon && <SoonPill />}
166+ </Link>
167+ </Hint>
167168 ))}
168169 </TabStrip>
169170 );
+31−25
4343 import { type ActiveBranch, ActiveBranches } from "../../components/branches";
4444 import { ProductionChecklist } from "../../components/checklist";
4545 import { PackageIcon } from "../../components/package-icon";
46+import { Hint } from "../../components/ui/hint";
4647 import { Loading, Skeleton, SkeletonRows } from "../../components/ui/skeleton";
4748 import { ProductionShot } from "../../components/production-shot";
4849 import { GithubLinkStrip } from "../../components/github";
421422 <span className="font-mono text-faint">#{card.number}</span>
422423 <span className="grow" />
423424 {card.needsYou ? (
424− <span className="inline-flex items-center gap-1 text-warn" title="Stopped: a person decides what happens next">
425− <Hand size={11} /> you
426− </span>
425+ <Hint label="Stopped: a person decides what happens next">
426+ <span className="inline-flex items-center gap-1 text-warn">
427+ <Hand size={11} /> you
428+ <span className="sr-only">: stopped, a person decides what happens next</span>
429+ </span>
430+ </Hint>
427431 ) : stage === "landed" ? (
428432 <TimeAgo at={card.at} />
429433 ) : card.runStarted ? (
436440 )}
437441 </span>
438442 {card.step && (
439− <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70" title={card.step}>
440− {card.runKind}: {card.step}
441− </span>
443+ <Hint label={card.step}>
444+ <span className="mt-1.5 block truncate font-mono text-[0.6875rem] text-fg/70">
445+ {card.runKind}: {card.step}
446+ </span>
447+ </Hint>
442448 )}
443449 </Link>
444450 </li>
771777 )}
772778 {settings?.enabled ? (
773779 loaderData.can.run && <Form method="post">
774− <SubmitButton
775− variant="quiet"
776− name="intent"
777− value="redeploy"
778− pending="Redeploying…"
779− title="Build production again from the default branch"
780− >
781− <RotateCw size={14} />
782− Redeploy
783− </SubmitButton>
780+ <Hint label="Build production again from the default branch">
781+ <SubmitButton variant="quiet" name="intent" value="redeploy" pending="Redeploying…">
782+ <RotateCw size={14} />
783+ Redeploy
784+ </SubmitButton>
785+ </Hint>
784786 </Form>
785787 ) : loaderData.can.manage_integrations && (
786788 <ButtonLink to={`${base}/settings/deployments`} variant="accent">
829831 to={commit ? `${base}/commit/${commit.hash}` : undefined}
830832 value={
831833 commit ? (
832− <span title={commit.message.split("\n")[0]}>
833− <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "}
834− <span className="font-normal text-muted">
835− <TimeAgo at={commit.authoredAt} />
834+ <Hint label={commit.message.split("\n")[0]}>
835+ <span>
836+ <span className="font-mono">{commit.hash.slice(0, 7)}</span>{" "}
837+ <span className="font-normal text-muted">
838+ <TimeAgo at={commit.authoredAt} />
839+ </span>
836840 </span>
837− </span>
841+ </Hint>
838842 ) : (
839843 "No commits yet"
840844 )
897901 {run.title ?? "A run"} {run.number != null && <span className="text-faint">#{run.number}</span>}
898902 </Link>
899903 {run.step && (
900− <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70" title={run.step}>
901− <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" />
902− {run.step}
903− </p>
904+ <Hint label={run.step}>
905+ <p className="mt-1 truncate font-mono text-[0.6875rem] text-fg/70">
906+ <span className="mr-1.5 inline-block size-1.5 animate-pulse rounded-full bg-merged align-middle" />
907+ {run.step}
908+ </p>
909+ </Hint>
904910 )}
905911 <p className="mt-1.5 flex items-center gap-3 text-[0.6875rem] text-faint">
906912 {formatCost(run.costUsd) && <span>{formatCost(run.costUsd)} so far</span>}
+12−7
1212 import { Outcome } from "../../components/outcome";
1313 import { ErrorText, SubmitButton, TimeAgo, usePending } from "../../components/ui";
1414 import { Checkbox } from "../../components/ui/checkbox";
15+import { Hint } from "../../components/ui/hint";
1516 import { Label } from "../../components/work";
1617 import { billing, events, identity, work } from "../../lib/services.server";
1718 import {
263264
264265 {plan.status === "ready" && (
265266 <div className="mt-6 flex flex-wrap items-center gap-3">
266− <SubmitButton variant="accent" name="action" value="assign" pending="Opening issues…" disabled={applying || !loaderData.can.run} title={whyNot(loaderData.can, "run")}>
267− <Sparkles size={15} />
268− Open these and assign g1t
269− </SubmitButton>
270− <SubmitButton variant="quiet" name="action" value="open" pending="Opening issues…" disabled={applying || !loaderData.can.run} title={whyNot(loaderData.can, "run")}>
271− Only open the issues
272− </SubmitButton>
267+ <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}>
268+ <SubmitButton variant="accent" name="action" value="assign" pending="Opening issues…" disabled={applying || !loaderData.can.run}>
269+ <Sparkles size={15} />
270+ Open these and assign g1t
271+ </SubmitButton>
272+ </Hint>
273+ <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}>
274+ <SubmitButton variant="quiet" name="action" value="open" pending="Opening issues…" disabled={applying || !loaderData.can.run}>
275+ Only open the issues
276+ </SubmitButton>
277+ </Hint>
273278 <span className="text-xs text-muted">
274279 Untick any you do not want. Agents work on the independent ones at once
275280 and the rest follow as what they depend on merges.
+7−4
99 import { whyNot } from "../../lib/access";
1010 import { page } from "../../lib/meta";
1111 import { ComputeNote, EmptyState, ErrorText, SubmitButton, Textarea, TimeAgo } from "../../components/ui";
12+import { Hint } from "../../components/ui/hint";
1213 import { computeNoteFor } from "../../lib/compute.server";
1314 import { work } from "../../lib/services.server";
1415 import {
7980 }
8081 />
8182 <div className="flex flex-wrap items-center gap-3">
82− <SubmitButton variant="accent" pending="Starting the planner…" disabled={!loaderData.can.run} title={whyNot(loaderData.can, "run")}>
83− <Sparkles size={15} />
84− Plan it
85− </SubmitButton>
83+ <Hint label={whyNot(loaderData.can, "run")} disabled={!loaderData.can.run}>
84+ <SubmitButton variant="accent" pending="Starting the planner…" disabled={!loaderData.can.run}>
85+ <Sparkles size={15} />
86+ Plan it
87+ </SubmitButton>
88+ </Hint>
8689 <span className="text-xs text-muted">
8790 Nothing is opened until you have read the plan.
8891 </span>
+13−11
6363 usePending,
6464 } from "../../components/ui";
6565 import { CheckboxOption } from "../../components/ui/checkbox";
66+import { Hint } from "../../components/ui/hint";
6667 import { Loading, SkeletonLine } from "../../components/ui/skeleton";
6768 import { TabStrip } from "../../components/ui/tab-strip";
6869 import { WorkflowStatuses } from "../../components/actions";
12131214 />
12141215 )}
12151216 <div className="flex flex-wrap items-center gap-3">
1216− <SubmitButton
1217− variant="accent"
1218− name="action"
1219− value="merge"
1220− disabled={mergeBlocked != null}
1221− title={mergeBlocked ?? undefined}
1222− pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1223− >
1224− {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1225− {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1226− </SubmitButton>
1217+ <Hint label={mergeBlocked} disabled={mergeBlocked != null}>
1218+ <SubmitButton
1219+ variant="accent"
1220+ name="action"
1221+ value="merge"
1222+ disabled={mergeBlocked != null}
1223+ pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
1224+ >
1225+ {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1226+ {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
1227+ </SubmitButton>
1228+ </Hint>
12271229 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
12281230 {mergeBlocked ?? (mergeQueue
12291231 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
+10−7
55 import { page } from "../../lib/meta";
66 import { openedBy } from "../../lib/opened-by";
77 import { ButtonLink, EmptyState, TimeAgo } from "../../components/ui";
8+import { Hint } from "../../components/ui/hint";
89 import { CheckBadge } from "../../components/checks";
910 import { ChangeSize, PullIcon, StateTabs } from "../../components/work";
1011 import { AgentBadge, useActiveRuns } from "../../components/agents";
112113 }))}
113114 />
114115 {loaderData.base && (
115− <Link
116− to={withFilter(list, current, "base", null)}
117− className="inline-flex h-8 items-center gap-1.5 rounded-md border border-accent/40 bg-accent/5 px-2.5 text-sm"
118− title="Show pull requests into every branch"
119− >
120− Into <span className="font-mono">{loaderData.base}</span>
121− </Link>
116+ <Hint label="Show pull requests into every branch">
117+ <Link
118+ to={withFilter(list, current, "base", null)}
119+ className="inline-flex h-8 items-center gap-1.5 rounded-md border border-accent/40 bg-accent/5 px-2.5 text-sm"
120+ >
121+ Into <span className="font-mono">{loaderData.base}</span>
122+ <span className="sr-only">: show pull requests into every branch</span>
123+ </Link>
124+ </Hint>
122125 )}
123126 {filtered && (
124127 <Link to={state === "closed" ? `${list}?state=closed` : list} className="text-xs text-muted hover:text-fg">
+10−8
55 import type { Route } from "./+types/security-settings";
66 import { page } from "../../lib/meta";
77 import { ActivationPrompt, CARD, SectionHeader } from "../../components/security-suite";
8+import { Hint } from "../../components/ui/hint";
89 import { Switch } from "../../components/ui/switch";
910 import { securitySuite } from "../../lib/services.server";
1011 import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server";
122123 </label>
123124 </fieldset>
124125 <div className="flex flex-wrap items-center gap-3">
125− <button
126− type="submit"
127− disabled={disabled || fetcher.state !== "idle"}
128− title={whyNot(can, "manage_settings")}
129− className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-white disabled:opacity-50"
130− >
131− {fetcher.state !== "idle" ? "Saving…" : "Save"}
132− </button>
126+ <Hint label={whyNot(can, "manage_settings")} disabled={!can.manage_settings}>
127+ <button
128+ type="submit"
129+ disabled={disabled || fetcher.state !== "idle"}
130+ className="rounded-md bg-fg px-3.5 py-2 text-sm font-medium text-bg hover:bg-white disabled:opacity-50"
131+ >
132+ {fetcher.state !== "idle" ? "Saving…" : "Save"}
133+ </button>
134+ </Hint>
133135 <Link to={`${base}/settings/branches`} className="text-sm text-muted underline underline-offset-2 hover:text-fg">
134136 Require the checks in branch protection
135137 </Link>
+9−7
1313 type UpgradeFix,
1414 VulnerabilityList,
1515 } from "../../components/security";
16+import { Hint } from "../../components/ui/hint";
1617 import { Switch } from "../../components/ui/switch";
1718 import { security, work } from "../../lib/services.server";
1819 import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
205206 </span>
206207 {upkeep.data?.error && <span className="mt-1 block text-xs text-danger">{upkeep.data.error}</span>}
207208 </span>
208− <Switch
209− className="mt-0.5"
210− checked={upkeepOn}
211− disabled={upkeep.state !== "idle" || !can.manage_settings}
212− title={whyNot(can, "manage_settings")}
213− onCheckedChange={(checked) => upkeep.submit({ intent: "upkeep", enabled: String(checked) }, { method: "post", action })}
214− />
209+ <Hint label={whyNot(can, "manage_settings")} disabled={!can.manage_settings}>
210+ <Switch
211+ className="mt-0.5"
212+ checked={upkeepOn}
213+ disabled={upkeep.state !== "idle" || !can.manage_settings}
214+ onCheckedChange={(checked) => upkeep.submit({ intent: "upkeep", enabled: String(checked) }, { method: "post", action })}
215+ />
216+ </Hint>
215217 </label>
216218 </div>
217219 </section>
+4−3
77 import { page } from "../../lib/meta";
88 import { RunCard, formatCost, useLiveRefresh } from "../../components/agents";
99 import { Avatar, TimeAgo } from "../../components/ui";
10+import { Hint } from "../../components/ui/hint";
1011 import { agents } from "../../lib/services.server";
1112 import { getViewer, unwrap } from "../../lib/session.server";
1213 import { accessTo } from "../../lib/access.server";
5758 <li className="flex gap-2.5 px-4 py-2 text-xs">
5859 <Wrench size={13} className="mt-0.5 shrink-0 text-faint" />
5960 <span className="shrink-0 font-medium text-muted">{entry.tool}</span>
60− <code className="min-w-0 grow truncate font-mono text-fg/85" title={entry.text}>
61− {entry.text}
62− </code>
61+ <Hint label={entry.text}>
62+ <code className="min-w-0 grow truncate font-mono text-fg/85">{entry.text}</code>
63+ </Hint>
6364 </li>
6465 );
6566 }
+8−4
77 import { page } from "../../lib/meta";
88 import { RepoSettingsHeading } from "../../components/repo-settings-heading";
99 import { Avatar, EmptyState, ErrorText, Field, Input, SubmitButton } from "../../components/ui";
10+import { Hint } from "../../components/ui/hint";
1011 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select";
1112 import { projects } from "../../lib/services.server";
1213 import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
5354 <span className="text-xs text-faint">no variable</span>
5455 )}
5556 {link.source === "file" && (
56− <span className="inline-flex items-center gap-1 text-xs text-faint" title="Declared in .g1t/project.yml">
57− <FileCode2 size={12} />
58− project.yml
59− </span>
57+ <Hint label="Declared in .g1t/project.yml">
58+ <span className="inline-flex items-center gap-1 text-xs text-faint">
59+ <FileCode2 size={12} />
60+ <span className="sr-only">Declared in .g1t/</span>
61+ project.yml
62+ </span>
63+ </Hint>
6064 )}
6165 {removable && link.source === "ui" && (
6266 <Form method="post" className="ml-auto">
+7−9
2020 AlertDialogTrigger,
2121 } from "../../components/ui/alert-dialog";
2222 import { CheckboxOption } from "../../components/ui/checkbox";
23+import { Hint } from "../../components/ui/hint";
2324 import { deployments } from "../../lib/services.server";
2425 import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
2526 import { requireCapability, requireInsider } from "../../lib/access.server";
238239 <Form method="post">
239240 <input type="hidden" name="intent" value="refresh" />
240241 <input type="hidden" name="id" value={domain.id} />
241− <SubmitButton
242− variant="quiet"
243− match={{ intent: "refresh", id: domain.id }}
244− pending="Checking…"
245− title="Ask Cloudflare to check the records again now"
246− >
247− <RotateCw size={14} />
248− Check now
249− </SubmitButton>
242+ <Hint label="Ask Cloudflare to check the records again now">
243+ <SubmitButton variant="quiet" match={{ intent: "refresh", id: domain.id }} pending="Checking…">
244+ <RotateCw size={14} />
245+ Check now
246+ </SubmitButton>
247+ </Hint>
250248 </Form>
251249 )}
252250 <RemoveDomain domain={domain} />
+6−3
33
44 import type { Route } from "./+types/tags";
55 import { Avatar, EmptyState, TimeAgo } from "../../components/ui";
6+import { Hint } from "../../components/ui/hint";
67 import { page } from "../../lib/meta";
78 import { repos } from "../../lib/services.server";
89 import { getViewer, unwrap } from "../../lib/session.server";
4445 <Avatar name={tag.commit.author.name} size={13} />
4546 <span className="shrink-0">{tag.commit.author.name}</span>
4647 <span className="text-faint">·</span>
47− <Link to={`${base}/commit/${tag.commit.hash}`} className="min-w-0 truncate hover:text-fg" title={tag.commit.message}>
48− {tag.commit.message.split("\n")[0]}
49− </Link>
48+ <Hint label={tag.commit.message}>
49+ <Link to={`${base}/commit/${tag.commit.hash}`} className="min-w-0 truncate hover:text-fg">
50+ {tag.commit.message.split("\n")[0]}
51+ </Link>
52+ </Hint>
5053 <span className="shrink-0 text-faint">
5154 · <TimeAgo at={tag.commit.authoredAt} />
5255 </span>
+6−3
33 import type { Route } from "./+types/github";
44 import { page } from "../../lib/meta";
55 import { ErrorText, SubmitButton, TimeAgo } from "../../components/ui";
6+import { Hint } from "../../components/ui/hint";
67 import { assertSameOrigin, requireUser } from "../../lib/session.server";
78 import { GithubMark } from "../../components/github";
89 import { githubSignIn } from "../../lib/github.server";
5455 )}
5556 <Form method="post">
5657 <input type="hidden" name="intent" value="unlink-github" />
57− <SubmitButton variant="quiet" pending="Unlinking…" match={{ intent: "unlink-github" }} disabled={!github.hasPassword} title={github.hasPassword ? undefined : "GitHub is how you sign in. Set a password first."}>
58− Unlink
59− </SubmitButton>
58+ <Hint label={github.hasPassword ? undefined : "GitHub is how you sign in. Set a password first."} disabled={!github.hasPassword}>
59+ <SubmitButton variant="quiet" pending="Unlinking…" match={{ intent: "unlink-github" }} disabled={!github.hasPassword}>
60+ Unlink
61+ </SubmitButton>
62+ </Hint>
6063 </Form>
6164 </div>
6265 </div>
+15−12
99 import { ConfirmDialog } from "../../components/repo-lifecycle";
1010 import { Button, CopyLine, ErrorText, SubmitButton, TimeAgo } from "../../components/ui";
1111 import { Badge } from "../../components/ui/badge";
12+import { Hint } from "../../components/ui/hint";
1213 import { page } from "../../lib/meta";
1314 import { ECOSYSTEM_LABEL, formatBytes, installCommands, shortDigest } from "../../lib/packages";
1415 import { packages } from "../../lib/services.server";
221222 <span className="text-xs text-faint">Untagged</span>
222223 )}
223224 {!npm && (
224− <code className="font-mono text-xs text-muted" title={version.digest}>
225− {shortDigest(version.digest)}
226− </code>
225+ <Hint label={version.digest}>
226+ <code className="font-mono text-xs text-muted">{shortDigest(version.digest)}</code>
227+ </Hint>
227228 )}
228229 {version.deprecated && (
229− <Badge tone="neutral" title={version.deprecated}>
230− Deprecated
231− </Badge>
230+ <Hint label={version.deprecated}>
231+ <Badge tone="neutral">Deprecated</Badge>
232+ </Hint>
232233 )}
233234 {version.symbols && (
234− <Badge tone="neutral" title="A symbol package (.snupkg) was pushed: debuggers load its PDBs from the feed's symbol server.">
235− Symbols
236− </Badge>
235+ <Hint label="A symbol package (.snupkg) was pushed: debuggers load its PDBs from the feed's symbol server.">
236+ <Badge tone="neutral">Symbols</Badge>
237+ </Hint>
237238 )}
238239 </div>
239240 {version.deprecated && <p className="text-xs text-muted">{version.deprecated}</p>}
246247 )}
247248 {version.platforms.length > 0 && <span>{version.platforms.join(", ")}</span>}
248249 {attached.length > 0 && (
249− <span title={attached.map((a) => a.artifact_type ?? a.media_type ?? "artifact").join(", ")}>
250− {attached.length} attached ({attached.map((a) => artifactWord(a)).join(", ")})
251− </span>
250+ <Hint label={attached.map((a) => a.artifact_type ?? a.media_type ?? "artifact").join(", ")}>
251+ <span>
252+ {attached.length} attached ({attached.map((a) => artifactWord(a)).join(", ")})
253+ </span>
254+ </Hint>
252255 )}
253256 <span>
254257 {version.published_by ? `${version.published_by} · ` : ""}
+8−4
77 import { PackageIcon } from "../../components/package-icon";
88 import { CopyLine, EmptyState, TimeAgo } from "../../components/ui";
99 import { Badge } from "../../components/ui/badge";
10+import { Hint } from "../../components/ui/hint";
1011 import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu";
1112 import { cn } from "../../lib/cn";
1213 import { page } from "../../lib/meta";
224225 <span className="hidden shrink-0 items-center gap-5 text-xs text-muted tabular-nums sm:flex">
225226 {pkg.latest && <span className="max-w-32 truncate font-mono text-fg-soft">{pkg.latest}</span>}
226227 <span className="w-16 text-right">{formatBytes(pkg.size)}</span>
227− <span className="inline-flex w-14 items-center justify-end gap-1" title={`${pkg.downloads.toLocaleString("en-US")} downloads`}>
228− <Download size={12} />
229− {shortCount(pkg.downloads)}
230− </span>
228+ <Hint label={`${pkg.downloads.toLocaleString("en-US")} downloads`}>
229+ <span className="inline-flex w-14 items-center justify-end gap-1">
230+ <Download size={12} />
231+ {shortCount(pkg.downloads)}
232+ <span className="sr-only"> downloads</span>
233+ </span>
234+ </Hint>
231235 </span>
232236 </Link>
233237 </li>