| author | |
| committer | |
| log | 4c9fbf1f647c83179af1c2acd78eded04ef07fe8 |
| tree | f71d7ba480e2521fd09e53e4944d2db8af0db2b0 |
| parent | 18aa7036732c0fc2e2ab79e54d5d8f59a160843d |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>9 files changed, 65 insertions(+), 62 deletions(-)
apps/web/src/components/Sidebar.tsx+1-1| ... | ... | @@ -1374,7 +1374,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1374 | 1374 | if (!showSnoozeButton) setSnoozeMenuOpen(false); |
| 1375 | 1375 | }, [showSnoozeButton]); |
| 1376 | 1376 | const handlePrClick = useCallback( |
| 1377 | (event: ReactMouseEvent<HTMLAnchorElement>) => { | |
| 1377 | (event: ReactMouseEvent<HTMLElement>) => { | |
| 1378 | 1378 | const url = pr?.url ?? currentLinkedPr?.url; |
| 1379 | 1379 | if (!url) return; |
| 1380 | 1380 | const openedInRightPanel = openPrLink( |
apps/web/src/components/ThreadStatusIndicators.tsx+27-25| ... | ... | @@ -16,7 +16,7 @@ import { |
| 16 | 16 | } from "@t3tools/shared/threadPullRequests"; |
| 17 | 17 | import { FolderGit2Icon, TerminalIcon } from "lucide-react"; |
| 18 | 18 | import { useMemo, type MouseEvent } from "react"; |
| 19 | import { buttonVariants, InlineButton } from "./ui/button"; | |
| 19 | import { Button, InlineButton } from "./ui/button"; | |
| 20 | 20 | import { cn } from "../lib/utils"; |
| 21 | 21 | import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments"; |
| 22 | 22 | import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; |
| ... | ... | @@ -207,50 +207,52 @@ export function ThreadPullRequestBadgeControl({ |
| 207 | 207 | url?: string | undefined; |
| 208 | 208 | status: PrStatusIndicator | null; |
| 209 | 209 | onOpenStack: () => void; |
| 210 | onOpenPullRequest: (event: MouseEvent<HTMLAnchorElement>) => void; | |
| 210 | onOpenPullRequest: (event: MouseEvent<HTMLElement>) => void; | |
| 211 | 211 | }) { |
| 212 | 212 | const presentation = resolveThreadPullRequestBadgePresentation({ badge, number, url, status }); |
| 213 | 213 | if (presentation === null) return null; |
| 214 | 214 | const isStack = badge?.kind === "stack"; |
| 215 | const className = cn( | |
| 216 | variant === "ghost" | |
| 217 | ? buttonVariants({ variant: "ghost", size: "xs" }) | |
| 218 | : "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap border-b border-transparent hover:border-current focus-visible:outline-2 focus-visible:outline-ring", | |
| 219 | "text-xs tabular-nums", | |
| 220 | variant === "ghost" && | |
| 221 | "font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]", | |
| 222 | presentation.toneClassName, | |
| 223 | ); | |
| 224 | 215 | const content = ( |
| 225 | 216 | <> |
| 226 | 217 | <presentation.Icon aria-hidden className="size-3 shrink-0" /> |
| 227 | 218 | {presentation.text} |
| 228 | 219 | </> |
| 229 | 220 | ); |
| 221 | const linkProps = isStack | |
| 222 | ? { | |
| 223 | onClick: (event: MouseEvent<HTMLElement>) => { | |
| 224 | event.preventDefault(); | |
| 225 | event.stopPropagation(); | |
| 226 | onOpenStack(); | |
| 227 | }, | |
| 228 | } | |
| 229 | : { onClick: onOpenPullRequest }; | |
| 230 | const element = isStack ? ( | |
| 231 | <button type="button" /> | |
| 232 | ) : ( | |
| 233 | <a href={url} target="_blank" rel="noopener noreferrer" /> | |
| 234 | ); | |
| 230 | 235 | return ( |
| 231 | 236 | <Tooltip> |
| 232 | 237 | <TooltipTrigger |
| 233 | 238 | render={ |
| 234 | isStack ? ( | |
| 235 | <InlineButton | |
| 236 | className={className} | |
| 239 | variant === "ghost" ? ( | |
| 240 | <Button | |
| 241 | render={element} | |
| 242 | variant="ghost" | |
| 243 | size="xs" | |
| 244 | className={presentation.toneClassName} | |
| 237 | 245 | aria-label={presentation.label} |
| 238 | 246 | onPointerDown={(event) => event.stopPropagation()} |
| 239 | onClick={(event) => { | |
| 240 | event.preventDefault(); | |
| 241 | event.stopPropagation(); | |
| 242 | onOpenStack(); | |
| 243 | }} | |
| 247 | {...linkProps} | |
| 244 | 248 | /> |
| 245 | 249 | ) : ( |
| 246 | <a | |
| 247 | href={url} | |
| 248 | target="_blank" | |
| 249 | rel="noopener noreferrer" | |
| 250 | className={className} | |
| 250 | <InlineButton | |
| 251 | render={element} | |
| 252 | className={presentation.toneClassName} | |
| 251 | 253 | aria-label={presentation.label} |
| 252 | 254 | onPointerDown={(event) => event.stopPropagation()} |
| 253 | onClick={onOpenPullRequest} | |
| 255 | {...linkProps} | |
| 254 | 256 | /> |
| 255 | 257 | ) |
| 256 | 258 | } |
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-4| ... | ... | @@ -169,10 +169,7 @@ export function PullRequestDetailGhost({ |
| 169 | 169 | <span className="min-w-0 truncate font-medium">{seed.repository}</span> |
| 170 | 170 | <InlineButton |
| 171 | 171 | onClick={() => void readLocalApi()?.shell.openExternal(seed.url)} |
| 172 | className={cn( | |
| 173 | "font-medium underline-offset-2 hover:underline", | |
| 174 | statePresentation?.toneClassName, | |
| 175 | )} | |
| 172 | className={statePresentation?.toneClassName} | |
| 176 | 173 | aria-label={`Open pull request #${seed.number} on host`} |
| 177 | 174 | > |
| 178 | 175 | #{seed.number} |
apps/web/src/components/settings/ExpandableText.tsx+2-1| ... | ... | @@ -37,7 +37,8 @@ export function ExpandableText({ |
| 37 | 37 | <InlineButton |
| 38 | 38 | aria-expanded={expanded} |
| 39 | 39 | aria-controls={textId} |
| 40 | className="mt-1 text-[11px] font-medium text-foreground/70 underline-offset-2 hover:text-foreground hover:underline" | |
| 40 | tone="muted" | |
| 41 | className="mt-1" | |
| 41 | 42 | onClick={() => setExpanded((value) => !value)} |
| 42 | 43 | > |
| 43 | 44 | {expanded ? "Show less" : expandLabel} |
apps/web/src/components/settings/ProcessSignalActions.tsx+2-2| ... | ... | @@ -19,7 +19,7 @@ export function ProcessSignalActions({ |
| 19 | 19 | <InlineButton |
| 20 | 20 | disabled={disabled} |
| 21 | 21 | aria-label="Send SIGINT" |
| 22 | className="text-[11px] font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" | |
| 22 | tone="muted" | |
| 23 | 23 | onClick={() => onSignal("SIGINT")} |
| 24 | 24 | > |
| 25 | 25 | INT |
| ... | ... | @@ -34,7 +34,7 @@ export function ProcessSignalActions({ |
| 34 | 34 | <InlineButton |
| 35 | 35 | disabled={disabled} |
| 36 | 36 | aria-label="Send SIGKILL" |
| 37 | className="text-[11px] font-medium text-destructive underline-offset-2 hover:underline" | |
| 37 | tone="destructive" | |
| 38 | 38 | onClick={() => onSignal("SIGKILL")} |
| 39 | 39 | > |
| 40 | 40 | KILL |
apps/web/src/components/settings/SettingInheritance.tsx+2-8| ... | ... | @@ -286,10 +286,7 @@ export function SettingInheritance({ |
| 286 | 286 | <div className="flex items-center justify-between gap-3 px-2 text-xs text-muted-foreground"> |
| 287 | 287 | <span>Overridden by</span> |
| 288 | 288 | {onClearOverrides ? ( |
| 289 | <InlineButton | |
| 290 | className="font-medium text-foreground underline-offset-2 hover:underline" | |
| 291 | onClick={() => onClearOverrides(overriding)} | |
| 292 | > | |
| 289 | <InlineButton onClick={() => onClearOverrides(overriding)}> | |
| 293 | 290 | Reset {overriding.length === 1 ? "it" : "all"} |
| 294 | 291 | </InlineButton> |
| 295 | 292 | ) : null} |
| ... | ... | @@ -300,10 +297,7 @@ export function SettingInheritance({ |
| 300 | 297 | key={project.projectId} |
| 301 | 298 | className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 px-2 py-1" |
| 302 | 299 | > |
| 303 | <InlineButton | |
| 304 | className="min-w-0 justify-start text-left text-foreground underline-offset-2 hover:underline" | |
| 305 | onClick={project.open} | |
| 306 | > | |
| 300 | <InlineButton className="min-w-0 justify-start" onClick={project.open}> | |
| 307 | 301 | <span className="truncate">{project.label}</span> |
| 308 | 302 | </InlineButton> |
| 309 | 303 | <span className="max-w-32 truncate text-muted-foreground tabular-nums"> |
apps/web/src/components/sidebar/SidebarThreadUndoNotice.tsx+1-5| ... | ... | @@ -17,11 +17,7 @@ export function SidebarThreadUndoNotice() { |
| 17 | 17 | <Alert role="status" variant="sidebar"> |
| 18 | 18 | <AlertDescription> |
| 19 | 19 | {notice.action} {notice.count} thread{notice.count === 1 ? "" : "s"},{" "} |
| 20 | <InlineButton | |
| 21 | underline | |
| 22 | onClick={undoLatestThreadAction} | |
| 23 | className="hover:text-sidebar-foreground" | |
| 24 | > | |
| 20 | <InlineButton onClick={undoLatestThreadAction}> | |
| 25 | 21 | {shortcut ? `${shortcut} to undo` : "Undo"} |
| 26 | 22 | </InlineButton> |
| 27 | 23 | </AlertDescription> |
apps/web/src/components/ui/button.tsx+28-15| ... | ... | @@ -97,22 +97,35 @@ function Button({ className, variant, size, render, ...props }: ButtonProps) { |
| 97 | 97 | |
| 98 | 98 | export { Button, buttonVariants }; |
| 99 | 99 | |
| 100 | /** An inline action that keeps the geometry of surrounding text or a graph node. */ | |
| 100 | const inlineButtonVariants = cva( | |
| 101 | "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap font-medium underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-64", | |
| 102 | { | |
| 103 | defaultVariants: { tone: "default" }, | |
| 104 | variants: { | |
| 105 | tone: { | |
| 106 | default: "text-foreground", | |
| 107 | muted: "text-muted-foreground hover:text-foreground", | |
| 108 | destructive: "text-destructive/80 hover:text-destructive", | |
| 109 | }, | |
| 110 | }, | |
| 111 | }, | |
| 112 | ); | |
| 113 | ||
| 114 | /** An inline text action that keeps the size of the surrounding text and underlines on hover. */ | |
| 101 | 115 | export function InlineButton({ |
| 102 | 116 | className, |
| 103 | underline = false, | |
| 117 | tone, | |
| 118 | render, | |
| 104 | 119 | ...props |
| 105 | }: React.ComponentProps<"button"> & { underline?: boolean }) { | |
| 106 | return ( | |
| 107 | <button | |
| 108 | type="button" | |
| 109 | data-slot="inline-button" | |
| 110 | className={cn( | |
| 111 | "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap focus-visible:outline-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-64", | |
| 112 | underline && "border-b border-transparent hover:border-current", | |
| 113 | className, | |
| 114 | )} | |
| 115 | {...props} | |
| 116 | /> | |
| 117 | ); | |
| 120 | }: useRender.ComponentProps<"button"> & VariantProps<typeof inlineButtonVariants>) { | |
| 121 | const defaultProps = { | |
| 122 | className: cn(inlineButtonVariants({ tone }), className), | |
| 123 | "data-slot": "inline-button", | |
| 124 | type: render ? undefined : ("button" as const), | |
| 125 | }; | |
| 126 | return useRender({ | |
| 127 | defaultTagName: "button", | |
| 128 | props: mergeProps<"button">(defaultProps, props), | |
| 129 | render, | |
| 130 | }); | |
| 118 | 131 | } |
scripts/lint-restyle-ceiling.ts+1-1| ... | ... | @@ -8,7 +8,7 @@ import * as NodeURL from "node:url"; |
| 8 | 8 | // components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the |
| 9 | 9 | // count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you |
| 10 | 10 | // migrate a file, and delete this script when the rule becomes an error. |
| 11 | export const RESTYLE_CEILING = 865; | |
| 11 | export const RESTYLE_CEILING = 826; | |
| 12 | 12 | |
| 13 | 13 | const RULE = "shadcn(no-restyle)"; |
| 14 | 14 |