| author | |
| committer | |
| log | f46522777372be1705e3b7a1ab08756f236db5d2 |
| tree | 073cf891878b64f05fe464e289b4f04647671fa7 |
| parent | 21079391c72d1eeaa3c40f1cc5eb3e78abf0a846 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>41 files changed, 686 insertions(+), 713 deletions(-)
apps/web/src/components/AppSidebarLayout.tsx+4-4| ... | ... | @@ -125,11 +125,12 @@ function SidebarControl() { |
| 125 | 125 | <TooltipTrigger |
| 126 | 126 | render={ |
| 127 | 127 | <SidebarTrigger |
| 128 | // Over the stage artwork the trigger is a control on imagery, like the media | |
| 129 | // viewer's arrows; that variant positions itself, so the layout is reset here. | |
| 130 | variant={isSidebarVisible && stageBackdropVariant ? "media-navigation" : "ghost"} | |
| 128 | 131 | className={cn( |
| 129 | 132 | "pointer-events-auto", |
| 130 | isSidebarVisible && | |
| 131 | stageBackdropVariant && | |
| 132 | "focus-visible:ring-white/90 [&_svg]:stroke-white/90! [&_svg]:opacity-100! [&_svg]:hover:stroke-white! [:hover,[data-pressed]]:bg-white/15", | |
| 133 | isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0", | |
| 133 | 134 | isSidebarVisible && |
| 134 | 135 | stageBackdropVariant && |
| 135 | 136 | resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant), |
| ... | ... | @@ -244,7 +245,6 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 244 | 245 | side="left" |
| 245 | 246 | collapsible="offcanvas" |
| 246 | 247 | data-app-sidebar="" |
| 247 | className="border-r border-sidebar-border" | |
| 248 | 248 | resizable={{ |
| 249 | 249 | maxWidth: sidebarMaximumWidth, |
| 250 | 250 | minWidth: THREAD_SIDEBAR_MIN_WIDTH, |
apps/web/src/components/BranchToolbarBranchSelector.tsx+4-3| ... | ... | @@ -817,11 +817,12 @@ export function BranchToolbarBranchSelector({ |
| 817 | 817 | data-composer-label |
| 818 | 818 | className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0" |
| 819 | 819 | > |
| 820 | <MiddleTruncate | |
| 821 | value={triggerLabel} | |
| 820 | <span | |
| 822 | 821 | data-composer-label-motion |
| 823 | 822 | className="flex w-full max-w-[240px] transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none" |
| 824 | /> | |
| 823 | > | |
| 824 | <MiddleTruncate value={triggerLabel} /> | |
| 825 | </span> | |
| 825 | 826 | </span> |
| 826 | 827 | <ChevronDownIcon className="size-3 shrink-0 opacity-50" /> |
| 827 | 828 | </ComboboxTrigger> |
apps/web/src/components/CustomSnoozeDialog.tsx+3-9| ... | ... | @@ -93,7 +93,7 @@ function CustomSnoozeDialog() { |
| 93 | 93 | <DialogTitle>Custom snooze</DialogTitle> |
| 94 | 94 | <DialogDescription>Choose when snoozed threads return to your inbox.</DialogDescription> |
| 95 | 95 | </DialogHeader> |
| 96 | <DialogPanel className="text-base sm:text-sm"> | |
| 96 | <DialogPanel> | |
| 97 | 97 | <div className="flex flex-col gap-4"> |
| 98 | 98 | <ToggleGroup |
| 99 | 99 | aria-label="Schedule type" |
| ... | ... | @@ -147,10 +147,7 @@ function CustomSnoozeDialog() { |
| 147 | 147 | </PopoverPopup> |
| 148 | 148 | </Popover> |
| 149 | 149 | </div> |
| 150 | <Label | |
| 151 | className="flex min-w-0 flex-col items-stretch gap-1.5" | |
| 152 | htmlFor={`${id}-time`} | |
| 153 | > | |
| 150 | <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-time`}> | |
| 154 | 151 | Time |
| 155 | 152 | <Input |
| 156 | 153 | nativeInput |
| ... | ... | @@ -185,10 +182,7 @@ function CustomSnoozeDialog() { |
| 185 | 182 | <NumberFieldIncrement aria-label="Increase duration" /> |
| 186 | 183 | </NumberFieldGroup> |
| 187 | 184 | </NumberField> |
| 188 | <Label | |
| 189 | className="flex min-w-0 flex-col items-stretch gap-1.5" | |
| 190 | htmlFor={`${id}-unit`} | |
| 191 | > | |
| 185 | <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-unit`}> | |
| 192 | 186 | Unit |
| 193 | 187 | <Select |
| 194 | 188 | value={unit} |
apps/web/src/components/GitActionsControl.tsx+59-53| ... | ... | @@ -627,7 +627,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { |
| 627 | 627 | setPublishRepositoryOverride(null); |
| 628 | 628 | }} |
| 629 | 629 | aria-labelledby="publish-provider-cards-label" |
| 630 | className="grid grid-cols-2 gap-2.5" | |
| 630 | className="grid grid-cols-2" | |
| 631 | 631 | > |
| 632 | 632 | {sortedPublishProviderOptions.map((option) => { |
| 633 | 633 | const readiness = publishProviderReadiness[option.value]; |
| ... | ... | @@ -737,7 +737,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { |
| 737 | 737 | } |
| 738 | 738 | aria-labelledby="publish-visibility-cards-label" |
| 739 | 739 | disabled={publishRepositoryAction.isPending} |
| 740 | className="grid grid-cols-2 gap-2.5" | |
| 740 | className="grid grid-cols-2" | |
| 741 | 741 | > |
| 742 | 742 | {[ |
| 743 | 743 | { |
| ... | ... | @@ -1915,57 +1915,63 @@ export default function GitActionsControl({ |
| 1915 | 1915 | <p className="font-medium">none</p> |
| 1916 | 1916 | ) : ( |
| 1917 | 1917 | <div className="space-y-2"> |
| 1918 | <ScrollArea className="h-44 rounded-lg bg-card ring-1 ring-black/5 dark:bg-white/[0.025] dark:ring-white/5"> | |
| 1919 | <div className="space-y-1 p-1"> | |
| 1920 | {allFiles.map((file) => { | |
| 1921 | const isExcluded = excludedFiles.has(file.path); | |
| 1922 | return ( | |
| 1923 | <div | |
| 1924 | key={file.path} | |
| 1925 | className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50" | |
| 1926 | > | |
| 1927 | {isEditingFiles && ( | |
| 1928 | <Checkbox | |
| 1929 | checked={!excludedFiles.has(file.path)} | |
| 1930 | onCheckedChange={() => { | |
| 1931 | setExcludedFiles((prev) => { | |
| 1932 | const next = new Set(prev); | |
| 1933 | if (next.has(file.path)) { | |
| 1934 | next.delete(file.path); | |
| 1935 | } else { | |
| 1936 | next.add(file.path); | |
| 1937 | } | |
| 1938 | return next; | |
| 1939 | }); | |
| 1940 | }} | |
| 1941 | /> | |
| 1942 | )} | |
| 1943 | <button | |
| 1944 | type="button" | |
| 1945 | className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left" | |
| 1946 | onClick={() => openChangedFileInEditor(file.path)} | |
| 1918 | <div className="h-44 rounded-lg bg-card ring-1 ring-black/5 dark:bg-white/[0.025] dark:ring-white/5"> | |
| 1919 | <ScrollArea> | |
| 1920 | <div className="space-y-1 p-1"> | |
| 1921 | {allFiles.map((file) => { | |
| 1922 | const isExcluded = excludedFiles.has(file.path); | |
| 1923 | return ( | |
| 1924 | <div | |
| 1925 | key={file.path} | |
| 1926 | className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50" | |
| 1947 | 1927 | > |
| 1948 | <StartTruncatedPath | |
| 1949 | path={file.path} | |
| 1950 | className={`flex-1${isExcluded ? " text-muted-foreground" : ""}`} | |
| 1951 | /> | |
| 1952 | <span className="shrink-0"> | |
| 1953 | {isExcluded ? ( | |
| 1954 | <span className="text-muted-foreground">Excluded</span> | |
| 1955 | ) : ( | |
| 1956 | <> | |
| 1957 | <span className="text-diff-addition">+{file.insertions}</span> | |
| 1958 | <span className="text-muted-foreground"> / </span> | |
| 1959 | <span className="text-diff-deletion">-{file.deletions}</span> | |
| 1960 | </> | |
| 1961 | )} | |
| 1962 | </span> | |
| 1963 | </button> | |
| 1964 | </div> | |
| 1965 | ); | |
| 1966 | })} | |
| 1967 | </div> | |
| 1968 | </ScrollArea> | |
| 1928 | {isEditingFiles && ( | |
| 1929 | <Checkbox | |
| 1930 | checked={!excludedFiles.has(file.path)} | |
| 1931 | onCheckedChange={() => { | |
| 1932 | setExcludedFiles((prev) => { | |
| 1933 | const next = new Set(prev); | |
| 1934 | if (next.has(file.path)) { | |
| 1935 | next.delete(file.path); | |
| 1936 | } else { | |
| 1937 | next.add(file.path); | |
| 1938 | } | |
| 1939 | return next; | |
| 1940 | }); | |
| 1941 | }} | |
| 1942 | /> | |
| 1943 | )} | |
| 1944 | <button | |
| 1945 | type="button" | |
| 1946 | className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left" | |
| 1947 | onClick={() => openChangedFileInEditor(file.path)} | |
| 1948 | > | |
| 1949 | <StartTruncatedPath | |
| 1950 | path={file.path} | |
| 1951 | className={`flex-1${isExcluded ? " text-muted-foreground" : ""}`} | |
| 1952 | /> | |
| 1953 | <span className="shrink-0"> | |
| 1954 | {isExcluded ? ( | |
| 1955 | <span className="text-muted-foreground">Excluded</span> | |
| 1956 | ) : ( | |
| 1957 | <> | |
| 1958 | <span className="text-diff-addition"> | |
| 1959 | +{file.insertions} | |
| 1960 | </span> | |
| 1961 | <span className="text-muted-foreground"> / </span> | |
| 1962 | <span className="text-diff-deletion"> | |
| 1963 | -{file.deletions} | |
| 1964 | </span> | |
| 1965 | </> | |
| 1966 | )} | |
| 1967 | </span> | |
| 1968 | </button> | |
| 1969 | </div> | |
| 1970 | ); | |
| 1971 | })} | |
| 1972 | </div> | |
| 1973 | </ScrollArea> | |
| 1974 | </div> | |
| 1969 | 1975 | <div className="flex justify-end font-mono"> |
| 1970 | 1976 | <span className="text-diff-addition"> |
| 1971 | 1977 | +{selectedFiles.reduce((sum, f) => sum + f.insertions, 0)} |
| ... | ... | @@ -2040,7 +2046,7 @@ export default function GitActionsControl({ |
| 2040 | 2046 | </DialogTitle> |
| 2041 | 2047 | <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription> |
| 2042 | 2048 | </DialogHeader> |
| 2043 | <DialogFooter className="dark:border-transparent dark:bg-transparent sm:flex-wrap sm:items-center"> | |
| 2049 | <DialogFooter variant="bare" className="sm:flex-wrap sm:items-center"> | |
| 2044 | 2050 | <Button |
| 2045 | 2051 | className="w-full sm:mr-auto sm:w-auto" |
| 2046 | 2052 | variant="outline" |
apps/web/src/components/LegacySidebar.tsx+7-11| ... | ... | @@ -1038,7 +1038,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( |
| 1038 | 1038 | return ( |
| 1039 | 1039 | <SidebarMenuSub |
| 1040 | 1040 | ref={attachThreadListAutoAnimateRef} |
| 1041 | className="mx-0.5 my-0 w-full translate-x-0 gap-0.5 overflow-hidden border-l-0 px-1 py-0 sm:mx-1 sm:px-1.5" | |
| 1041 | className="mx-0.5 my-0 w-full translate-x-0 overflow-hidden sm:mx-1" | |
| 1042 | 1042 | > |
| 1043 | 1043 | {shouldShowThreadPanel && showEmptyThreadState ? ( |
| 1044 | 1044 | <SidebarMenuSubItem className="w-full" data-thread-selection-safe> |
| ... | ... | @@ -2357,9 +2357,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2357 | 2357 | <div className="group/project-header relative"> |
| 2358 | 2358 | <SidebarMenuButton |
| 2359 | 2359 | ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined} |
| 2360 | className={`pr-8 group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${ | |
| 2361 | isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "" | |
| 2362 | }`} | |
| 2360 | className={isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : undefined} | |
| 2363 | 2361 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} |
| 2364 | 2362 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.listeners : {})} |
| 2365 | 2363 | onPointerDownCapture={handleProjectButtonPointerDownCapture} |
| ... | ... | @@ -2408,6 +2406,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2408 | 2406 | </span> |
| 2409 | 2407 | ) : null} |
| 2410 | 2408 | </span> |
| 2409 | {/* Keeps the name clear of the environment badge and new-thread button overlaid on | |
| 2410 | the row's end (two slots on touch, where both stay visible). */} | |
| 2411 | <span aria-hidden className="w-4 shrink-0 max-sm:w-10" /> | |
| 2411 | 2412 | </SidebarMenuButton> |
| 2412 | 2413 | {/* Environment badge – visible by default, crossfades with the |
| 2413 | 2414 | "new thread" button on hover using the same pointer-events + |
| ... | ... | @@ -2618,7 +2619,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2618 | 2619 | |
| 2619 | 2620 | const SidebarProjectListRow = memo(function SidebarProjectListRow(props: SidebarProjectItemProps) { |
| 2620 | 2621 | return ( |
| 2621 | <SidebarMenuItem className="rounded-md"> | |
| 2622 | <SidebarMenuItem> | |
| 2622 | 2623 | <SidebarProjectItem {...props} /> |
| 2623 | 2624 | </SidebarMenuItem> |
| 2624 | 2625 | ); |
| ... | ... | @@ -2963,12 +2964,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2963 | 2964 | <SidebarMenu> |
| 2964 | 2965 | <SidebarMenuItem> |
| 2965 | 2966 | <CommandDialogTrigger |
| 2966 | render={ | |
| 2967 | <SidebarMenuButton | |
| 2968 | className="focus-visible:ring-0" | |
| 2969 | data-testid="command-palette-trigger" | |
| 2970 | /> | |
| 2971 | } | |
| 2967 | render={<SidebarMenuButton data-testid="command-palette-trigger" />} | |
| 2972 | 2968 | > |
| 2973 | 2969 | <SearchIcon /> |
| 2974 | 2970 | <span className="flex-1 truncate">Search</span> |
apps/web/src/components/PullRequestThreadDialog.tsx+2-2| ... | ... | @@ -196,8 +196,8 @@ export function PullRequestThreadDialog({ |
| 196 | 196 | > |
| 197 | 197 | <DialogPopup className="max-w-xl"> |
| 198 | 198 | <DialogHeader> |
| 199 | <DialogTitle className="flex items-center gap-2"> | |
| 200 | <SourceControlIcon className="size-4" /> | |
| 199 | <DialogTitle className="flex items-center"> | |
| 200 | <SourceControlIcon className="me-2 size-4" /> | |
| 201 | 201 | Checkout {terminology.singular} |
| 202 | 202 | </DialogTitle> |
| 203 | 203 | <DialogDescription> |
apps/web/src/components/Sidebar.tsx+9-16| ... | ... | @@ -345,14 +345,9 @@ function SidebarThreadTooltip({ |
| 345 | 345 | const driverKind = providerEntry?.driverKind ?? null; |
| 346 | 346 | const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId); |
| 347 | 347 | return ( |
| 348 | <TooltipPopup | |
| 349 | side="right" | |
| 350 | align="start" | |
| 351 | sideOffset={4} | |
| 352 | variant="glass" | |
| 353 | className="[&_[data-slot=tooltip-viewport]]:p-0" | |
| 354 | > | |
| 355 | <div className="flex min-w-0 max-w-80 flex-col gap-2 p-[var(--floating-content-inset)]"> | |
| 348 | <TooltipPopup side="right" align="start" sideOffset={4} variant="glass"> | |
| 349 | {/* The viewport's own inset (py-1 px-2) plus this one make the floating inset. */} | |
| 350 | <div className="flex min-w-0 max-w-80 flex-col gap-2 px-1 py-2"> | |
| 356 | 351 | <div className="min-w-0 truncate text-xs leading-tight font-medium text-foreground"> |
| 357 | 352 | {thread.title} |
| 358 | 353 | </div> |
| ... | ... | @@ -373,9 +368,9 @@ function SidebarThreadTooltip({ |
| 373 | 368 | </div> |
| 374 | 369 | ) : null} |
| 375 | 370 | {thread.branch ? ( |
| 376 | <div className="flex min-w-0 items-center gap-2"> | |
| 371 | <div className="flex min-w-0 items-center gap-2 text-foreground/75"> | |
| 377 | 372 | <GitBranchIcon className="size-3 shrink-0 stroke-muted-foreground" /> |
| 378 | <MiddleTruncate value={thread.branch} className="flex text-foreground/75" /> | |
| 373 | <MiddleTruncate value={thread.branch} className="flex" /> | |
| 379 | 374 | </div> |
| 380 | 375 | ) : null} |
| 381 | 376 | {branchMismatch ? ( |
| ... | ... | @@ -1920,11 +1915,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1920 | 1915 | {thread.branch ? ( |
| 1921 | 1916 | <> |
| 1922 | 1917 | <ThreadWorktreeIndicator thread={thread} /> |
| 1923 | <MiddleTruncate | |
| 1924 | value={thread.branch} | |
| 1925 | showTitle={false} | |
| 1926 | className="flex-1 text-muted-foreground/40" | |
| 1927 | /> | |
| 1918 | <span className="flex min-w-0 flex-1 text-muted-foreground/40"> | |
| 1919 | <MiddleTruncate value={thread.branch} showTitle={false} /> | |
| 1920 | </span> | |
| 1928 | 1921 | </> |
| 1929 | 1922 | ) : ( |
| 1930 | 1923 | <span className="flex-1" /> |
| ... | ... | @@ -4544,7 +4537,7 @@ export default function Sidebar() { |
| 4544 | 4537 | </SidebarGroup> |
| 4545 | 4538 | } |
| 4546 | 4539 | > |
| 4547 | <SidebarGroup className="flex-1 ps-[calc(var(--sidebar-content-inset)+1px)]"> | |
| 4540 | <SidebarGroup className="flex-1"> | |
| 4548 | 4541 | {isSearchingThreads ? ( |
| 4549 | 4542 | threadSearchResults.length > 0 ? ( |
| 4550 | 4543 | <TooltipProvider |
apps/web/src/components/chat/ChangedFilesTree.tsx+3-4| ... | ... | @@ -247,10 +247,9 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: { |
| 247 | 247 | theme={resolvedTheme} |
| 248 | 248 | className="size-3.5 text-muted-foreground/70" |
| 249 | 249 | /> |
| 250 | <MiddleTruncate | |
| 251 | value={node.name} | |
| 252 | className="font-mono text-xs text-foreground/85 group-hover:text-foreground" | |
| 253 | /> | |
| 250 | <span className="flex min-w-0 font-mono text-xs text-foreground/85 group-hover:text-foreground"> | |
| 251 | <MiddleTruncate value={node.name} /> | |
| 252 | </span> | |
| 254 | 253 | {node.stat && ( |
| 255 | 254 | <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums"> |
| 256 | 255 | <DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} /> |
apps/web/src/components/chat/ComposerBanner.tsx+6-6| ... | ... | @@ -279,17 +279,17 @@ function Children({ className, render, ...props }: useRender.ComponentProps<"div |
| 279 | 279 | } |
| 280 | 280 | |
| 281 | 281 | /** Bounded banner content uses the app's scroll area and fades only overflowing edges. */ |
| 282 | function Scroll({ className, ...props }: ComponentProps<typeof ScrollArea>) { | |
| 282 | function Scroll({ className, children, ...props }: ComponentProps<typeof ScrollArea>) { | |
| 283 | 283 | return ( |
| 284 | 284 | <ScrollArea |
| 285 | 285 | radius="none" |
| 286 | 286 | scrollFade |
| 287 | className={cn( | |
| 288 | "h-auto max-h-[min(24rem,40dvh)] [&>[data-slot=scroll-area-viewport][data-has-overflow-y]]:pe-2", | |
| 289 | className, | |
| 290 | )} | |
| 287 | className={cn("h-auto max-h-[min(24rem,40dvh)]", className)} | |
| 291 | 288 | {...props} |
| 292 | /> | |
| 289 | > | |
| 290 | {/* Clears the overlay scrollbar only once there is something to scroll. */} | |
| 291 | <div className="[[data-has-overflow-y]>&]:pe-2">{children}</div> | |
| 292 | </ScrollArea> | |
| 293 | 293 | ); |
| 294 | 294 | } |
| 295 | 295 |
apps/web/src/components/chat/ComposerBannerStack.tsx+1-1| ... | ... | @@ -306,7 +306,7 @@ function NoticeDescription({ children, compact }: { children: ReactNode; compact |
| 306 | 306 | aria-label="Notice details" |
| 307 | 307 | tooltipStyle |
| 308 | 308 | side="top" |
| 309 | className="max-w-80 whitespace-normal text-pretty wrap-anywhere" | |
| 309 | className="max-w-80 whitespace-normal wrap-anywhere" | |
| 310 | 310 | > |
| 311 | 311 | <ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]"> |
| 312 | 312 | {children} |
apps/web/src/components/chat/MessagesTimeline.tsx+1-7| ... | ... | @@ -2562,13 +2562,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap |
| 2562 | 2562 | <Spinner size="xs" className="shrink-0" /> |
| 2563 | 2563 | <span className="truncate">{scriptName}</span> |
| 2564 | 2564 | </PopoverTrigger> |
| 2565 | <PopoverPopup | |
| 2566 | side="bottom" | |
| 2567 | align="end" | |
| 2568 | width="lg" | |
| 2569 | className="surface-glass!" | |
| 2570 | padding="compact" | |
| 2571 | > | |
| 2565 | <PopoverPopup side="bottom" align="end" width="lg" padding="compact"> | |
| 2572 | 2566 | <WorktreeSetupCard |
| 2573 | 2567 | snapshot={snapshot} |
| 2574 | 2568 | embedded |
apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx+2-2| ... | ... | @@ -97,10 +97,10 @@ export function useAutoBalanceUpdateBanner( |
| 97 | 97 | <Popover> |
| 98 | 98 | <PopoverTrigger |
| 99 | 99 | render={<InlineButton />} |
| 100 | className="block max-w-full truncate" | |
| 100 | className="max-w-full" | |
| 101 | 101 | aria-label={`${title}. View machines`} |
| 102 | 102 | > |
| 103 | {title} | |
| 103 | <span className="min-w-0 truncate">{title}</span> | |
| 104 | 104 | </PopoverTrigger> |
| 105 | 105 | <PopoverPopup side="top" align="start" width="md"> |
| 106 | 106 | <div className="space-y-3 text-xs"> |
apps/web/src/components/onboarding/WelcomeWizard.tsx+2-2| ... | ... | @@ -584,8 +584,8 @@ function PairingForm({ |
| 584 | 584 | {isPairing ? "Pairing..." : "Pair"} |
| 585 | 585 | </Button> |
| 586 | 586 | </div> |
| 587 | <CollapsiblePanel className="pt-3"> | |
| 588 | <p className="text-sm text-muted-foreground"> | |
| 587 | <CollapsiblePanel> | |
| 588 | <p className="pt-3 text-sm text-muted-foreground"> | |
| 589 | 589 | Run this on the computer with your code. |
| 590 | 590 | </p> |
| 591 | 591 | <CommandBlock command="npx t3 pair" className="mt-2" /> |
apps/web/src/components/projectScriptEditor.tsx+1-1| ... | ... | @@ -431,7 +431,7 @@ export function ProjectScriptEditorDialog({ |
| 431 | 431 | </fieldset> |
| 432 | 432 | </form> |
| 433 | 433 | </DialogPanel> |
| 434 | <DialogFooter className="dark:border-transparent dark:bg-transparent"> | |
| 434 | <DialogFooter variant="bare"> | |
| 435 | 435 | {isEditing && ( |
| 436 | 436 | <Button |
| 437 | 437 | type="button" |
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+3-7| ... | ... | @@ -2252,8 +2252,8 @@ export function PullRequestDetailPanel({ |
| 2252 | 2252 | <PullRequestActorLabel |
| 2253 | 2253 | actor={detail.author} |
| 2254 | 2254 | profileUrl={authorProfileUrl} |
| 2255 | className="shrink-0 rounded-full" | |
| 2256 | labelClassName="sr-only" | |
| 2255 | variant="avatar" | |
| 2256 | className="shrink-0" | |
| 2257 | 2257 | /> |
| 2258 | 2258 | <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span> |
| 2259 | 2259 | </span> |
| ... | ... | @@ -2428,11 +2428,7 @@ export function PullRequestDetailPanel({ |
| 2428 | 2428 | )} |
| 2429 | 2429 | <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground"> |
| 2430 | 2430 | <PullRequestMetaLine className="min-w-0 whitespace-nowrap"> |
| 2431 | <PullRequestActorLabel | |
| 2432 | actor={detail.author} | |
| 2433 | profileUrl={authorProfileUrl} | |
| 2434 | className="font-medium" | |
| 2435 | /> | |
| 2431 | <PullRequestActorLabel actor={detail.author} profileUrl={authorProfileUrl} /> | |
| 2436 | 2432 | <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span> |
| 2437 | 2433 | </PullRequestMetaLine> |
| 2438 | 2434 | {checkoutCommand ? ( |
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-5| ... | ... | @@ -216,11 +216,7 @@ export function PullRequestDetailGhost({ |
| 216 | 216 | <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground"> |
| 217 | 217 | {seed ? ( |
| 218 | 218 | <PullRequestMetaLine className="min-w-0 whitespace-nowrap"> |
| 219 | <PullRequestActorLabel | |
| 220 | actor={seed.author ?? null} | |
| 221 | className="font-medium" | |
| 222 | tooltip={false} | |
| 223 | /> | |
| 219 | <PullRequestActorLabel actor={seed.author ?? null} tooltip={false} /> | |
| 224 | 220 | <span>updated {formatRelativeTimeLabel(seed.updatedAt)}</span> |
| 225 | 221 | </PullRequestMetaLine> |
| 226 | 222 | ) : ( |
apps/web/src/components/pullRequest/PullRequestLinkPreview.tsx+33-29| ... | ... | @@ -96,37 +96,41 @@ export function PullRequestLinkPreview({ |
| 96 | 96 | <PreviewCard open={open} onOpenChange={setOpen}> |
| 97 | 97 | <PreviewCardTrigger render={trigger} delay={350} closeDelay={120} /> |
| 98 | 98 | {detail !== null || detailQuery.error !== null ? ( |
| 99 | <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)] p-3"> | |
| 100 | {detail === null ? ( | |
| 101 | (fallback ?? ( | |
| 102 | <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere"> | |
| 103 | {originalUrl} | |
| 104 | </p> | |
| 105 | )) | |
| 106 | ) : ( | |
| 107 | <div className="min-w-0"> | |
| 108 | <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground"> | |
| 109 | <span className="min-w-0 truncate">{detail.repository}</span> | |
| 110 | <span className="shrink-0">#{detail.number}</span> | |
| 111 | <span aria-hidden>·</span> | |
| 112 | {state === null ? null : ( | |
| 113 | <span className="inline-flex shrink-0 items-center gap-1"> | |
| 114 | <state.Icon aria-hidden className={`size-3 ${state.toneClassName}`} /> | |
| 115 | {state.label} | |
| 99 | <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)]"> | |
| 100 | <div className="p-3"> | |
| 101 | {detail === null ? ( | |
| 102 | (fallback ?? ( | |
| 103 | <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere"> | |
| 104 | {originalUrl} | |
| 105 | </p> | |
| 106 | )) | |
| 107 | ) : ( | |
| 108 | <div className="min-w-0"> | |
| 109 | <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground"> | |
| 110 | <span className="min-w-0 truncate">{detail.repository}</span> | |
| 111 | <span className="shrink-0">#{detail.number}</span> | |
| 112 | <span aria-hidden>·</span> | |
| 113 | {state === null ? null : ( | |
| 114 | <span className="inline-flex shrink-0 items-center gap-1"> | |
| 115 | <state.Icon aria-hidden className={`size-3 ${state.toneClassName}`} /> | |
| 116 | {state.label} | |
| 117 | </span> | |
| 118 | )} | |
| 119 | </div> | |
| 120 | <p className="mt-1 text-sm font-medium leading-snug text-foreground text-pretty"> | |
| 121 | {detail.title} | |
| 122 | </p> | |
| 123 | <div className="mt-2 flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground"> | |
| 124 | <PullRequestActorAvatar actor={detail.author} className="size-4" /> | |
| 125 | <span className="min-w-0 truncate">{authorLabel}</span> | |
| 126 | <span aria-hidden>·</span> | |
| 127 | <span className="shrink-0"> | |
| 128 | opened {formatRelativeTimeLabel(detail.createdAt)} | |
| 116 | 129 | </span> |
| 117 | )} | |
| 130 | </div> | |
| 118 | 131 | </div> |
| 119 | <p className="mt-1 text-sm font-medium leading-snug text-foreground text-pretty"> | |
| 120 | {detail.title} | |
| 121 | </p> | |
| 122 | <div className="mt-2 flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground"> | |
| 123 | <PullRequestActorAvatar actor={detail.author} className="size-4" /> | |
| 124 | <span className="min-w-0 truncate">{authorLabel}</span> | |
| 125 | <span aria-hidden>·</span> | |
| 126 | <span className="shrink-0">opened {formatRelativeTimeLabel(detail.createdAt)}</span> | |
| 127 | </div> | |
| 128 | </div> | |
| 129 | )} | |
| 132 | )} | |
| 133 | </div> | |
| 130 | 134 | </PreviewCardPopup> |
| 131 | 135 | ) : null} |
| 132 | 136 | </PreviewCard> |
apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx+1-1| ... | ... | @@ -265,7 +265,7 @@ export function ReviewThreadCard({ |
| 265 | 265 | {comments.map((comment) => ( |
| 266 | 266 | <article key={comment.id} className="group min-w-0"> |
| 267 | 267 | <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground"> |
| 268 | <PullRequestActorLabel actor={comment.author} className="text-foreground" /> | |
| 268 | <PullRequestActorLabel actor={comment.author} /> | |
| 269 | 269 | <span>{formatRelativeTimeLabel(comment.createdAt)}</span> |
| 270 | 270 | <PullRequestReactionBar |
| 271 | 271 | className="ml-auto justify-end" |
apps/web/src/components/pullRequest/PullRequestReviewerPicker.tsx+1-1| ... | ... | @@ -120,7 +120,7 @@ export function PullRequestReviewerPicker({ |
| 120 | 120 | > |
| 121 | 121 | {(candidate) => ( |
| 122 | 122 | <> |
| 123 | <PullRequestActorLabel actor={candidate} className="min-w-0 flex-1 truncate" /> | |
| 123 | <PullRequestActorLabel actor={candidate} className="flex-1" /> | |
| 124 | 124 | {candidate.kind === "team" ? ( |
| 125 | 125 | <span className="shrink-0 text-muted-foreground">team</span> |
| 126 | 126 | ) : null} |
apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx+21-28| ... | ... | @@ -78,11 +78,7 @@ function CommentIdentity({ |
| 78 | 78 | : null; |
| 79 | 79 | return ( |
| 80 | 80 | <div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs"> |
| 81 | <PullRequestActorLabel | |
| 82 | actor={actor} | |
| 83 | profileUrl={profileUrl} | |
| 84 | className="max-w-full font-medium text-foreground [&>img]:size-6 [&>span:first-child]:size-6" | |
| 85 | /> | |
| 81 | <PullRequestActorLabel actor={actor} profileUrl={profileUrl} className="max-w-full" /> | |
| 86 | 82 | <Tooltip> |
| 87 | 83 | <TooltipTrigger |
| 88 | 84 | render={ |
| ... | ... | @@ -383,22 +379,26 @@ function CommentGroup({ |
| 383 | 379 | <div className="flex items-center gap-3 pl-3"> |
| 384 | 380 | <div className="flex shrink-0 -space-x-1.5"> |
| 385 | 381 | {authors.slice(0, 3).map((actor) => ( |
| 386 | <PullRequestActorLabel | |
| 382 | // The ring separates the overlapping faces; it belongs to the stack, not the actor. | |
| 383 | <span | |
| 387 | 384 | key={actor?.login ?? "ghost"} |
| 388 | actor={actor} | |
| 389 | profileUrl={ | |
| 390 | detail.provider === "github" && actor | |
| 391 | ? new URL( | |
| 392 | actor.isBot || actor.login.endsWith("[bot]") | |
| 393 | ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}` | |
| 394 | : `/${encodeURIComponent(actor.login)}`, | |
| 395 | detail.url, | |
| 396 | ).toString() | |
| 397 | : null | |
| 398 | } | |
| 399 | labelClassName="sr-only" | |
| 400 | className="relative rounded-full bg-background ring-2 ring-background hover:z-10 focus-visible:z-10 [&>img]:size-6 [&>span:first-child]:size-6" | |
| 401 | /> | |
| 385 | className="relative flex rounded-full ring-2 ring-background hover:z-10 focus-within:z-10" | |
| 386 | > | |
| 387 | <PullRequestActorLabel | |
| 388 | actor={actor} | |
| 389 | profileUrl={ | |
| 390 | detail.provider === "github" && actor | |
| 391 | ? new URL( | |
| 392 | actor.isBot || actor.login.endsWith("[bot]") | |
| 393 | ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}` | |
| 394 | : `/${encodeURIComponent(actor.login)}`, | |
| 395 | detail.url, | |
| 396 | ).toString() | |
| 397 | : null | |
| 398 | } | |
| 399 | variant="avatar" | |
| 400 | /> | |
| 401 | </span> | |
| 402 | 402 | ))} |
| 403 | 403 | {authors.length > 3 ? ( |
| 404 | 404 | <span className="relative flex size-6 items-center justify-center rounded-full bg-muted text-[10px] text-muted-foreground ring-2 ring-background"> |
| ... | ... | @@ -752,14 +752,7 @@ export function PullRequestSummaryTab({ |
| 752 | 752 | <PullRequestActorLabel |
| 753 | 753 | actor={entry.actor} |
| 754 | 754 | tooltip={false} |
| 755 | className={cn( | |
| 756 | "gap-0 [&>span:last-child]:sr-only", | |
| 757 | // Only where the wrapper is not already drawing one, or the opaque | |
| 758 | // separator would cover the verdict in the band they share. | |
| 759 | entry.outcome | |
| 760 | ? undefined | |
| 761 | : "[&>img]:ring-2 [&>img]:ring-background [&>span:first-child]:ring-2 [&>span:first-child]:ring-background", | |
| 762 | )} | |
| 755 | variant="avatar" | |
| 763 | 756 | /> |
| 764 | 757 | {/* Colour alone says nothing to a reader who cannot see it, and the |
| 765 | 758 | login beside this is already in the accessible name. */} |
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+27-21| ... | ... | @@ -23,6 +23,7 @@ import { Children, type CSSProperties, isValidElement, type ReactNode, useState |
| 23 | 23 | import { cn } from "~/lib/utils"; |
| 24 | 24 | |
| 25 | 25 | import { Badge } from "../ui/badge"; |
| 26 | import { InlineButton } from "../ui/button"; | |
| 26 | 27 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 27 | 28 | import type { PullRequestReviewOutcome } from "./pullRequestDetail.logic"; |
| 28 | 29 | import { pullRequestLabelColor } from "./pullRequestList.logic"; |
| ... | ... | @@ -445,51 +446,56 @@ export function PullRequestActorAvatar({ |
| 445 | 446 | ); |
| 446 | 447 | } |
| 447 | 448 | |
| 448 | /** GitHub attributes work from a deleted account to "ghost"; say the same word everywhere. */ | |
| 449 | /** | |
| 450 | * GitHub attributes work from a deleted account to "ghost"; say the same word everywhere. | |
| 451 | * | |
| 452 | * An actor as a login beside its avatar, or as the avatar alone. With a profile URL the actor | |
| 453 | * is an inline link; `className` places it and never restyles it. | |
| 454 | */ | |
| 449 | 455 | export function PullRequestActorLabel({ |
| 450 | 456 | actor, |
| 451 | 457 | className, |
| 452 | labelClassName, | |
| 458 | variant = "label", | |
| 453 | 459 | tooltip = true, |
| 454 | 460 | profileUrl, |
| 455 | 461 | }: { |
| 456 | 462 | actor: PullRequestActor | null; |
| 457 | 463 | className?: string; |
| 458 | labelClassName?: string; | |
| 464 | variant?: "label" | "avatar"; | |
| 459 | 465 | tooltip?: boolean; |
| 460 | 466 | profileUrl?: string | null; |
| 461 | 467 | }) { |
| 462 | 468 | const login = actor?.login ?? "ghost"; |
| 463 | 469 | const label = ( |
| 464 | <> | |
| 470 | <span className={cn("flex min-w-0 items-center", variant === "label" && "gap-1.5")}> | |
| 465 | 471 | <PullRequestActorAvatar actor={actor} /> |
| 466 | <span className={cn("truncate", labelClassName)}>{login}</span> | |
| 467 | </> | |
| 472 | <span className={variant === "label" ? "truncate font-medium text-foreground" : "sr-only"}> | |
| 473 | {login} | |
| 474 | </span> | |
| 475 | </span> | |
| 468 | 476 | ); |
| 469 | if (!tooltip) { | |
| 470 | return <span className={cn("flex min-w-0 items-center gap-1.5", className)}>{label}</span>; | |
| 471 | } | |
| 477 | const placement = cn("flex min-w-0 shrink", className); | |
| 478 | if (!tooltip) return <span className={placement}>{label}</span>; | |
| 472 | 479 | return ( |
| 473 | 480 | <Tooltip> |
| 474 | 481 | <TooltipTrigger |
| 475 | 482 | render={ |
| 476 | 483 | profileUrl ? ( |
| 477 | <a | |
| 478 | href={profileUrl} | |
| 479 | target="_blank" | |
| 480 | rel="noopener noreferrer" | |
| 481 | aria-label={`Open ${login}'s profile`} | |
| 484 | <InlineButton | |
| 485 | className={placement} | |
| 486 | render={ | |
| 487 | <a | |
| 488 | href={profileUrl} | |
| 489 | target="_blank" | |
| 490 | rel="noopener noreferrer" | |
| 491 | aria-label={`Open ${login}'s profile`} | |
| 492 | /> | |
| 493 | } | |
| 482 | 494 | /> |
| 483 | 495 | ) : ( |
| 484 | <span /> | |
| 496 | <span className={placement} /> | |
| 485 | 497 | ) |
| 486 | 498 | } |
| 487 | className={cn( | |
| 488 | "flex min-w-0 items-center gap-1.5", | |
| 489 | profileUrl && | |
| 490 | "cursor-pointer rounded-sm underline-offset-2 outline-none hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", | |
| 491 | className, | |
| 492 | )} | |
| 493 | 499 | > |
| 494 | 500 | {label} |
| 495 | 501 | </TooltipTrigger> |
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-4| ... | ... | @@ -67,14 +67,13 @@ function SearchOptionButton(props: { |
| 67 | 67 | <Toggle |
| 68 | 68 | aria-label={props.label} |
| 69 | 69 | pressed={props.active} |
| 70 | className="size-8 rounded-[5px] font-mono text-muted-foreground data-pressed:text-foreground sm:size-7" | |
| 71 | size="compact" | |
| 72 | variant="ghost" | |
| 70 | size="segmented" | |
| 71 | variant="segmented" | |
| 73 | 72 | onClick={props.onClick} |
| 74 | 73 | /> |
| 75 | 74 | } |
| 76 | 75 | > |
| 77 | {props.children} | |
| 76 | <span className="font-mono">{props.children}</span> | |
| 78 | 77 | </TooltipTrigger> |
| 79 | 78 | <TooltipPopup side="top">{props.label}</TooltipPopup> |
| 80 | 79 | </Tooltip> |
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+1-1| ... | ... | @@ -251,7 +251,7 @@ export function AddProviderInstanceDialog({ |
| 251 | 251 | value={driver} |
| 252 | 252 | onValueChange={(value) => setDriver(ProviderDriverKind.make(value))} |
| 253 | 253 | aria-labelledby="add-instance-driver-label" |
| 254 | className="grid grid-cols-1 gap-2 sm:grid-cols-2" | |
| 254 | className="grid grid-cols-1 sm:grid-cols-2" | |
| 255 | 255 | > |
| 256 | 256 | {DRIVER_OPTIONS.map((option) => { |
| 257 | 257 | const IconComponent = option.icon; |
apps/web/src/components/settings/DiagnosticsSettings.tsx+158-154| ... | ... | @@ -344,84 +344,86 @@ function ProcessDiagnosticsTable({ |
| 344 | 344 | }, []); |
| 345 | 345 | |
| 346 | 346 | return ( |
| 347 | <ScrollArea | |
| 348 | radius="none" | |
| 349 | chainVerticalScroll | |
| 350 | scrollFade | |
| 351 | hideScrollbars | |
| 352 | className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60" | |
| 353 | > | |
| 354 | <table className="w-full min-w-[1040px] table-fixed text-left text-xs"> | |
| 355 | <colgroup> | |
| 356 | <col className="w-[24%]" /> | |
| 357 | <col className="w-[8%]" /> | |
| 358 | <col className="w-[10%]" /> | |
| 359 | <col className="w-[33%]" /> | |
| 360 | <col className="w-[8%]" /> | |
| 361 | <col className="w-[11%]" /> | |
| 362 | <col className="w-[6%]" /> | |
| 363 | </colgroup> | |
| 364 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> | |
| 365 | <tr> | |
| 366 | <th className="px-4 py-2 font-semibold sm:pl-5">Name</th> | |
| 367 | <th className="px-3 py-2 text-right font-semibold">CPU</th> | |
| 368 | <th className="px-3 py-2 text-right font-semibold">Memory</th> | |
| 369 | <th className="px-3 py-2 font-semibold">Command</th> | |
| 370 | <th className="px-3 py-2 text-right font-semibold">PID</th> | |
| 371 | <th className="px-3 py-2 font-semibold">Type</th> | |
| 372 | <th className="p-2 text-right font-semibold sm:pr-4">Kill</th> | |
| 373 | </tr> | |
| 374 | </thead> | |
| 375 | <tbody className="divide-y divide-border/50"> | |
| 376 | {visibleProcesses.length === 0 ? ( | |
| 347 | <div className="border-t border-border/60"> | |
| 348 | <ScrollArea | |
| 349 | radius="none" | |
| 350 | chainVerticalScroll | |
| 351 | scrollFade | |
| 352 | hideScrollbars | |
| 353 | className="max-h-[min(64vh,44rem)] w-full max-w-full" | |
| 354 | > | |
| 355 | <table className="w-full min-w-[1040px] table-fixed text-left text-xs"> | |
| 356 | <colgroup> | |
| 357 | <col className="w-[24%]" /> | |
| 358 | <col className="w-[8%]" /> | |
| 359 | <col className="w-[10%]" /> | |
| 360 | <col className="w-[33%]" /> | |
| 361 | <col className="w-[8%]" /> | |
| 362 | <col className="w-[11%]" /> | |
| 363 | <col className="w-[6%]" /> | |
| 364 | </colgroup> | |
| 365 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> | |
| 377 | 366 | <tr> |
| 378 | <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> | |
| 379 | {emptyLabel ?? "No live descendant processes found."} | |
| 380 | </td> | |
| 367 | <th className="px-4 py-2 font-semibold sm:pl-5">Name</th> | |
| 368 | <th className="px-3 py-2 text-right font-semibold">CPU</th> | |
| 369 | <th className="px-3 py-2 text-right font-semibold">Memory</th> | |
| 370 | <th className="px-3 py-2 font-semibold">Command</th> | |
| 371 | <th className="px-3 py-2 text-right font-semibold">PID</th> | |
| 372 | <th className="px-3 py-2 font-semibold">Type</th> | |
| 373 | <th className="p-2 text-right font-semibold sm:pr-4">Kill</th> | |
| 381 | 374 | </tr> |
| 382 | ) : null} | |
| 383 | {visibleProcesses.map((process) => ( | |
| 384 | <tr key={process.pid} className="hover:bg-muted/20"> | |
| 385 | <td className="px-4 py-2 align-middle sm:pl-5"> | |
| 386 | <ProcessNameCell | |
| 387 | process={process} | |
| 388 | isExpanded={!collapsedPids.has(process.pid)} | |
| 389 | onToggle={toggleProcess} | |
| 390 | /> | |
| 391 | </td> | |
| 392 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 393 | {process.cpuPercent.toFixed(1)}% | |
| 394 | </td> | |
| 395 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 396 | {formatBytes(process.rssBytes)} | |
| 397 | </td> | |
| 398 | <td className="px-3 py-2 align-middle text-muted-foreground"> | |
| 399 | <Tooltip> | |
| 400 | <TooltipTrigger | |
| 401 | render={<span className="block truncate">{process.command}</span>} | |
| 375 | </thead> | |
| 376 | <tbody className="divide-y divide-border/50"> | |
| 377 | {visibleProcesses.length === 0 ? ( | |
| 378 | <tr> | |
| 379 | <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> | |
| 380 | {emptyLabel ?? "No live descendant processes found."} | |
| 381 | </td> | |
| 382 | </tr> | |
| 383 | ) : null} | |
| 384 | {visibleProcesses.map((process) => ( | |
| 385 | <tr key={process.pid} className="hover:bg-muted/20"> | |
| 386 | <td className="px-4 py-2 align-middle sm:pl-5"> | |
| 387 | <ProcessNameCell | |
| 388 | process={process} | |
| 389 | isExpanded={!collapsedPids.has(process.pid)} | |
| 390 | onToggle={toggleProcess} | |
| 402 | 391 | /> |
| 403 | <TooltipPopup side="top" variant="code"> | |
| 404 | {process.command} | |
| 405 | </TooltipPopup> | |
| 406 | </Tooltip> | |
| 407 | </td> | |
| 408 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground"> | |
| 409 | {process.pid} | |
| 410 | </td> | |
| 411 | <td className="truncate px-3 py-2 align-middle text-muted-foreground"> | |
| 412 | {formatProcessType(process)} | |
| 413 | </td> | |
| 414 | <td className="p-2 align-middle sm:pr-4"> | |
| 415 | <ProcessSignalActions | |
| 416 | disabled={signalingPid === process.pid} | |
| 417 | onSignal={(signal) => onSignal(process.pid, signal)} | |
| 418 | /> | |
| 419 | </td> | |
| 420 | </tr> | |
| 421 | ))} | |
| 422 | </tbody> | |
| 423 | </table> | |
| 424 | </ScrollArea> | |
| 392 | </td> | |
| 393 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 394 | {process.cpuPercent.toFixed(1)}% | |
| 395 | </td> | |
| 396 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 397 | {formatBytes(process.rssBytes)} | |
| 398 | </td> | |
| 399 | <td className="px-3 py-2 align-middle text-muted-foreground"> | |
| 400 | <Tooltip> | |
| 401 | <TooltipTrigger | |
| 402 | render={<span className="block truncate">{process.command}</span>} | |
| 403 | /> | |
| 404 | <TooltipPopup side="top" variant="code"> | |
| 405 | {process.command} | |
| 406 | </TooltipPopup> | |
| 407 | </Tooltip> | |
| 408 | </td> | |
| 409 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground"> | |
| 410 | {process.pid} | |
| 411 | </td> | |
| 412 | <td className="truncate px-3 py-2 align-middle text-muted-foreground"> | |
| 413 | {formatProcessType(process)} | |
| 414 | </td> | |
| 415 | <td className="p-2 align-middle sm:pr-4"> | |
| 416 | <ProcessSignalActions | |
| 417 | disabled={signalingPid === process.pid} | |
| 418 | onSignal={(signal) => onSignal(process.pid, signal)} | |
| 419 | /> | |
| 420 | </td> | |
| 421 | </tr> | |
| 422 | ))} | |
| 423 | </tbody> | |
| 424 | </table> | |
| 425 | </ScrollArea> | |
| 426 | </div> | |
| 425 | 427 | ); |
| 426 | 428 | } |
| 427 | 429 | |
| ... | ... | @@ -568,88 +570,90 @@ function ProcessResourceHistoryTable({ |
| 568 | 570 | }, null); |
| 569 | 571 | |
| 570 | 572 | return ( |
| 571 | <ScrollArea | |
| 572 | chainVerticalScroll | |
| 573 | scrollFade | |
| 574 | hideScrollbars | |
| 575 | className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60" | |
| 576 | > | |
| 577 | <table className="w-full min-w-[980px] table-fixed text-left text-xs"> | |
| 578 | <colgroup> | |
| 579 | <col className="w-[24%]" /> | |
| 580 | <col className="w-[10%]" /> | |
| 581 | <col className="w-[10%]" /> | |
| 582 | <col className="w-[10%]" /> | |
| 583 | <col className="w-[10%]" /> | |
| 584 | <col className="w-[10%]" /> | |
| 585 | <col className="w-[16%]" /> | |
| 586 | <col className="w-[10%]" /> | |
| 587 | </colgroup> | |
| 588 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> | |
| 589 | <tr> | |
| 590 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 591 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 592 | <th className="px-3 py-2 text-right font-semibold">Current</th> | |
| 593 | <th className="px-3 py-2 text-right font-semibold">Average</th> | |
| 594 | <th className="px-3 py-2 text-right font-semibold">Peak</th> | |
| 595 | <th className="px-3 py-2 text-right font-semibold">Max Mem</th> | |
| 596 | <th className="px-3 py-2 font-semibold">Command</th> | |
| 597 | <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th> | |
| 598 | </tr> | |
| 599 | </thead> | |
| 600 | <tbody className="divide-y divide-border/50"> | |
| 601 | {processes.length === 0 ? ( | |
| 573 | <div className="border-t border-border/60"> | |
| 574 | <ScrollArea | |
| 575 | chainVerticalScroll | |
| 576 | scrollFade | |
| 577 | hideScrollbars | |
| 578 | className="max-h-[min(64vh,44rem)] w-full max-w-full" | |
| 579 | > | |
| 580 | <table className="w-full min-w-[980px] table-fixed text-left text-xs"> | |
| 581 | <colgroup> | |
| 582 | <col className="w-[24%]" /> | |
| 583 | <col className="w-[10%]" /> | |
| 584 | <col className="w-[10%]" /> | |
| 585 | <col className="w-[10%]" /> | |
| 586 | <col className="w-[10%]" /> | |
| 587 | <col className="w-[10%]" /> | |
| 588 | <col className="w-[16%]" /> | |
| 589 | <col className="w-[10%]" /> | |
| 590 | </colgroup> | |
| 591 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> | |
| 602 | 592 | <tr> |
| 603 | <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> | |
| 604 | {emptyLabel} | |
| 605 | </td> | |
| 593 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 594 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 595 | <th className="px-3 py-2 text-right font-semibold">Current</th> | |
| 596 | <th className="px-3 py-2 text-right font-semibold">Average</th> | |
| 597 | <th className="px-3 py-2 text-right font-semibold">Peak</th> | |
| 598 | <th className="px-3 py-2 text-right font-semibold">Max Mem</th> | |
| 599 | <th className="px-3 py-2 font-semibold">Command</th> | |
| 600 | <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th> | |
| 606 | 601 | </tr> |
| 607 | ) : null} | |
| 608 | {processes.map((process) => ( | |
| 609 | <tr key={process.processKey} className="hover:bg-muted/20"> | |
| 610 | <td className="px-4 py-2 align-middle sm:pl-5"> | |
| 611 | <ResourceHistoryProcessNameCell | |
| 612 | process={process} | |
| 613 | visualDepth={ | |
| 614 | process.isServerRoot || shallowestChildDepth === null | |
| 615 | ? 0 | |
| 616 | : Math.max(1, process.depth - shallowestChildDepth + 1) | |
| 617 | } | |
| 618 | /> | |
| 619 | </td> | |
| 620 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 621 | {formatCpuTime(process.cpuSecondsApprox)} | |
| 622 | </td> | |
| 623 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 624 | {process.currentCpuPercent.toFixed(1)}% | |
| 625 | </td> | |
| 626 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 627 | {process.avgCpuPercent.toFixed(1)}% | |
| 628 | </td> | |
| 629 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 630 | {process.maxCpuPercent.toFixed(1)}% | |
| 631 | </td> | |
| 632 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 633 | {formatBytes(process.maxRssBytes)} | |
| 634 | </td> | |
| 635 | <td className="px-3 py-2 align-middle text-muted-foreground"> | |
| 636 | <Tooltip> | |
| 637 | <TooltipTrigger | |
| 638 | render={<span className="block truncate">{process.command}</span>} | |
| 602 | </thead> | |
| 603 | <tbody className="divide-y divide-border/50"> | |
| 604 | {processes.length === 0 ? ( | |
| 605 | <tr> | |
| 606 | <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> | |
| 607 | {emptyLabel} | |
| 608 | </td> | |
| 609 | </tr> | |
| 610 | ) : null} | |
| 611 | {processes.map((process) => ( | |
| 612 | <tr key={process.processKey} className="hover:bg-muted/20"> | |
| 613 | <td className="px-4 py-2 align-middle sm:pl-5"> | |
| 614 | <ResourceHistoryProcessNameCell | |
| 615 | process={process} | |
| 616 | visualDepth={ | |
| 617 | process.isServerRoot || shallowestChildDepth === null | |
| 618 | ? 0 | |
| 619 | : Math.max(1, process.depth - shallowestChildDepth + 1) | |
| 620 | } | |
| 639 | 621 | /> |
| 640 | <TooltipPopup side="top" variant="code"> | |
| 641 | {process.command} | |
| 642 | </TooltipPopup> | |
| 643 | </Tooltip> | |
| 644 | </td> | |
| 645 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground sm:pr-5"> | |
| 646 | {process.pid} | |
| 647 | </td> | |
| 648 | </tr> | |
| 649 | ))} | |
| 650 | </tbody> | |
| 651 | </table> | |
| 652 | </ScrollArea> | |
| 622 | </td> | |
| 623 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 624 | {formatCpuTime(process.cpuSecondsApprox)} | |
| 625 | </td> | |
| 626 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 627 | {process.currentCpuPercent.toFixed(1)}% | |
| 628 | </td> | |
| 629 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 630 | {process.avgCpuPercent.toFixed(1)}% | |
| 631 | </td> | |
| 632 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 633 | {process.maxCpuPercent.toFixed(1)}% | |
| 634 | </td> | |
| 635 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> | |
| 636 | {formatBytes(process.maxRssBytes)} | |
| 637 | </td> | |
| 638 | <td className="px-3 py-2 align-middle text-muted-foreground"> | |
| 639 | <Tooltip> | |
| 640 | <TooltipTrigger | |
| 641 | render={<span className="block truncate">{process.command}</span>} | |
| 642 | /> | |
| 643 | <TooltipPopup side="top" variant="code"> | |
| 644 | {process.command} | |
| 645 | </TooltipPopup> | |
| 646 | </Tooltip> | |
| 647 | </td> | |
| 648 | <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground sm:pr-5"> | |
| 649 | {process.pid} | |
| 650 | </td> | |
| 651 | </tr> | |
| 652 | ))} | |
| 653 | </tbody> | |
| 654 | </table> | |
| 655 | </ScrollArea> | |
| 656 | </div> | |
| 653 | 657 | ); |
| 654 | 658 | } |
| 655 | 659 |
apps/web/src/components/settings/IntegrationsSettings.tsx+4-7| ... | ... | @@ -1278,14 +1278,11 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) { |
| 1278 | 1278 | onCommit={(next) => renameProfile(profile.id, next)} |
| 1279 | 1279 | /> |
| 1280 | 1280 | )} |
| 1281 | {/* | |
| 1282 | Dimmed with the rest of the row: a `Badge` has no disabled | |
| 1283 | treatment of its own, so a solid `bg-primary` pill would | |
| 1284 | otherwise sit at full strength beside a name, rename field | |
| 1285 | and menu button that are all at 0.64. | |
| 1286 | */} | |
| 1281 | {/* Dimmed with the rest of the row, whose controls are all disabled. */} | |
| 1287 | 1282 | {isDefault ? ( |
| 1288 | <Badge className={cn(profileWritesDisabled && "opacity-64")}>Default</Badge> | |
| 1283 | <span className={cn("flex", profileWritesDisabled && "opacity-64")}> | |
| 1284 | <Badge>Default</Badge> | |
| 1285 | </span> | |
| 1289 | 1286 | ) : null} |
| 1290 | 1287 | </span> |
| 1291 | 1288 | <Menu> |
apps/web/src/components/settings/ProviderAccentColorPicker.tsx+1-6| ... | ... | @@ -90,12 +90,7 @@ function ProviderCustomColorPicker(props: { |
| 90 | 90 | </button> |
| 91 | 91 | } |
| 92 | 92 | /> |
| 93 | <PopoverPopup | |
| 94 | side="bottom" | |
| 95 | align="start" | |
| 96 | sideOffset={6} | |
| 97 | className="overflow-hidden rounded-md p-0 [--viewport-inline-padding:0px] [&_[data-slot=popover-viewport]]:p-0" | |
| 98 | > | |
| 93 | <PopoverPopup side="bottom" align="start" sideOffset={6} padding="none"> | |
| 99 | 94 | <ProviderCustomColorPanel value={normalized} onCommit={props.onCommit} /> |
| 100 | 95 | <PopoverClose |
| 101 | 96 | render={ |
apps/web/src/components/settings/ProviderSettingsPanel.tsx+1-1| ... | ... | @@ -353,7 +353,7 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) { |
| 353 | 353 | <Tooltip key={environment.environmentId}> |
| 354 | 354 | <TooltipTrigger |
| 355 | 355 | render={ |
| 356 | <Toggle value={environment.environmentId} className="gap-2 text-left"> | |
| 356 | <Toggle value={environment.environmentId}> | |
| 357 | 357 | <EnvironmentMachineIcon |
| 358 | 358 | kind={machine} |
| 359 | 359 | className="size-3.5 shrink-0" |
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+185-179| ... | ... | @@ -455,11 +455,13 @@ function ResourceHistoryChart({ |
| 455 | 455 | </div> |
| 456 | 456 | } |
| 457 | 457 | /> |
| 458 | <TooltipPopup side="top" className="space-y-0.5 text-left"> | |
| 459 | <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div> | |
| 460 | <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div> | |
| 461 | <div>Read {formatBytes(bucket.ioReadBytes)}</div> | |
| 462 | <div>Write {formatBytes(bucket.ioWriteBytes)}</div> | |
| 458 | <TooltipPopup side="top" className="text-left"> | |
| 459 | <div className="space-y-0.5"> | |
| 460 | <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div> | |
| 461 | <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div> | |
| 462 | <div>Read {formatBytes(bucket.ioReadBytes)}</div> | |
| 463 | <div>Write {formatBytes(bucket.ioWriteBytes)}</div> | |
| 464 | </div> | |
| 463 | 465 | </TooltipPopup> |
| 464 | 466 | </Tooltip> |
| 465 | 467 | ); |
| ... | ... | @@ -565,100 +567,102 @@ function ProcessTable({ |
| 565 | 567 | }, []); |
| 566 | 568 | |
| 567 | 569 | return ( |
| 568 | <ScrollArea | |
| 569 | chainVerticalScroll | |
| 570 | scrollFade | |
| 571 | hideScrollbars | |
| 572 | className="max-h-[min(68vh,48rem)] w-full max-w-full border-t border-border/60" | |
| 573 | > | |
| 574 | <table className="w-full min-w-[1320px] table-fixed text-left text-xs"> | |
| 575 | <colgroup> | |
| 576 | <col className="w-[20%]" /> | |
| 577 | <col className="w-[10%]" /> | |
| 578 | <col className="w-[7%]" /> | |
| 579 | <col className="w-[8%]" /> | |
| 580 | <col className="w-[9%]" /> | |
| 581 | <col className="w-[9%]" /> | |
| 582 | <col className="w-[9%]" /> | |
| 583 | <col className="w-[10%]" /> | |
| 584 | <col className="w-[8%]" /> | |
| 585 | <col className="w-[6%]" /> | |
| 586 | <col className="w-[4%]" /> | |
| 587 | </colgroup> | |
| 588 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65"> | |
| 589 | <tr> | |
| 590 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 591 | <th className="px-3 py-2 font-semibold">Category</th> | |
| 592 | <th className="px-3 py-2 text-right font-semibold">CPU</th> | |
| 593 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 594 | <th className="px-3 py-2 text-right font-semibold">Memory</th> | |
| 595 | <th className="px-3 py-2 text-right font-semibold">Read/s</th> | |
| 596 | <th className="px-3 py-2 text-right font-semibold">Write/s</th> | |
| 597 | <th className="px-3 py-2 text-right font-semibold">Read Total</th> | |
| 598 | <th className="px-3 py-2 text-right font-semibold">Write Total</th> | |
| 599 | <th className="px-3 py-2 text-right font-semibold">PID</th> | |
| 600 | <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th> | |
| 601 | </tr> | |
| 602 | </thead> | |
| 603 | <tbody className="divide-y divide-border/50"> | |
| 604 | {visible.length === 0 ? ( | |
| 570 | <div className="border-t border-border/60"> | |
| 571 | <ScrollArea | |
| 572 | chainVerticalScroll | |
| 573 | scrollFade | |
| 574 | hideScrollbars | |
| 575 | className="max-h-[min(68vh,48rem)] w-full max-w-full" | |
| 576 | > | |
| 577 | <table className="w-full min-w-[1320px] table-fixed text-left text-xs"> | |
| 578 | <colgroup> | |
| 579 | <col className="w-[20%]" /> | |
| 580 | <col className="w-[10%]" /> | |
| 581 | <col className="w-[7%]" /> | |
| 582 | <col className="w-[8%]" /> | |
| 583 | <col className="w-[9%]" /> | |
| 584 | <col className="w-[9%]" /> | |
| 585 | <col className="w-[9%]" /> | |
| 586 | <col className="w-[10%]" /> | |
| 587 | <col className="w-[8%]" /> | |
| 588 | <col className="w-[6%]" /> | |
| 589 | <col className="w-[4%]" /> | |
| 590 | </colgroup> | |
| 591 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65"> | |
| 605 | 592 | <tr> |
| 606 | <td colSpan={11} className="px-4 py-5 text-xs text-muted-foreground sm:px-5"> | |
| 607 | Waiting for the native process monitor. | |
| 608 | </td> | |
| 609 | </tr> | |
| 610 | ) : null} | |
| 611 | {visible.map((process) => ( | |
| 612 | <tr key={processIdentityKey(process)} className="hover:bg-muted/20"> | |
| 613 | <td className="px-4 py-2 sm:pl-5"> | |
| 614 | <ProcessTreeName | |
| 615 | process={process} | |
| 616 | collapsed={collapsed.has(processIdentityKey(process))} | |
| 617 | onToggle={toggle} | |
| 618 | /> | |
| 619 | </td> | |
| 620 | <td className="truncate px-3 py-2 text-[11px] text-muted-foreground"> | |
| 621 | {categoryLabel(process.category)} | |
| 622 | </td> | |
| 623 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 624 | {process.cpuPercent.toFixed(1)}% | |
| 625 | </td> | |
| 626 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 627 | {formatCpuTime(process.cpuTimeMs)} | |
| 628 | </td> | |
| 629 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 630 | {formatBytes(process.residentBytes)} | |
| 631 | </td> | |
| 632 | <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300"> | |
| 633 | {formatRate(process.ioReadBytesPerSecond)} | |
| 634 | </td> | |
| 635 | <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300"> | |
| 636 | {formatRate(process.ioWriteBytesPerSecond)} | |
| 637 | </td> | |
| 638 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 639 | {formatBytes(process.ioReadBytes)} | |
| 640 | </td> | |
| 641 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 642 | <Tooltip> | |
| 643 | <TooltipTrigger render={<span>{formatBytes(process.ioWriteBytes)}</span>} /> | |
| 644 | <TooltipPopup side="top">{ioSemanticsLabel(process.ioSemantics)}</TooltipPopup> | |
| 645 | </Tooltip> | |
| 646 | </td> | |
| 647 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 648 | {process.identity.pid} | |
| 649 | </td> | |
| 650 | <td className="px-2 py-2 text-right sm:pr-4"> | |
| 651 | <ProcessActions | |
| 652 | process={process} | |
| 653 | signalingKeys={signalingKeys} | |
| 654 | onSignal={onSignal} | |
| 655 | /> | |
| 656 | </td> | |
| 593 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 594 | <th className="px-3 py-2 font-semibold">Category</th> | |
| 595 | <th className="px-3 py-2 text-right font-semibold">CPU</th> | |
| 596 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 597 | <th className="px-3 py-2 text-right font-semibold">Memory</th> | |
| 598 | <th className="px-3 py-2 text-right font-semibold">Read/s</th> | |
| 599 | <th className="px-3 py-2 text-right font-semibold">Write/s</th> | |
| 600 | <th className="px-3 py-2 text-right font-semibold">Read Total</th> | |
| 601 | <th className="px-3 py-2 text-right font-semibold">Write Total</th> | |
| 602 | <th className="px-3 py-2 text-right font-semibold">PID</th> | |
| 603 | <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th> | |
| 657 | 604 | </tr> |
| 658 | ))} | |
| 659 | </tbody> | |
| 660 | </table> | |
| 661 | </ScrollArea> | |
| 605 | </thead> | |
| 606 | <tbody className="divide-y divide-border/50"> | |
| 607 | {visible.length === 0 ? ( | |
| 608 | <tr> | |
| 609 | <td colSpan={11} className="px-4 py-5 text-xs text-muted-foreground sm:px-5"> | |
| 610 | Waiting for the native process monitor. | |
| 611 | </td> | |
| 612 | </tr> | |
| 613 | ) : null} | |
| 614 | {visible.map((process) => ( | |
| 615 | <tr key={processIdentityKey(process)} className="hover:bg-muted/20"> | |
| 616 | <td className="px-4 py-2 sm:pl-5"> | |
| 617 | <ProcessTreeName | |
| 618 | process={process} | |
| 619 | collapsed={collapsed.has(processIdentityKey(process))} | |
| 620 | onToggle={toggle} | |
| 621 | /> | |
| 622 | </td> | |
| 623 | <td className="truncate px-3 py-2 text-[11px] text-muted-foreground"> | |
| 624 | {categoryLabel(process.category)} | |
| 625 | </td> | |
| 626 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 627 | {process.cpuPercent.toFixed(1)}% | |
| 628 | </td> | |
| 629 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 630 | {formatCpuTime(process.cpuTimeMs)} | |
| 631 | </td> | |
| 632 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 633 | {formatBytes(process.residentBytes)} | |
| 634 | </td> | |
| 635 | <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300"> | |
| 636 | {formatRate(process.ioReadBytesPerSecond)} | |
| 637 | </td> | |
| 638 | <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300"> | |
| 639 | {formatRate(process.ioWriteBytesPerSecond)} | |
| 640 | </td> | |
| 641 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 642 | {formatBytes(process.ioReadBytes)} | |
| 643 | </td> | |
| 644 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 645 | <Tooltip> | |
| 646 | <TooltipTrigger render={<span>{formatBytes(process.ioWriteBytes)}</span>} /> | |
| 647 | <TooltipPopup side="top">{ioSemanticsLabel(process.ioSemantics)}</TooltipPopup> | |
| 648 | </Tooltip> | |
| 649 | </td> | |
| 650 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 651 | {process.identity.pid} | |
| 652 | </td> | |
| 653 | <td className="px-2 py-2 text-right sm:pr-4"> | |
| 654 | <ProcessActions | |
| 655 | process={process} | |
| 656 | signalingKeys={signalingKeys} | |
| 657 | onSignal={onSignal} | |
| 658 | /> | |
| 659 | </td> | |
| 660 | </tr> | |
| 661 | ))} | |
| 662 | </tbody> | |
| 663 | </table> | |
| 664 | </ScrollArea> | |
| 665 | </div> | |
| 662 | 666 | ); |
| 663 | 667 | } |
| 664 | 668 | |
| ... | ... | @@ -668,90 +672,92 @@ function HistoryProcessTable({ |
| 668 | 672 | processes: ReadonlyArray<ResourceTelemetryProcessSummary>; |
| 669 | 673 | }) { |
| 670 | 674 | return ( |
| 671 | <ScrollArea | |
| 672 | chainVerticalScroll | |
| 673 | scrollFade | |
| 674 | hideScrollbars | |
| 675 | className="max-h-[28rem] w-full max-w-full border-t border-border/60" | |
| 676 | > | |
| 677 | <table className="w-full min-w-[1020px] table-fixed text-left text-xs"> | |
| 678 | <colgroup> | |
| 679 | <col className="w-[24%]" /> | |
| 680 | <col className="w-[11%]" /> | |
| 681 | <col className="w-[10%]" /> | |
| 682 | <col className="w-[10%]" /> | |
| 683 | <col className="w-[11%]" /> | |
| 684 | <col className="w-[11%]" /> | |
| 685 | <col className="w-[11%]" /> | |
| 686 | <col className="w-[7%]" /> | |
| 687 | <col className="w-[5%]" /> | |
| 688 | </colgroup> | |
| 689 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65"> | |
| 690 | <tr> | |
| 691 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 692 | <th className="px-3 py-2 font-semibold">Category</th> | |
| 693 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 694 | <th className="px-3 py-2 text-right font-semibold">Peak CPU</th> | |
| 695 | <th className="px-3 py-2 text-right font-semibold">Peak Mem</th> | |
| 696 | <th className="px-3 py-2 text-right font-semibold">Read</th> | |
| 697 | <th className="px-3 py-2 text-right font-semibold">Write</th> | |
| 698 | <th className="px-3 py-2 text-right font-semibold">Samples</th> | |
| 699 | <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th> | |
| 700 | </tr> | |
| 701 | </thead> | |
| 702 | <tbody className="divide-y divide-border/50"> | |
| 703 | {processes.length === 0 ? ( | |
| 675 | <div className="border-t border-border/60"> | |
| 676 | <ScrollArea | |
| 677 | chainVerticalScroll | |
| 678 | scrollFade | |
| 679 | hideScrollbars | |
| 680 | className="max-h-[28rem] w-full max-w-full" | |
| 681 | > | |
| 682 | <table className="w-full min-w-[1020px] table-fixed text-left text-xs"> | |
| 683 | <colgroup> | |
| 684 | <col className="w-[24%]" /> | |
| 685 | <col className="w-[11%]" /> | |
| 686 | <col className="w-[10%]" /> | |
| 687 | <col className="w-[10%]" /> | |
| 688 | <col className="w-[11%]" /> | |
| 689 | <col className="w-[11%]" /> | |
| 690 | <col className="w-[11%]" /> | |
| 691 | <col className="w-[7%]" /> | |
| 692 | <col className="w-[5%]" /> | |
| 693 | </colgroup> | |
| 694 | <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65"> | |
| 704 | 695 | <tr> |
| 705 | <td colSpan={9} className="px-4 py-5 text-xs text-muted-foreground sm:px-5"> | |
| 706 | No retained process samples in this window. | |
| 707 | </td> | |
| 708 | </tr> | |
| 709 | ) : null} | |
| 710 | {processes.map((process) => ( | |
| 711 | <tr key={processSummaryIdentityKey(process)} className="hover:bg-muted/20"> | |
| 712 | <td className="px-4 py-2 sm:pl-5"> | |
| 713 | <Tooltip> | |
| 714 | <TooltipTrigger | |
| 715 | render={ | |
| 716 | <span className="block truncate font-medium text-foreground"> | |
| 717 | {process.name || process.command} | |
| 718 | </span> | |
| 719 | } | |
| 720 | /> | |
| 721 | <TooltipPopup side="top" variant="code"> | |
| 722 | {process.command || process.name} | |
| 723 | </TooltipPopup> | |
| 724 | </Tooltip> | |
| 725 | </td> | |
| 726 | <td className="truncate px-3 py-2 text-[11px] text-muted-foreground"> | |
| 727 | {categoryLabel(process.category)} | |
| 728 | </td> | |
| 729 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 730 | {formatCpuTime(process.cpuTimeMs)} | |
| 731 | </td> | |
| 732 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 733 | {process.maxCpuPercent.toFixed(1)}% | |
| 734 | </td> | |
| 735 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 736 | {formatBytes(process.peakRssBytes)} | |
| 737 | </td> | |
| 738 | <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300"> | |
| 739 | {formatBytes(process.ioReadBytes)} | |
| 740 | </td> | |
| 741 | <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300"> | |
| 742 | {formatBytes(process.ioWriteBytes)} | |
| 743 | </td> | |
| 744 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 745 | {process.sampleCount} | |
| 746 | </td> | |
| 747 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground sm:pr-5"> | |
| 748 | {process.identity.pid} | |
| 749 | </td> | |
| 696 | <th className="px-4 py-2 font-semibold sm:pl-5">Process</th> | |
| 697 | <th className="px-3 py-2 font-semibold">Category</th> | |
| 698 | <th className="px-3 py-2 text-right font-semibold">CPU Time</th> | |
| 699 | <th className="px-3 py-2 text-right font-semibold">Peak CPU</th> | |
| 700 | <th className="px-3 py-2 text-right font-semibold">Peak Mem</th> | |
| 701 | <th className="px-3 py-2 text-right font-semibold">Read</th> | |
| 702 | <th className="px-3 py-2 text-right font-semibold">Write</th> | |
| 703 | <th className="px-3 py-2 text-right font-semibold">Samples</th> | |
| 704 | <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th> | |
| 750 | 705 | </tr> |
| 751 | ))} | |
| 752 | </tbody> | |
| 753 | </table> | |
| 754 | </ScrollArea> | |
| 706 | </thead> | |
| 707 | <tbody className="divide-y divide-border/50"> | |
| 708 | {processes.length === 0 ? ( | |
| 709 | <tr> | |
| 710 | <td colSpan={9} className="px-4 py-5 text-xs text-muted-foreground sm:px-5"> | |
| 711 | No retained process samples in this window. | |
| 712 | </td> | |
| 713 | </tr> | |
| 714 | ) : null} | |
| 715 | {processes.map((process) => ( | |
| 716 | <tr key={processSummaryIdentityKey(process)} className="hover:bg-muted/20"> | |
| 717 | <td className="px-4 py-2 sm:pl-5"> | |
| 718 | <Tooltip> | |
| 719 | <TooltipTrigger | |
| 720 | render={ | |
| 721 | <span className="block truncate font-medium text-foreground"> | |
| 722 | {process.name || process.command} | |
| 723 | </span> | |
| 724 | } | |
| 725 | /> | |
| 726 | <TooltipPopup side="top" variant="code"> | |
| 727 | {process.command || process.name} | |
| 728 | </TooltipPopup> | |
| 729 | </Tooltip> | |
| 730 | </td> | |
| 731 | <td className="truncate px-3 py-2 text-[11px] text-muted-foreground"> | |
| 732 | {categoryLabel(process.category)} | |
| 733 | </td> | |
| 734 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 735 | {formatCpuTime(process.cpuTimeMs)} | |
| 736 | </td> | |
| 737 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 738 | {process.maxCpuPercent.toFixed(1)}% | |
| 739 | </td> | |
| 740 | <td className="px-3 py-2 text-right font-mono tabular-nums"> | |
| 741 | {formatBytes(process.peakRssBytes)} | |
| 742 | </td> | |
| 743 | <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300"> | |
| 744 | {formatBytes(process.ioReadBytes)} | |
| 745 | </td> | |
| 746 | <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300"> | |
| 747 | {formatBytes(process.ioWriteBytes)} | |
| 748 | </td> | |
| 749 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground"> | |
| 750 | {process.sampleCount} | |
| 751 | </td> | |
| 752 | <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground sm:pr-5"> | |
| 753 | {process.identity.pid} | |
| 754 | </td> | |
| 755 | </tr> | |
| 756 | ))} | |
| 757 | </tbody> | |
| 758 | </table> | |
| 759 | </ScrollArea> | |
| 760 | </div> | |
| 755 | 761 | ); |
| 756 | 762 | } |
| 757 | 763 |
apps/web/src/components/settings/SettingsScopeNotice.tsx+1-1| ... | ... | @@ -66,7 +66,7 @@ export function SettingsScopeNotice({ |
| 66 | 66 | <Alert role="status"> |
| 67 | 67 | <AlertDescription> |
| 68 | 68 | <p>{children}</p> |
| 69 | <AlertAction className="flex-wrap gap-2"> | |
| 69 | <AlertAction className="flex-wrap"> | |
| 70 | 70 | {choices.map((choice) => ( |
| 71 | 71 | <Button |
| 72 | 72 | key={JSON.stringify(choice.search)} |
apps/web/src/components/settings/SettingsSidebarNav.tsx+105-101| ... | ... | @@ -234,113 +234,117 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 234 | 234 | return ( |
| 235 | 235 | <> |
| 236 | 236 | <SidebarContent className="overflow-x-hidden"> |
| 237 | <SidebarGroup className="gap-2"> | |
| 238 | <div className="flex h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground"> | |
| 239 | <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> | |
| 240 | <SidebarInput | |
| 241 | ref={searchInputRef} | |
| 242 | nativeInput | |
| 243 | type="search" | |
| 244 | value={query} | |
| 245 | onChange={(event) => { | |
| 246 | setQuery(event.currentTarget.value); | |
| 247 | setActiveResultIndex(0); | |
| 248 | }} | |
| 249 | onKeyDown={handleSearchKeyDown} | |
| 250 | placeholder="Search" | |
| 251 | aria-label="Search settings" | |
| 252 | role="combobox" | |
| 253 | aria-autocomplete="list" | |
| 254 | aria-expanded={isSearching && hasResults} | |
| 255 | aria-controls={isSearching && hasResults ? "settings-search-results" : undefined} | |
| 256 | aria-activedescendant={ | |
| 257 | isSearching && results[activeResultIndex] | |
| 258 | ? `settings-search-result-${results[activeResultIndex].id}` | |
| 259 | : undefined | |
| 260 | } | |
| 261 | className="min-w-0 flex-1" | |
| 262 | /> | |
| 263 | {isSearching ? ( | |
| 264 | <Button | |
| 265 | type="button" | |
| 266 | size="icon-micro" | |
| 267 | variant="ghost" | |
| 268 | className="shrink-0 text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" | |
| 269 | aria-label="Clear settings search" | |
| 270 | onClick={() => { | |
| 271 | clearSearch(); | |
| 272 | searchInputRef.current?.focus(); | |
| 237 | <SidebarGroup> | |
| 238 | <div className="flex flex-col gap-2"> | |
| 239 | <div className="flex h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground"> | |
| 240 | <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> | |
| 241 | <SidebarInput | |
| 242 | ref={searchInputRef} | |
| 243 | nativeInput | |
| 244 | type="search" | |
| 245 | value={query} | |
| 246 | onChange={(event) => { | |
| 247 | setQuery(event.currentTarget.value); | |
| 248 | setActiveResultIndex(0); | |
| 273 | 249 | }} |
| 250 | onKeyDown={handleSearchKeyDown} | |
| 251 | placeholder="Search" | |
| 252 | aria-label="Search settings" | |
| 253 | role="combobox" | |
| 254 | aria-autocomplete="list" | |
| 255 | aria-expanded={isSearching && hasResults} | |
| 256 | aria-controls={isSearching && hasResults ? "settings-search-results" : undefined} | |
| 257 | aria-activedescendant={ | |
| 258 | isSearching && results[activeResultIndex] | |
| 259 | ? `settings-search-result-${results[activeResultIndex].id}` | |
| 260 | : undefined | |
| 261 | } | |
| 262 | className="min-w-0 flex-1" | |
| 263 | /> | |
| 264 | {isSearching ? ( | |
| 265 | <Button | |
| 266 | type="button" | |
| 267 | size="icon-micro" | |
| 268 | variant="ghost" | |
| 269 | className="shrink-0 text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" | |
| 270 | aria-label="Clear settings search" | |
| 271 | onClick={() => { | |
| 272 | clearSearch(); | |
| 273 | searchInputRef.current?.focus(); | |
| 274 | }} | |
| 275 | > | |
| 276 | <XIcon className="size-3" /> | |
| 277 | </Button> | |
| 278 | ) : ( | |
| 279 | <Kbd>/</Kbd> | |
| 280 | )} | |
| 281 | </div> | |
| 282 | {isSearching && results.length === 0 ? ( | |
| 283 | <p | |
| 284 | role="status" | |
| 285 | className="px-2 py-6 text-center text-xs text-sidebar-muted-foreground" | |
| 274 | 286 | > |
| 275 | <XIcon className="size-3" /> | |
| 276 | </Button> | |
| 277 | ) : ( | |
| 278 | <Kbd>/</Kbd> | |
| 279 | )} | |
| 280 | </div> | |
| 281 | {isSearching && results.length === 0 ? ( | |
| 282 | <p | |
| 283 | role="status" | |
| 284 | className="px-2 py-6 text-center text-xs text-sidebar-muted-foreground" | |
| 285 | > | |
| 286 | No settings found | |
| 287 | </p> | |
| 288 | ) : null} | |
| 289 | {isSearching ? ( | |
| 290 | <SidebarMenu | |
| 291 | className="ps-px" | |
| 292 | id={hasResults ? "settings-search-results" : undefined} | |
| 293 | role={hasResults ? "listbox" : undefined} | |
| 294 | aria-label={hasResults ? "Settings search results" : undefined} | |
| 295 | > | |
| 296 | {results.map((item, index) => ( | |
| 297 | <SidebarMenuItem key={item.id} role="presentation"> | |
| 298 | <SidebarMenuButton | |
| 299 | id={`settings-search-result-${item.id}`} | |
| 300 | role="option" | |
| 301 | aria-selected={index === activeResultIndex} | |
| 302 | tabIndex={-1} | |
| 303 | size="sm" | |
| 304 | isActive={index === activeResultIndex} | |
| 305 | className="h-auto min-h-10 items-start gap-2 rounded-md px-2 py-2 text-left hover:bg-sidebar-row-hover hover:text-sidebar-foreground" | |
| 306 | onMouseMove={() => setActiveResultIndex(index)} | |
| 307 | onClick={() => handleSearchResultClick(item)} | |
| 308 | > | |
| 309 | <SettingsSectionIcon to={item.to} /> | |
| 310 | <span className="min-w-0 flex-1"> | |
| 311 | <span className="block truncate text-sm font-medium text-sidebar-foreground"> | |
| 312 | {item.title} | |
| 313 | </span> | |
| 314 | <span className="block truncate text-[11px] text-sidebar-muted-foreground/75"> | |
| 315 | {SETTINGS_SECTION_LABELS[item.to]} | |
| 316 | </span> | |
| 317 | </span> | |
| 318 | </SidebarMenuButton> | |
| 319 | </SidebarMenuItem> | |
| 320 | ))} | |
| 321 | </SidebarMenu> | |
| 322 | ) : ( | |
| 323 | <SidebarMenu className="ps-px"> | |
| 324 | {navItems.map((item) => { | |
| 325 | const Icon = item.icon; | |
| 326 | const isGeneralDetailPage = | |
| 327 | item.to === "/settings/general" && pathname === "/settings/open-source-licenses"; | |
| 328 | const isActive = | |
| 329 | isGeneralDetailPage || pathname === item.to || pathname.startsWith(`${item.to}/`); | |
| 330 | return ( | |
| 331 | <SidebarMenuItem key={item.to}> | |
| 287 | No settings found | |
| 288 | </p> | |
| 289 | ) : null} | |
| 290 | {isSearching ? ( | |
| 291 | <SidebarMenu | |
| 292 | id={hasResults ? "settings-search-results" : undefined} | |
| 293 | role={hasResults ? "listbox" : undefined} | |
| 294 | aria-label={hasResults ? "Settings search results" : undefined} | |
| 295 | > | |
| 296 | {results.map((item, index) => ( | |
| 297 | <SidebarMenuItem key={item.id} role="presentation"> | |
| 332 | 298 | <SidebarMenuButton |
| 333 | isActive={isActive} | |
| 334 | onClick={() => handleSectionClick(item.to)} | |
| 299 | id={`settings-search-result-${item.id}`} | |
| 300 | role="option" | |
| 301 | aria-selected={index === activeResultIndex} | |
| 302 | tabIndex={-1} | |
| 303 | size="sm" | |
| 304 | isActive={index === activeResultIndex} | |
| 305 | className="h-auto min-h-10 items-start" | |
| 306 | onMouseMove={() => setActiveResultIndex(index)} | |
| 307 | onClick={() => handleSearchResultClick(item)} | |
| 335 | 308 | > |
| 336 | <Icon /> | |
| 337 | <span className="truncate">{item.label}</span> | |
| 309 | <SettingsSectionIcon to={item.to} /> | |
| 310 | <span className="min-w-0 flex-1"> | |
| 311 | <span className="block truncate text-sm font-medium text-sidebar-foreground"> | |
| 312 | {item.title} | |
| 313 | </span> | |
| 314 | <span className="block truncate text-[11px] text-sidebar-muted-foreground/75"> | |
| 315 | {SETTINGS_SECTION_LABELS[item.to]} | |
| 316 | </span> | |
| 317 | </span> | |
| 338 | 318 | </SidebarMenuButton> |
| 339 | 319 | </SidebarMenuItem> |
| 340 | ); | |
| 341 | })} | |
| 342 | </SidebarMenu> | |
| 343 | )} | |
| 320 | ))} | |
| 321 | </SidebarMenu> | |
| 322 | ) : ( | |
| 323 | <SidebarMenu> | |
| 324 | {navItems.map((item) => { | |
| 325 | const Icon = item.icon; | |
| 326 | const isGeneralDetailPage = | |
| 327 | item.to === "/settings/general" && | |
| 328 | pathname === "/settings/open-source-licenses"; | |
| 329 | const isActive = | |
| 330 | isGeneralDetailPage || | |
| 331 | pathname === item.to || | |
| 332 | pathname.startsWith(`${item.to}/`); | |
| 333 | return ( | |
| 334 | <SidebarMenuItem key={item.to}> | |
| 335 | <SidebarMenuButton | |
| 336 | isActive={isActive} | |
| 337 | onClick={() => handleSectionClick(item.to)} | |
| 338 | > | |
| 339 | <Icon /> | |
| 340 | <span className="truncate">{item.label}</span> | |
| 341 | </SidebarMenuButton> | |
| 342 | </SidebarMenuItem> | |
| 343 | ); | |
| 344 | })} | |
| 345 | </SidebarMenu> | |
| 346 | )} | |
| 347 | </div> | |
| 344 | 348 | </SidebarGroup> |
| 345 | 349 | </SidebarContent> |
| 346 | 350 | <SidebarFooter> |
apps/web/src/components/settings/ThemeColorPicker.tsx+2-2| ... | ... | @@ -284,7 +284,7 @@ function ThemeColorPicker({ |
| 284 | 284 | </Tooltip> |
| 285 | 285 | <PopoverPopup |
| 286 | 286 | align="end" |
| 287 | className="overflow-hidden rounded-2xl border border-border/70 p-0 shadow-2xl [--viewport-inline-padding:0px] [&_[data-slot=popover-viewport]]:p-0" | |
| 287 | padding="none" | |
| 288 | 288 | data-theme-editor-panel="" |
| 289 | 289 | side="bottom" |
| 290 | 290 | sideOffset={10} |
| ... | ... | @@ -353,7 +353,7 @@ export const ThemeColorField = memo(function ThemeColorField({ |
| 353 | 353 | <Input |
| 354 | 354 | aria-invalid={!isColorValue} |
| 355 | 355 | aria-label={`${label} hex value`} |
| 356 | className="w-28 shrink-0 [&_[data-slot=input]]:text-right" | |
| 356 | className="w-28 shrink-0" | |
| 357 | 357 | font="mono" |
| 358 | 358 | id={`${role}-hex`} |
| 359 | 359 | nativeInput |
apps/web/src/components/settings/ThemeEditorPanel.tsx+1-5| ... | ... | @@ -947,11 +947,7 @@ export function ThemeEditorPanel({ |
| 947 | 947 | // A locked mode stays hoverable so the tooltip can say why it is off; |
| 948 | 948 | // a real disabled attribute would swallow the pointer events. |
| 949 | 949 | const button = ( |
| 950 | <Toggle | |
| 951 | aria-disabled={lockReason !== null} | |
| 952 | value={appearance} | |
| 953 | className={lockReason !== null ? "opacity-50" : undefined} | |
| 954 | > | |
| 950 | <Toggle aria-disabled={lockReason !== null} value={appearance}> | |
| 955 | 951 | {appearance === "light" ? "Light" : "Dark"} |
| 956 | 952 | </Toggle> |
| 957 | 953 | ); |
apps/web/src/components/sidebar/SidebarChrome.tsx+7-7| ... | ... | @@ -17,7 +17,6 @@ import { |
| 17 | 17 | import { Badge } from "../ui/badge"; |
| 18 | 18 | import { |
| 19 | 19 | SidebarFooter, |
| 20 | SidebarHeader, | |
| 21 | 20 | SidebarMenu, |
| 22 | 21 | SidebarMenuButton, |
| 23 | 22 | SidebarMenuItem, |
| ... | ... | @@ -48,18 +47,19 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({ |
| 48 | 47 | : null; |
| 49 | 48 | |
| 50 | 49 | return ( |
| 51 | <SidebarHeader | |
| 50 | // The titlebar row, not a padded SidebarHeader: it aligns to the window controls. | |
| 51 | <div | |
| 52 | 52 | className={cn( |
| 53 | "@container/sidebar-header relative h-[var(--workspace-topbar-height)] shrink-0 flex-row items-center px-3 py-0 md:px-0", | |
| 53 | "@container/sidebar-header relative flex h-[var(--workspace-topbar-height)] shrink-0 flex-row items-center gap-2 px-3 md:px-0", | |
| 54 | 54 | isElectron && "drag-region", |
| 55 | 55 | )} |
| 56 | 56 | > |
| 57 | 57 | {backdropVariant ? <SidebarStageBackdrop variant={backdropVariant} /> : null} |
| 58 | 58 | <SidebarTrigger |
| 59 | // Over the stage artwork: the media viewer's control-on-imagery treatment. | |
| 60 | variant={backdropVariant ? "media-navigation" : "ghost"} | |
| 59 | 61 | className={cn( |
| 60 | "relative z-10 md:hidden", | |
| 61 | backdropVariant && | |
| 62 | "focus-visible:ring-white/90 [&_svg]:stroke-white/90! [&_svg]:opacity-100! [&_svg]:hover:stroke-white! [:hover,[data-pressed]]:bg-white/15", | |
| 62 | "relative top-auto z-10 translate-y-0 md:hidden", | |
| 63 | 63 | backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant), |
| 64 | 64 | )} |
| 65 | 65 | /> |
| ... | ... | @@ -74,7 +74,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({ |
| 74 | 74 | {pillLabel} |
| 75 | 75 | </Badge> |
| 76 | 76 | ) : null} |
| 77 | </SidebarHeader> | |
| 77 | </div> | |
| 78 | 78 | ); |
| 79 | 79 | }); |
| 80 | 80 |
apps/web/src/components/sidebar/SidebarThreadHeader.tsx+1-4| ... | ... | @@ -187,10 +187,7 @@ export function SidebarHeaderIconButton({ |
| 187 | 187 | type="button" |
| 188 | 188 | aria-label={label} |
| 189 | 189 | {...rest} |
| 190 | className={cn( | |
| 191 | "relative size-7 shrink-0 focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar", | |
| 192 | className, | |
| 193 | )} | |
| 190 | className={cn("relative size-7 shrink-0", className)} | |
| 194 | 191 | /> |
| 195 | 192 | } |
| 196 | 193 | > |
apps/web/src/components/sidebar/SidebarUpdatePill.tsx-1| ... | ... | @@ -393,7 +393,6 @@ function SidebarUpdateControl() { |
| 393 | 393 | <PopoverPopup |
| 394 | 394 | align="center" |
| 395 | 395 | aria-label="Nightly update release notes" |
| 396 | className="text-balance shadow-xl shadow-black/25" | |
| 397 | 396 | initialFocus={false} |
| 398 | 397 | onKeyDownCapture={(event) => { |
| 399 | 398 | if ( |
apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx+2-2| ... | ... | @@ -85,7 +85,7 @@ export function SidebarUpdateReleaseNotes({ |
| 85 | 85 | |
| 86 | 86 | return ( |
| 87 | 87 | <div key={releaseNote.version}> |
| 88 | {index > 0 && <Separator className="my-3 bg-border/60" />} | |
| 88 | {index > 0 && <Separator className="my-3" />} | |
| 89 | 89 | <section> |
| 90 | 90 | <h3 className="text-foreground text-xs leading-4 font-semibold"> |
| 91 | 91 | {index === 0 ? "What's changed" : `Changes in ${releaseNote.version}`} |
| ... | ... | @@ -108,7 +108,7 @@ export function SidebarUpdateReleaseNotes({ |
| 108 | 108 | })} |
| 109 | 109 | {state.omittedReleaseCount > 0 ? ( |
| 110 | 110 | <div> |
| 111 | <Separator className="my-3 bg-border/60" /> | |
| 111 | <Separator className="my-3" /> | |
| 112 | 112 | <ReleaseLink releaseUrl={getDesktopUpdateReleaseHistoryUrl()} shell={shell}> |
| 113 | 113 | {`${state.omittedReleaseCount} older ${state.omittedReleaseCount === 1 ? "release" : "releases"} on GitHub`} |
| 114 | 114 | </ReleaseLink> |
apps/web/src/components/ui/toggle.tsx+1-1| ... | ... | @@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"; |
| 6 | 6 | import { cn } from "~/lib/utils"; |
| 7 | 7 | |
| 8 | 8 | const toggleVariants = cva( |
| 9 | "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | |
| 9 | "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | |
| 10 | 10 | { |
| 11 | 11 | defaultVariants: { |
| 12 | 12 | size: "default", |
apps/web/src/components/usage/UsageLimitsPooled.tsx+2-1| ... | ... | @@ -345,7 +345,8 @@ function LegendRow({ |
| 345 | 345 | return ( |
| 346 | 346 | <PopoverTrigger |
| 347 | 347 | style={{ gridColumn: "1 / -1", gridRow: index + 1 }} |
| 348 | className="flex min-h-7 min-w-0 cursor-pointer items-center gap-2 rounded-md px-1 py-0.5 text-start text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring @2xl/pool:hidden" | |
| 348 | render={<Button variant="ghost" size="compact" />} | |
| 349 | className="min-w-0 @2xl/pool:hidden" | |
| 349 | 350 | > |
| 350 | 351 | <span className="relative inline-flex size-4 shrink-0 items-center justify-center rounded-sm text-[10px] leading-none font-semibold text-foreground/80 tabular-nums"> |
| 351 | 352 | <span |
apps/web/src/components/usage/UsagePriceOverrides.tsx+18-21| ... | ... | @@ -323,12 +323,12 @@ export function UsagePriceOverrides({ |
| 323 | 323 | <col className="w-10" /> |
| 324 | 324 | </colgroup> |
| 325 | 325 | <TableHeader> |
| 326 | <TableRow className="hover:bg-transparent"> | |
| 327 | <TableHead className="pl-3">Model ID</TableHead> | |
| 326 | <TableRow> | |
| 327 | <TableHead>Model ID</TableHead> | |
| 328 | 328 | {USAGE_PRICE_FIELDS.map((field) => ( |
| 329 | 329 | <TableHead key={field.key}>{field.label}</TableHead> |
| 330 | 330 | ))} |
| 331 | <TableHead className="px-1"> | |
| 331 | <TableHead> | |
| 332 | 332 | <Button |
| 333 | 333 | size="icon-xs" |
| 334 | 334 | variant="ghost" |
| ... | ... | @@ -347,24 +347,19 @@ export function UsagePriceOverrides({ |
| 347 | 347 | </TableHeader> |
| 348 | 348 | <TableBody> |
| 349 | 349 | {rows.length === 0 ? ( |
| 350 | <TableRow className="hover:bg-transparent"> | |
| 351 | <TableCell | |
| 352 | colSpan={6} | |
| 353 | className="py-8 text-center whitespace-normal text-muted-foreground" | |
| 354 | > | |
| 355 | {selected.some((environment) => environment.prices === null) | |
| 356 | ? "Some environment prices are unavailable." | |
| 357 | : "No custom prices. Add a row to override automatic pricing."} | |
| 350 | <TableRow> | |
| 351 | <TableCell colSpan={6} className="text-center whitespace-normal"> | |
| 352 | <p className="py-6 text-muted-foreground"> | |
| 353 | {selected.some((environment) => environment.prices === null) | |
| 354 | ? "Some environment prices are unavailable." | |
| 355 | : "No custom prices. Add a row to override automatic pricing."} | |
| 356 | </p> | |
| 358 | 357 | </TableCell> |
| 359 | 358 | </TableRow> |
| 360 | 359 | ) : ( |
| 361 | 360 | rows.map((row) => ( |
| 362 | <TableRow | |
| 363 | key={row.id} | |
| 364 | data-row-id={row.id} | |
| 365 | className="hover:bg-transparent" | |
| 366 | > | |
| 367 | <TableCell className="pl-3 whitespace-normal"> | |
| 361 | <TableRow key={row.id} data-row-id={row.id}> | |
| 362 | <TableCell className="whitespace-normal"> | |
| 368 | 363 | {row.isNew ? ( |
| 369 | 364 | <Input |
| 370 | 365 | size="compact" |
| ... | ... | @@ -407,14 +402,16 @@ export function UsagePriceOverrides({ |
| 407 | 402 | ) : null} |
| 408 | 403 | </TableCell> |
| 409 | 404 | {row.removed ? ( |
| 410 | <TableCell colSpan={4} className="text-muted-foreground"> | |
| 411 | Automatic pricing after saving | |
| 405 | <TableCell colSpan={4}> | |
| 406 | <span className="text-muted-foreground"> | |
| 407 | Automatic pricing after saving | |
| 408 | </span> | |
| 412 | 409 | </TableCell> |
| 413 | 410 | ) : ( |
| 414 | 411 | USAGE_PRICE_FIELDS.map((field) => { |
| 415 | 412 | const cell = usagePriceCell(selected, row.model, field.key); |
| 416 | 413 | return ( |
| 417 | <TableCell key={field.key} className="px-1"> | |
| 414 | <TableCell key={field.key}> | |
| 418 | 415 | <Input |
| 419 | 416 | size="compact" |
| 420 | 417 | inputMode="decimal" |
| ... | ... | @@ -438,7 +435,7 @@ export function UsagePriceOverrides({ |
| 438 | 435 | ); |
| 439 | 436 | }) |
| 440 | 437 | )} |
| 441 | <TableCell className="px-1"> | |
| 438 | <TableCell> | |
| 442 | 439 | <Tooltip> |
| 443 | 440 | <TooltipTrigger |
| 444 | 441 | render={<Button size="icon-xs" variant="ghost" />} |
apps/web/src/routes/_chat.pull-requests.tsx+1-1| ... | ... | @@ -218,7 +218,7 @@ function PullRequestGroupHeader({ |
| 218 | 218 | <Icon aria-hidden className="size-3.5 shrink-0" /> |
| 219 | 219 | <h2 className="shrink-0">{group.label}</h2> |
| 220 | 220 | <span className="shrink-0 tabular-nums text-muted-foreground/50">{group.entries.length}</span> |
| 221 | <Separator className="min-w-2 flex-1 bg-border/60" /> | |
| 221 | <Separator className="min-w-2 flex-1" /> | |
| 222 | 222 | </div> |
| 223 | 223 | ); |
| 224 | 224 | } |
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 = 402; | |
| 11 | export const RESTYLE_CEILING = 250; | |
| 12 | 12 | |
| 13 | 13 | const RULE = "shadcn(no-restyle)"; |
| 14 | 14 |