authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 21:33:02-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 21:33:02-07:00
logf46522777372be1705e3b7a1ab08756f236db5d2
tree073cf891878b64f05fe464e289b4f04647671fa7
parent21079391c72d1eeaa3c40f1cc5eb3e78abf0a846
signature Signed by PGP key B5690EEEBB952194

refactor(web): app code stops restyling sidebar, popover, table and misc ui exports (#13207)

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,11 +125,12 @@ function SidebarControl() {
125 <TooltipTrigger125 <TooltipTrigger
126 render={126 render={
127 <SidebarTrigger127 <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 className={cn(131 className={cn(
129 "pointer-events-auto",132 "pointer-events-auto",
130 isSidebarVisible &&133 isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0",
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 &&134 isSidebarVisible &&
134 stageBackdropVariant &&135 stageBackdropVariant &&
135 resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),136 resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),
...@@ -244,7 +245,6 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {...@@ -244,7 +245,6 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {
244 side="left"245 side="left"
245 collapsible="offcanvas"246 collapsible="offcanvas"
246 data-app-sidebar=""247 data-app-sidebar=""
247 className="border-r border-sidebar-border"
248 resizable={{248 resizable={{
249 maxWidth: sidebarMaximumWidth,249 maxWidth: sidebarMaximumWidth,
250 minWidth: THREAD_SIDEBAR_MIN_WIDTH,250 minWidth: THREAD_SIDEBAR_MIN_WIDTH,
apps/web/src/components/BranchToolbarBranchSelector.tsx+4-3
...@@ -817,11 +817,12 @@ export function BranchToolbarBranchSelector({...@@ -817,11 +817,12 @@ export function BranchToolbarBranchSelector({
817 data-composer-label817 data-composer-label
818 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"818 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
819 >819 >
820 <MiddleTruncate820 <span
821 value={triggerLabel}
822 data-composer-label-motion821 data-composer-label-motion
823 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"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 </span>826 </span>
826 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />827 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />
827 </ComboboxTrigger>828 </ComboboxTrigger>
apps/web/src/components/CustomSnoozeDialog.tsx+3-9
...@@ -93,7 +93,7 @@ function CustomSnoozeDialog() {...@@ -93,7 +93,7 @@ function CustomSnoozeDialog() {
93 <DialogTitle>Custom snooze</DialogTitle>93 <DialogTitle>Custom snooze</DialogTitle>
94 <DialogDescription>Choose when snoozed threads return to your inbox.</DialogDescription>94 <DialogDescription>Choose when snoozed threads return to your inbox.</DialogDescription>
95 </DialogHeader>95 </DialogHeader>
96 <DialogPanel className="text-base sm:text-sm">96 <DialogPanel>
97 <div className="flex flex-col gap-4">97 <div className="flex flex-col gap-4">
98 <ToggleGroup98 <ToggleGroup
99 aria-label="Schedule type"99 aria-label="Schedule type"
...@@ -147,10 +147,7 @@ function CustomSnoozeDialog() {...@@ -147,10 +147,7 @@ function CustomSnoozeDialog() {
147 </PopoverPopup>147 </PopoverPopup>
148 </Popover>148 </Popover>
149 </div>149 </div>
150 <Label150 <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-time`}>
151 className="flex min-w-0 flex-col items-stretch gap-1.5"
152 htmlFor={`${id}-time`}
153 >
154 Time151 Time
155 <Input152 <Input
156 nativeInput153 nativeInput
...@@ -185,10 +182,7 @@ function CustomSnoozeDialog() {...@@ -185,10 +182,7 @@ function CustomSnoozeDialog() {
185 <NumberFieldIncrement aria-label="Increase duration" />182 <NumberFieldIncrement aria-label="Increase duration" />
186 </NumberFieldGroup>183 </NumberFieldGroup>
187 </NumberField>184 </NumberField>
188 <Label185 <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-unit`}>
189 className="flex min-w-0 flex-col items-stretch gap-1.5"
190 htmlFor={`${id}-unit`}
191 >
192 Unit186 Unit
193 <Select187 <Select
194 value={unit}188 value={unit}
apps/web/src/components/GitActionsControl.tsx+59-53
...@@ -627,7 +627,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {...@@ -627,7 +627,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
627 setPublishRepositoryOverride(null);627 setPublishRepositoryOverride(null);
628 }}628 }}
629 aria-labelledby="publish-provider-cards-label"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 {sortedPublishProviderOptions.map((option) => {632 {sortedPublishProviderOptions.map((option) => {
633 const readiness = publishProviderReadiness[option.value];633 const readiness = publishProviderReadiness[option.value];
...@@ -737,7 +737,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {...@@ -737,7 +737,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
737 }737 }
738 aria-labelledby="publish-visibility-cards-label"738 aria-labelledby="publish-visibility-cards-label"
739 disabled={publishRepositoryAction.isPending}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,57 +1915,63 @@ export default function GitActionsControl({
1915 <p className="font-medium">none</p>1915 <p className="font-medium">none</p>
1916 ) : (1916 ) : (
1917 <div className="space-y-2">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">1918 <div 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">1919 <ScrollArea>
1920 {allFiles.map((file) => {1920 <div className="space-y-1 p-1">
1921 const isExcluded = excludedFiles.has(file.path);1921 {allFiles.map((file) => {
1922 return (1922 const isExcluded = excludedFiles.has(file.path);
1923 <div1923 return (
1924 key={file.path}1924 <div
1925 className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50"1925 key={file.path}
1926 >1926 className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50"
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)}
1947 >1927 >
1948 <StartTruncatedPath1928 {isEditingFiles && (
1949 path={file.path}1929 <Checkbox
1950 className={`flex-1${isExcluded ? " text-muted-foreground" : ""}`}1930 checked={!excludedFiles.has(file.path)}
1951 />1931 onCheckedChange={() => {
1952 <span className="shrink-0">1932 setExcludedFiles((prev) => {
1953 {isExcluded ? (1933 const next = new Set(prev);
1954 <span className="text-muted-foreground">Excluded</span>1934 if (next.has(file.path)) {
1955 ) : (1935 next.delete(file.path);
1956 <>1936 } else {
1957 <span className="text-diff-addition">+{file.insertions}</span>1937 next.add(file.path);
1958 <span className="text-muted-foreground"> / </span>1938 }
1959 <span className="text-diff-deletion">-{file.deletions}</span>1939 return next;
1960 </>1940 });
1961 )}1941 }}
1962 </span>1942 />
1963 </button>1943 )}
1964 </div>1944 <button
1965 );1945 type="button"
1966 })}1946 className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left"
1967 </div>1947 onClick={() => openChangedFileInEditor(file.path)}
1968 </ScrollArea>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 <div className="flex justify-end font-mono">1975 <div className="flex justify-end font-mono">
1970 <span className="text-diff-addition">1976 <span className="text-diff-addition">
1971 +{selectedFiles.reduce((sum, f) => sum + f.insertions, 0)}1977 +{selectedFiles.reduce((sum, f) => sum + f.insertions, 0)}
...@@ -2040,7 +2046,7 @@ export default function GitActionsControl({...@@ -2040,7 +2046,7 @@ export default function GitActionsControl({
2040 </DialogTitle>2046 </DialogTitle>
2041 <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription>2047 <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription>
2042 </DialogHeader>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 <Button2050 <Button
2045 className="w-full sm:mr-auto sm:w-auto"2051 className="w-full sm:mr-auto sm:w-auto"
2046 variant="outline"2052 variant="outline"
apps/web/src/components/LegacySidebar.tsx+7-11
...@@ -1038,7 +1038,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(...@@ -1038,7 +1038,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
1038 return (1038 return (
1039 <SidebarMenuSub1039 <SidebarMenuSub
1040 ref={attachThreadListAutoAnimateRef}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 {shouldShowThreadPanel && showEmptyThreadState ? (1043 {shouldShowThreadPanel && showEmptyThreadState ? (
1044 <SidebarMenuSubItem className="w-full" data-thread-selection-safe>1044 <SidebarMenuSubItem className="w-full" data-thread-selection-safe>
...@@ -2357,9 +2357,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -2357,9 +2357,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
2357 <div className="group/project-header relative">2357 <div className="group/project-header relative">
2358 <SidebarMenuButton2358 <SidebarMenuButton
2359 ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined}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 ${2360 className={isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : undefined}
2361 isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : ""
2362 }`}
2363 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})}2361 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})}
2364 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.listeners : {})}2362 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.listeners : {})}
2365 onPointerDownCapture={handleProjectButtonPointerDownCapture}2363 onPointerDownCapture={handleProjectButtonPointerDownCapture}
...@@ -2408,6 +2406,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -2408,6 +2406,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
2408 </span>2406 </span>
2409 ) : null}2407 ) : null}
2410 </span>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 </SidebarMenuButton>2412 </SidebarMenuButton>
2412 {/* Environment badge – visible by default, crossfades with the2413 {/* Environment badge – visible by default, crossfades with the
2413 "new thread" button on hover using the same pointer-events +2414 "new thread" button on hover using the same pointer-events +
...@@ -2618,7 +2619,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -2618,7 +2619,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
26182619
2619const SidebarProjectListRow = memo(function SidebarProjectListRow(props: SidebarProjectItemProps) {2620const SidebarProjectListRow = memo(function SidebarProjectListRow(props: SidebarProjectItemProps) {
2620 return (2621 return (
2621 <SidebarMenuItem className="rounded-md">2622 <SidebarMenuItem>
2622 <SidebarProjectItem {...props} />2623 <SidebarProjectItem {...props} />
2623 </SidebarMenuItem>2624 </SidebarMenuItem>
2624 );2625 );
...@@ -2963,12 +2964,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(...@@ -2963,12 +2964,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
2963 <SidebarMenu>2964 <SidebarMenu>
2964 <SidebarMenuItem>2965 <SidebarMenuItem>
2965 <CommandDialogTrigger2966 <CommandDialogTrigger
2966 render={2967 render={<SidebarMenuButton data-testid="command-palette-trigger" />}
2967 <SidebarMenuButton
2968 className="focus-visible:ring-0"
2969 data-testid="command-palette-trigger"
2970 />
2971 }
2972 >2968 >
2973 <SearchIcon />2969 <SearchIcon />
2974 <span className="flex-1 truncate">Search</span>2970 <span className="flex-1 truncate">Search</span>
apps/web/src/components/PullRequestThreadDialog.tsx+2-2
...@@ -196,8 +196,8 @@ export function PullRequestThreadDialog({...@@ -196,8 +196,8 @@ export function PullRequestThreadDialog({
196 >196 >
197 <DialogPopup className="max-w-xl">197 <DialogPopup className="max-w-xl">
198 <DialogHeader>198 <DialogHeader>
199 <DialogTitle className="flex items-center gap-2">199 <DialogTitle className="flex items-center">
200 <SourceControlIcon className="size-4" />200 <SourceControlIcon className="me-2 size-4" />
201 Checkout {terminology.singular}201 Checkout {terminology.singular}
202 </DialogTitle>202 </DialogTitle>
203 <DialogDescription>203 <DialogDescription>
apps/web/src/components/Sidebar.tsx+9-16
...@@ -345,14 +345,9 @@ function SidebarThreadTooltip({...@@ -345,14 +345,9 @@ function SidebarThreadTooltip({
345 const driverKind = providerEntry?.driverKind ?? null;345 const driverKind = providerEntry?.driverKind ?? null;
346 const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId);346 const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId);
347 return (347 return (
348 <TooltipPopup348 <TooltipPopup side="right" align="start" sideOffset={4} variant="glass">
349 side="right"349 {/* The viewport's own inset (py-1 px-2) plus this one make the floating inset. */}
350 align="start"350 <div className="flex min-w-0 max-w-80 flex-col gap-2 px-1 py-2">
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)]">
356 <div className="min-w-0 truncate text-xs leading-tight font-medium text-foreground">351 <div className="min-w-0 truncate text-xs leading-tight font-medium text-foreground">
357 {thread.title}352 {thread.title}
358 </div>353 </div>
...@@ -373,9 +368,9 @@ function SidebarThreadTooltip({...@@ -373,9 +368,9 @@ function SidebarThreadTooltip({
373 </div>368 </div>
374 ) : null}369 ) : null}
375 {thread.branch ? (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 <GitBranchIcon className="size-3 shrink-0 stroke-muted-foreground" />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 </div>374 </div>
380 ) : null}375 ) : null}
381 {branchMismatch ? (376 {branchMismatch ? (
...@@ -1920,11 +1915,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1920,11 +1915,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1920 {thread.branch ? (1915 {thread.branch ? (
1921 <>1916 <>
1922 <ThreadWorktreeIndicator thread={thread} />1917 <ThreadWorktreeIndicator thread={thread} />
1923 <MiddleTruncate1918 <span className="flex min-w-0 flex-1 text-muted-foreground/40">
1924 value={thread.branch}1919 <MiddleTruncate value={thread.branch} showTitle={false} />
1925 showTitle={false}1920 </span>
1926 className="flex-1 text-muted-foreground/40"
1927 />
1928 </>1921 </>
1929 ) : (1922 ) : (
1930 <span className="flex-1" />1923 <span className="flex-1" />
...@@ -4544,7 +4537,7 @@ export default function Sidebar() {...@@ -4544,7 +4537,7 @@ export default function Sidebar() {
4544 </SidebarGroup>4537 </SidebarGroup>
4545 }4538 }
4546 >4539 >
4547 <SidebarGroup className="flex-1 ps-[calc(var(--sidebar-content-inset)+1px)]">4540 <SidebarGroup className="flex-1">
4548 {isSearchingThreads ? (4541 {isSearchingThreads ? (
4549 threadSearchResults.length > 0 ? (4542 threadSearchResults.length > 0 ? (
4550 <TooltipProvider4543 <TooltipProvider
apps/web/src/components/chat/ChangedFilesTree.tsx+3-4
...@@ -247,10 +247,9 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {...@@ -247,10 +247,9 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
247 theme={resolvedTheme}247 theme={resolvedTheme}
248 className="size-3.5 text-muted-foreground/70"248 className="size-3.5 text-muted-foreground/70"
249 />249 />
250 <MiddleTruncate250 <span className="flex min-w-0 font-mono text-xs text-foreground/85 group-hover:text-foreground">
251 value={node.name}251 <MiddleTruncate value={node.name} />
252 className="font-mono text-xs text-foreground/85 group-hover:text-foreground"252 </span>
253 />
254 {node.stat && (253 {node.stat && (
255 <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">254 <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
256 <DiffStatLabel additions={node.stat.additions} deletions={node.stat.deletions} />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,17 +279,17 @@ function Children({ className, render, ...props }: useRender.ComponentProps<"div
279}279}
280280
281/** Bounded banner content uses the app's scroll area and fades only overflowing edges. */281/** Bounded banner content uses the app's scroll area and fades only overflowing edges. */
282function Scroll({ className, ...props }: ComponentProps<typeof ScrollArea>) {282function Scroll({ className, children, ...props }: ComponentProps<typeof ScrollArea>) {
283 return (283 return (
284 <ScrollArea284 <ScrollArea
285 radius="none"285 radius="none"
286 scrollFade286 scrollFade
287 className={cn(287 className={cn("h-auto max-h-[min(24rem,40dvh)]", className)}
288 "h-auto max-h-[min(24rem,40dvh)] [&>[data-slot=scroll-area-viewport][data-has-overflow-y]]:pe-2",
289 className,
290 )}
291 {...props}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}
295295
apps/web/src/components/chat/ComposerBannerStack.tsx+1-1
...@@ -306,7 +306,7 @@ function NoticeDescription({ children, compact }: { children: ReactNode; compact...@@ -306,7 +306,7 @@ function NoticeDescription({ children, compact }: { children: ReactNode; compact
306 aria-label="Notice details"306 aria-label="Notice details"
307 tooltipStyle307 tooltipStyle
308 side="top"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 <ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]">311 <ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]">
312 {children}312 {children}
apps/web/src/components/chat/MessagesTimeline.tsx+1-7
...@@ -2562,13 +2562,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap...@@ -2562,13 +2562,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap
2562 <Spinner size="xs" className="shrink-0" />2562 <Spinner size="xs" className="shrink-0" />
2563 <span className="truncate">{scriptName}</span>2563 <span className="truncate">{scriptName}</span>
2564 </PopoverTrigger>2564 </PopoverTrigger>
2565 <PopoverPopup2565 <PopoverPopup side="bottom" align="end" width="lg" padding="compact">
2566 side="bottom"
2567 align="end"
2568 width="lg"
2569 className="surface-glass!"
2570 padding="compact"
2571 >
2572 <WorktreeSetupCard2566 <WorktreeSetupCard
2573 snapshot={snapshot}2567 snapshot={snapshot}
2574 embedded2568 embedded
apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx+2-2
...@@ -97,10 +97,10 @@ export function useAutoBalanceUpdateBanner(...@@ -97,10 +97,10 @@ export function useAutoBalanceUpdateBanner(
97 <Popover>97 <Popover>
98 <PopoverTrigger98 <PopoverTrigger
99 render={<InlineButton />}99 render={<InlineButton />}
100 className="block max-w-full truncate"100 className="max-w-full"
101 aria-label={`${title}. View machines`}101 aria-label={`${title}. View machines`}
102 >102 >
103 {title}103 <span className="min-w-0 truncate">{title}</span>
104 </PopoverTrigger>104 </PopoverTrigger>
105 <PopoverPopup side="top" align="start" width="md">105 <PopoverPopup side="top" align="start" width="md">
106 <div className="space-y-3 text-xs">106 <div className="space-y-3 text-xs">
apps/web/src/components/onboarding/WelcomeWizard.tsx+2-2
...@@ -584,8 +584,8 @@ function PairingForm({...@@ -584,8 +584,8 @@ function PairingForm({
584 {isPairing ? "Pairing..." : "Pair"}584 {isPairing ? "Pairing..." : "Pair"}
585 </Button>585 </Button>
586 </div>586 </div>
587 <CollapsiblePanel className="pt-3">587 <CollapsiblePanel>
588 <p className="text-sm text-muted-foreground">588 <p className="pt-3 text-sm text-muted-foreground">
589 Run this on the computer with your code.589 Run this on the computer with your code.
590 </p>590 </p>
591 <CommandBlock command="npx t3 pair" className="mt-2" />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,7 +431,7 @@ export function ProjectScriptEditorDialog({
431 </fieldset>431 </fieldset>
432 </form>432 </form>
433 </DialogPanel>433 </DialogPanel>
434 <DialogFooter className="dark:border-transparent dark:bg-transparent">434 <DialogFooter variant="bare">
435 {isEditing && (435 {isEditing && (
436 <Button436 <Button
437 type="button"437 type="button"
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+3-7
...@@ -2252,8 +2252,8 @@ export function PullRequestDetailPanel({...@@ -2252,8 +2252,8 @@ export function PullRequestDetailPanel({
2252 <PullRequestActorLabel2252 <PullRequestActorLabel
2253 actor={detail.author}2253 actor={detail.author}
2254 profileUrl={authorProfileUrl}2254 profileUrl={authorProfileUrl}
2255 className="shrink-0 rounded-full"2255 variant="avatar"
2256 labelClassName="sr-only"2256 className="shrink-0"
2257 />2257 />
2258 <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>2258 <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>
2259 </span>2259 </span>
...@@ -2428,11 +2428,7 @@ export function PullRequestDetailPanel({...@@ -2428,11 +2428,7 @@ export function PullRequestDetailPanel({
2428 )}2428 )}
2429 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">2429 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">
2430 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">2430 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">
2431 <PullRequestActorLabel2431 <PullRequestActorLabel actor={detail.author} profileUrl={authorProfileUrl} />
2432 actor={detail.author}
2433 profileUrl={authorProfileUrl}
2434 className="font-medium"
2435 />
2436 <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span>2432 <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span>
2437 </PullRequestMetaLine>2433 </PullRequestMetaLine>
2438 {checkoutCommand ? (2434 {checkoutCommand ? (
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-5
...@@ -216,11 +216,7 @@ export function PullRequestDetailGhost({...@@ -216,11 +216,7 @@ export function PullRequestDetailGhost({
216 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">216 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">
217 {seed ? (217 {seed ? (
218 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">218 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">
219 <PullRequestActorLabel219 <PullRequestActorLabel actor={seed.author ?? null} tooltip={false} />
220 actor={seed.author ?? null}
221 className="font-medium"
222 tooltip={false}
223 />
224 <span>updated {formatRelativeTimeLabel(seed.updatedAt)}</span>220 <span>updated {formatRelativeTimeLabel(seed.updatedAt)}</span>
225 </PullRequestMetaLine>221 </PullRequestMetaLine>
226 ) : (222 ) : (
apps/web/src/components/pullRequest/PullRequestLinkPreview.tsx+33-29
...@@ -96,37 +96,41 @@ export function PullRequestLinkPreview({...@@ -96,37 +96,41 @@ export function PullRequestLinkPreview({
96 <PreviewCard open={open} onOpenChange={setOpen}>96 <PreviewCard open={open} onOpenChange={setOpen}>
97 <PreviewCardTrigger render={trigger} delay={350} closeDelay={120} />97 <PreviewCardTrigger render={trigger} delay={350} closeDelay={120} />
98 {detail !== null || detailQuery.error !== null ? (98 {detail !== null || detailQuery.error !== null ? (
99 <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)] p-3">99 <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)]">
100 {detail === null ? (100 <div className="p-3">
101 (fallback ?? (101 {detail === null ? (
102 <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere">102 (fallback ?? (
103 {originalUrl}103 <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere">
104 </p>104 {originalUrl}
105 ))105 </p>
106 ) : (106 ))
107 <div className="min-w-0">107 ) : (
108 <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">108 <div className="min-w-0">
109 <span className="min-w-0 truncate">{detail.repository}</span>109 <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
110 <span className="shrink-0">#{detail.number}</span>110 <span className="min-w-0 truncate">{detail.repository}</span>
111 <span aria-hidden>·</span>111 <span className="shrink-0">#{detail.number}</span>
112 {state === null ? null : (112 <span aria-hidden>·</span>
113 <span className="inline-flex shrink-0 items-center gap-1">113 {state === null ? null : (
114 <state.Icon aria-hidden className={`size-3 ${state.toneClassName}`} />114 <span className="inline-flex shrink-0 items-center gap-1">
115 {state.label}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 </span>129 </span>
117 )}130 </div>
118 </div>131 </div>
119 <p className="mt-1 text-sm font-medium leading-snug text-foreground text-pretty">132 )}
120 {detail.title}133 </div>
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 )}
130 </PreviewCardPopup>134 </PreviewCardPopup>
131 ) : null}135 ) : null}
132 </PreviewCard>136 </PreviewCard>
apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx+1-1
...@@ -265,7 +265,7 @@ export function ReviewThreadCard({...@@ -265,7 +265,7 @@ export function ReviewThreadCard({
265 {comments.map((comment) => (265 {comments.map((comment) => (
266 <article key={comment.id} className="group min-w-0">266 <article key={comment.id} className="group min-w-0">
267 <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">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 <span>{formatRelativeTimeLabel(comment.createdAt)}</span>269 <span>{formatRelativeTimeLabel(comment.createdAt)}</span>
270 <PullRequestReactionBar270 <PullRequestReactionBar
271 className="ml-auto justify-end"271 className="ml-auto justify-end"
apps/web/src/components/pullRequest/PullRequestReviewerPicker.tsx+1-1
...@@ -120,7 +120,7 @@ export function PullRequestReviewerPicker({...@@ -120,7 +120,7 @@ export function PullRequestReviewerPicker({
120 >120 >
121 {(candidate) => (121 {(candidate) => (
122 <>122 <>
123 <PullRequestActorLabel actor={candidate} className="min-w-0 flex-1 truncate" />123 <PullRequestActorLabel actor={candidate} className="flex-1" />
124 {candidate.kind === "team" ? (124 {candidate.kind === "team" ? (
125 <span className="shrink-0 text-muted-foreground">team</span>125 <span className="shrink-0 text-muted-foreground">team</span>
126 ) : null}126 ) : null}
apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx+21-28
...@@ -78,11 +78,7 @@ function CommentIdentity({...@@ -78,11 +78,7 @@ function CommentIdentity({
78 : null;78 : null;
79 return (79 return (
80 <div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs">80 <div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs">
81 <PullRequestActorLabel81 <PullRequestActorLabel actor={actor} profileUrl={profileUrl} className="max-w-full" />
82 actor={actor}
83 profileUrl={profileUrl}
84 className="max-w-full font-medium text-foreground [&>img]:size-6 [&>span:first-child]:size-6"
85 />
86 <Tooltip>82 <Tooltip>
87 <TooltipTrigger83 <TooltipTrigger
88 render={84 render={
...@@ -383,22 +379,26 @@ function CommentGroup({...@@ -383,22 +379,26 @@ function CommentGroup({
383 <div className="flex items-center gap-3 pl-3">379 <div className="flex items-center gap-3 pl-3">
384 <div className="flex shrink-0 -space-x-1.5">380 <div className="flex shrink-0 -space-x-1.5">
385 {authors.slice(0, 3).map((actor) => (381 {authors.slice(0, 3).map((actor) => (
386 <PullRequestActorLabel382 // The ring separates the overlapping faces; it belongs to the stack, not the actor.
383 <span
387 key={actor?.login ?? "ghost"}384 key={actor?.login ?? "ghost"}
388 actor={actor}385 className="relative flex rounded-full ring-2 ring-background hover:z-10 focus-within:z-10"
389 profileUrl={386 >
390 detail.provider === "github" && actor387 <PullRequestActorLabel
391 ? new URL(388 actor={actor}
392 actor.isBot || actor.login.endsWith("[bot]")389 profileUrl={
393 ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}`390 detail.provider === "github" && actor
394 : `/${encodeURIComponent(actor.login)}`,391 ? new URL(
395 detail.url,392 actor.isBot || actor.login.endsWith("[bot]")
396 ).toString()393 ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}`
397 : null394 : `/${encodeURIComponent(actor.login)}`,
398 }395 detail.url,
399 labelClassName="sr-only"396 ).toString()
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"397 : null
401 />398 }
399 variant="avatar"
400 />
401 </span>
402 ))}402 ))}
403 {authors.length > 3 ? (403 {authors.length > 3 ? (
404 <span className="relative flex size-6 items-center justify-center rounded-full bg-muted text-[10px] text-muted-foreground ring-2 ring-background">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,14 +752,7 @@ export function PullRequestSummaryTab({
752 <PullRequestActorLabel752 <PullRequestActorLabel
753 actor={entry.actor}753 actor={entry.actor}
754 tooltip={false}754 tooltip={false}
755 className={cn(755 variant="avatar"
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 )}
763 />756 />
764 {/* Colour alone says nothing to a reader who cannot see it, and the757 {/* Colour alone says nothing to a reader who cannot see it, and the
765 login beside this is already in the accessible name. */}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,6 +23,7 @@ import { Children, type CSSProperties, isValidElement, type ReactNode, useState
23import { cn } from "~/lib/utils";23import { cn } from "~/lib/utils";
2424
25import { Badge } from "../ui/badge";25import { Badge } from "../ui/badge";
26import { InlineButton } from "../ui/button";
26import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";27import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
27import type { PullRequestReviewOutcome } from "./pullRequestDetail.logic";28import type { PullRequestReviewOutcome } from "./pullRequestDetail.logic";
28import { pullRequestLabelColor } from "./pullRequestList.logic";29import { pullRequestLabelColor } from "./pullRequestList.logic";
...@@ -445,51 +446,56 @@ export function PullRequestActorAvatar({...@@ -445,51 +446,56 @@ export function PullRequestActorAvatar({
445 );446 );
446}447}
447448
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 */
449export function PullRequestActorLabel({455export function PullRequestActorLabel({
450 actor,456 actor,
451 className,457 className,
452 labelClassName,458 variant = "label",
453 tooltip = true,459 tooltip = true,
454 profileUrl,460 profileUrl,
455}: {461}: {
456 actor: PullRequestActor | null;462 actor: PullRequestActor | null;
457 className?: string;463 className?: string;
458 labelClassName?: string;464 variant?: "label" | "avatar";
459 tooltip?: boolean;465 tooltip?: boolean;
460 profileUrl?: string | null;466 profileUrl?: string | null;
461}) {467}) {
462 const login = actor?.login ?? "ghost";468 const login = actor?.login ?? "ghost";
463 const label = (469 const label = (
464 <>470 <span className={cn("flex min-w-0 items-center", variant === "label" && "gap-1.5")}>
465 <PullRequestActorAvatar actor={actor} />471 <PullRequestActorAvatar actor={actor} />
466 <span className={cn("truncate", labelClassName)}>{login}</span>472 <span className={variant === "label" ? "truncate font-medium text-foreground" : "sr-only"}>
467 </>473 {login}
474 </span>
475 </span>
468 );476 );
469 if (!tooltip) {477 const placement = cn("flex min-w-0 shrink", className);
470 return <span className={cn("flex min-w-0 items-center gap-1.5", className)}>{label}</span>;478 if (!tooltip) return <span className={placement}>{label}</span>;
471 }
472 return (479 return (
473 <Tooltip>480 <Tooltip>
474 <TooltipTrigger481 <TooltipTrigger
475 render={482 render={
476 profileUrl ? (483 profileUrl ? (
477 <a484 <InlineButton
478 href={profileUrl}485 className={placement}
479 target="_blank"486 render={
480 rel="noopener noreferrer"487 <a
481 aria-label={`Open ${login}'s profile`}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 {label}500 {label}
495 </TooltipTrigger>501 </TooltipTrigger>
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-4
...@@ -67,14 +67,13 @@ function SearchOptionButton(props: {...@@ -67,14 +67,13 @@ function SearchOptionButton(props: {
67 <Toggle67 <Toggle
68 aria-label={props.label}68 aria-label={props.label}
69 pressed={props.active}69 pressed={props.active}
70 className="size-8 rounded-[5px] font-mono text-muted-foreground data-pressed:text-foreground sm:size-7"70 size="segmented"
71 size="compact"71 variant="segmented"
72 variant="ghost"
73 onClick={props.onClick}72 onClick={props.onClick}
74 />73 />
75 }74 }
76 >75 >
77 {props.children}76 <span className="font-mono">{props.children}</span>
78 </TooltipTrigger>77 </TooltipTrigger>
79 <TooltipPopup side="top">{props.label}</TooltipPopup>78 <TooltipPopup side="top">{props.label}</TooltipPopup>
80 </Tooltip>79 </Tooltip>
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+1-1
...@@ -251,7 +251,7 @@ export function AddProviderInstanceDialog({...@@ -251,7 +251,7 @@ export function AddProviderInstanceDialog({
251 value={driver}251 value={driver}
252 onValueChange={(value) => setDriver(ProviderDriverKind.make(value))}252 onValueChange={(value) => setDriver(ProviderDriverKind.make(value))}
253 aria-labelledby="add-instance-driver-label"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 {DRIVER_OPTIONS.map((option) => {256 {DRIVER_OPTIONS.map((option) => {
257 const IconComponent = option.icon;257 const IconComponent = option.icon;
apps/web/src/components/settings/DiagnosticsSettings.tsx+158-154
...@@ -344,84 +344,86 @@ function ProcessDiagnosticsTable({...@@ -344,84 +344,86 @@ function ProcessDiagnosticsTable({
344 }, []);344 }, []);
345345
346 return (346 return (
347 <ScrollArea347 <div className="border-t border-border/60">
348 radius="none"348 <ScrollArea
349 chainVerticalScroll349 radius="none"
350 scrollFade350 chainVerticalScroll
351 hideScrollbars351 scrollFade
352 className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60"352 hideScrollbars
353 >353 className="max-h-[min(64vh,44rem)] w-full max-w-full"
354 <table className="w-full min-w-[1040px] table-fixed text-left text-xs">354 >
355 <colgroup>355 <table className="w-full min-w-[1040px] table-fixed text-left text-xs">
356 <col className="w-[24%]" />356 <colgroup>
357 <col className="w-[8%]" />357 <col className="w-[24%]" />
358 <col className="w-[10%]" />358 <col className="w-[8%]" />
359 <col className="w-[33%]" />359 <col className="w-[10%]" />
360 <col className="w-[8%]" />360 <col className="w-[33%]" />
361 <col className="w-[11%]" />361 <col className="w-[8%]" />
362 <col className="w-[6%]" />362 <col className="w-[11%]" />
363 </colgroup>363 <col className="w-[6%]" />
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">364 </colgroup>
365 <tr>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">
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 ? (
377 <tr>366 <tr>
378 <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">367 <th className="px-4 py-2 font-semibold sm:pl-5">Name</th>
379 {emptyLabel ?? "No live descendant processes found."}368 <th className="px-3 py-2 text-right font-semibold">CPU</th>
380 </td>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 </tr>374 </tr>
382 ) : null}375 </thead>
383 {visibleProcesses.map((process) => (376 <tbody className="divide-y divide-border/50">
384 <tr key={process.pid} className="hover:bg-muted/20">377 {visibleProcesses.length === 0 ? (
385 <td className="px-4 py-2 align-middle sm:pl-5">378 <tr>
386 <ProcessNameCell379 <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
387 process={process}380 {emptyLabel ?? "No live descendant processes found."}
388 isExpanded={!collapsedPids.has(process.pid)}381 </td>
389 onToggle={toggleProcess}382 </tr>
390 />383 ) : null}
391 </td>384 {visibleProcesses.map((process) => (
392 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">385 <tr key={process.pid} className="hover:bg-muted/20">
393 {process.cpuPercent.toFixed(1)}%386 <td className="px-4 py-2 align-middle sm:pl-5">
394 </td>387 <ProcessNameCell
395 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">388 process={process}
396 {formatBytes(process.rssBytes)}389 isExpanded={!collapsedPids.has(process.pid)}
397 </td>390 onToggle={toggleProcess}
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>}
402 />391 />
403 <TooltipPopup side="top" variant="code">392 </td>
404 {process.command}393 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
405 </TooltipPopup>394 {process.cpuPercent.toFixed(1)}%
406 </Tooltip>395 </td>
407 </td>396 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
408 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground">397 {formatBytes(process.rssBytes)}
409 {process.pid}398 </td>
410 </td>399 <td className="px-3 py-2 align-middle text-muted-foreground">
411 <td className="truncate px-3 py-2 align-middle text-muted-foreground">400 <Tooltip>
412 {formatProcessType(process)}401 <TooltipTrigger
413 </td>402 render={<span className="block truncate">{process.command}</span>}
414 <td className="p-2 align-middle sm:pr-4">403 />
415 <ProcessSignalActions404 <TooltipPopup side="top" variant="code">
416 disabled={signalingPid === process.pid}405 {process.command}
417 onSignal={(signal) => onSignal(process.pid, signal)}406 </TooltipPopup>
418 />407 </Tooltip>
419 </td>408 </td>
420 </tr>409 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground">
421 ))}410 {process.pid}
422 </tbody>411 </td>
423 </table>412 <td className="truncate px-3 py-2 align-middle text-muted-foreground">
424 </ScrollArea>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}
427429
...@@ -568,88 +570,90 @@ function ProcessResourceHistoryTable({...@@ -568,88 +570,90 @@ function ProcessResourceHistoryTable({
568 }, null);570 }, null);
569571
570 return (572 return (
571 <ScrollArea573 <div className="border-t border-border/60">
572 chainVerticalScroll574 <ScrollArea
573 scrollFade575 chainVerticalScroll
574 hideScrollbars576 scrollFade
575 className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60"577 hideScrollbars
576 >578 className="max-h-[min(64vh,44rem)] w-full max-w-full"
577 <table className="w-full min-w-[980px] table-fixed text-left text-xs">579 >
578 <colgroup>580 <table className="w-full min-w-[980px] table-fixed text-left text-xs">
579 <col className="w-[24%]" />581 <colgroup>
580 <col className="w-[10%]" />582 <col className="w-[24%]" />
581 <col className="w-[10%]" />583 <col className="w-[10%]" />
582 <col className="w-[10%]" />584 <col className="w-[10%]" />
583 <col className="w-[10%]" />585 <col className="w-[10%]" />
584 <col className="w-[10%]" />586 <col className="w-[10%]" />
585 <col className="w-[16%]" />587 <col className="w-[10%]" />
586 <col className="w-[10%]" />588 <col className="w-[16%]" />
587 </colgroup>589 <col className="w-[10%]" />
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">590 </colgroup>
589 <tr>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">
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 ? (
602 <tr>592 <tr>
603 <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">593 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
604 {emptyLabel}594 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
605 </td>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 </tr>601 </tr>
607 ) : null}602 </thead>
608 {processes.map((process) => (603 <tbody className="divide-y divide-border/50">
609 <tr key={process.processKey} className="hover:bg-muted/20">604 {processes.length === 0 ? (
610 <td className="px-4 py-2 align-middle sm:pl-5">605 <tr>
611 <ResourceHistoryProcessNameCell606 <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
612 process={process}607 {emptyLabel}
613 visualDepth={608 </td>
614 process.isServerRoot || shallowestChildDepth === null609 </tr>
615 ? 0610 ) : null}
616 : Math.max(1, process.depth - shallowestChildDepth + 1)611 {processes.map((process) => (
617 }612 <tr key={process.processKey} className="hover:bg-muted/20">
618 />613 <td className="px-4 py-2 align-middle sm:pl-5">
619 </td>614 <ResourceHistoryProcessNameCell
620 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">615 process={process}
621 {formatCpuTime(process.cpuSecondsApprox)}616 visualDepth={
622 </td>617 process.isServerRoot || shallowestChildDepth === null
623 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">618 ? 0
624 {process.currentCpuPercent.toFixed(1)}%619 : Math.max(1, process.depth - shallowestChildDepth + 1)
625 </td>620 }
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>}
639 />621 />
640 <TooltipPopup side="top" variant="code">622 </td>
641 {process.command}623 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
642 </TooltipPopup>624 {formatCpuTime(process.cpuSecondsApprox)}
643 </Tooltip>625 </td>
644 </td>626 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
645 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground sm:pr-5">627 {process.currentCpuPercent.toFixed(1)}%
646 {process.pid}628 </td>
647 </td>629 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
648 </tr>630 {process.avgCpuPercent.toFixed(1)}%
649 ))}631 </td>
650 </tbody>632 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
651 </table>633 {process.maxCpuPercent.toFixed(1)}%
652 </ScrollArea>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}
655659
apps/web/src/components/settings/IntegrationsSettings.tsx+4-7
...@@ -1278,14 +1278,11 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {...@@ -1278,14 +1278,11 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
1278 onCommit={(next) => renameProfile(profile.id, next)}1278 onCommit={(next) => renameProfile(profile.id, next)}
1279 />1279 />
1280 )}1280 )}
1281 {/*1281 {/* Dimmed with the rest of the row, whose controls are all disabled. */}
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 */}
1287 {isDefault ? (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 ) : null}1286 ) : null}
1290 </span>1287 </span>
1291 <Menu>1288 <Menu>
apps/web/src/components/settings/ProviderAccentColorPicker.tsx+1-6
...@@ -90,12 +90,7 @@ function ProviderCustomColorPicker(props: {...@@ -90,12 +90,7 @@ function ProviderCustomColorPicker(props: {
90 </button>90 </button>
91 }91 }
92 />92 />
93 <PopoverPopup93 <PopoverPopup side="bottom" align="start" sideOffset={6} padding="none">
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 >
99 <ProviderCustomColorPanel value={normalized} onCommit={props.onCommit} />94 <ProviderCustomColorPanel value={normalized} onCommit={props.onCommit} />
100 <PopoverClose95 <PopoverClose
101 render={96 render={
apps/web/src/components/settings/ProviderSettingsPanel.tsx+1-1
...@@ -353,7 +353,7 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) {...@@ -353,7 +353,7 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) {
353 <Tooltip key={environment.environmentId}>353 <Tooltip key={environment.environmentId}>
354 <TooltipTrigger354 <TooltipTrigger
355 render={355 render={
356 <Toggle value={environment.environmentId} className="gap-2 text-left">356 <Toggle value={environment.environmentId}>
357 <EnvironmentMachineIcon357 <EnvironmentMachineIcon
358 kind={machine}358 kind={machine}
359 className="size-3.5 shrink-0"359 className="size-3.5 shrink-0"
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+185-179
...@@ -455,11 +455,13 @@ function ResourceHistoryChart({...@@ -455,11 +455,13 @@ function ResourceHistoryChart({
455 </div>455 </div>
456 }456 }
457 />457 />
458 <TooltipPopup side="top" className="space-y-0.5 text-left">458 <TooltipPopup side="top" className="text-left">
459 <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div>459 <div className="space-y-0.5">
460 <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div>460 <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div>
461 <div>Read {formatBytes(bucket.ioReadBytes)}</div>461 <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div>
462 <div>Write {formatBytes(bucket.ioWriteBytes)}</div>462 <div>Read {formatBytes(bucket.ioReadBytes)}</div>
463 <div>Write {formatBytes(bucket.ioWriteBytes)}</div>
464 </div>
463 </TooltipPopup>465 </TooltipPopup>
464 </Tooltip>466 </Tooltip>
465 );467 );
...@@ -565,100 +567,102 @@ function ProcessTable({...@@ -565,100 +567,102 @@ function ProcessTable({
565 }, []);567 }, []);
566568
567 return (569 return (
568 <ScrollArea570 <div className="border-t border-border/60">
569 chainVerticalScroll571 <ScrollArea
570 scrollFade572 chainVerticalScroll
571 hideScrollbars573 scrollFade
572 className="max-h-[min(68vh,48rem)] w-full max-w-full border-t border-border/60"574 hideScrollbars
573 >575 className="max-h-[min(68vh,48rem)] w-full max-w-full"
574 <table className="w-full min-w-[1320px] table-fixed text-left text-xs">576 >
575 <colgroup>577 <table className="w-full min-w-[1320px] table-fixed text-left text-xs">
576 <col className="w-[20%]" />578 <colgroup>
577 <col className="w-[10%]" />579 <col className="w-[20%]" />
578 <col className="w-[7%]" />580 <col className="w-[10%]" />
579 <col className="w-[8%]" />581 <col className="w-[7%]" />
580 <col className="w-[9%]" />582 <col className="w-[8%]" />
581 <col className="w-[9%]" />583 <col className="w-[9%]" />
582 <col className="w-[9%]" />584 <col className="w-[9%]" />
583 <col className="w-[10%]" />585 <col className="w-[9%]" />
584 <col className="w-[8%]" />586 <col className="w-[10%]" />
585 <col className="w-[6%]" />587 <col className="w-[8%]" />
586 <col className="w-[4%]" />588 <col className="w-[6%]" />
587 </colgroup>589 <col className="w-[4%]" />
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">590 </colgroup>
589 <tr>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">
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 ? (
605 <tr>592 <tr>
606 <td colSpan={11} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">593 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
607 Waiting for the native process monitor.594 <th className="px-3 py-2 font-semibold">Category</th>
608 </td>595 <th className="px-3 py-2 text-right font-semibold">CPU</th>
609 </tr>596 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
610 ) : null}597 <th className="px-3 py-2 text-right font-semibold">Memory</th>
611 {visible.map((process) => (598 <th className="px-3 py-2 text-right font-semibold">Read/s</th>
612 <tr key={processIdentityKey(process)} className="hover:bg-muted/20">599 <th className="px-3 py-2 text-right font-semibold">Write/s</th>
613 <td className="px-4 py-2 sm:pl-5">600 <th className="px-3 py-2 text-right font-semibold">Read Total</th>
614 <ProcessTreeName601 <th className="px-3 py-2 text-right font-semibold">Write Total</th>
615 process={process}602 <th className="px-3 py-2 text-right font-semibold">PID</th>
616 collapsed={collapsed.has(processIdentityKey(process))}603 <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th>
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>
657 </tr>604 </tr>
658 ))}605 </thead>
659 </tbody>606 <tbody className="divide-y divide-border/50">
660 </table>607 {visible.length === 0 ? (
661 </ScrollArea>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}
664668
...@@ -668,90 +672,92 @@ function HistoryProcessTable({...@@ -668,90 +672,92 @@ function HistoryProcessTable({
668 processes: ReadonlyArray<ResourceTelemetryProcessSummary>;672 processes: ReadonlyArray<ResourceTelemetryProcessSummary>;
669}) {673}) {
670 return (674 return (
671 <ScrollArea675 <div className="border-t border-border/60">
672 chainVerticalScroll676 <ScrollArea
673 scrollFade677 chainVerticalScroll
674 hideScrollbars678 scrollFade
675 className="max-h-[28rem] w-full max-w-full border-t border-border/60"679 hideScrollbars
676 >680 className="max-h-[28rem] w-full max-w-full"
677 <table className="w-full min-w-[1020px] table-fixed text-left text-xs">681 >
678 <colgroup>682 <table className="w-full min-w-[1020px] table-fixed text-left text-xs">
679 <col className="w-[24%]" />683 <colgroup>
680 <col className="w-[11%]" />684 <col className="w-[24%]" />
681 <col className="w-[10%]" />685 <col className="w-[11%]" />
682 <col className="w-[10%]" />686 <col className="w-[10%]" />
683 <col className="w-[11%]" />687 <col className="w-[10%]" />
684 <col className="w-[11%]" />688 <col className="w-[11%]" />
685 <col className="w-[11%]" />689 <col className="w-[11%]" />
686 <col className="w-[7%]" />690 <col className="w-[11%]" />
687 <col className="w-[5%]" />691 <col className="w-[7%]" />
688 </colgroup>692 <col className="w-[5%]" />
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">693 </colgroup>
690 <tr>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">
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 ? (
704 <tr>695 <tr>
705 <td colSpan={9} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">696 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
706 No retained process samples in this window.697 <th className="px-3 py-2 font-semibold">Category</th>
707 </td>698 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
708 </tr>699 <th className="px-3 py-2 text-right font-semibold">Peak CPU</th>
709 ) : null}700 <th className="px-3 py-2 text-right font-semibold">Peak Mem</th>
710 {processes.map((process) => (701 <th className="px-3 py-2 text-right font-semibold">Read</th>
711 <tr key={processSummaryIdentityKey(process)} className="hover:bg-muted/20">702 <th className="px-3 py-2 text-right font-semibold">Write</th>
712 <td className="px-4 py-2 sm:pl-5">703 <th className="px-3 py-2 text-right font-semibold">Samples</th>
713 <Tooltip>704 <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th>
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>
750 </tr>705 </tr>
751 ))}706 </thead>
752 </tbody>707 <tbody className="divide-y divide-border/50">
753 </table>708 {processes.length === 0 ? (
754 </ScrollArea>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}
757763
apps/web/src/components/settings/SettingsScopeNotice.tsx+1-1
...@@ -66,7 +66,7 @@ export function SettingsScopeNotice({...@@ -66,7 +66,7 @@ export function SettingsScopeNotice({
66 <Alert role="status">66 <Alert role="status">
67 <AlertDescription>67 <AlertDescription>
68 <p>{children}</p>68 <p>{children}</p>
69 <AlertAction className="flex-wrap gap-2">69 <AlertAction className="flex-wrap">
70 {choices.map((choice) => (70 {choices.map((choice) => (
71 <Button71 <Button
72 key={JSON.stringify(choice.search)}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,113 +234,117 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
234 return (234 return (
235 <>235 <>
236 <SidebarContent className="overflow-x-hidden">236 <SidebarContent className="overflow-x-hidden">
237 <SidebarGroup className="gap-2">237 <SidebarGroup>
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">238 <div className="flex flex-col gap-2">
239 <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" />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 <SidebarInput240 <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" />
241 ref={searchInputRef}241 <SidebarInput
242 nativeInput242 ref={searchInputRef}
243 type="search"243 nativeInput
244 value={query}244 type="search"
245 onChange={(event) => {245 value={query}
246 setQuery(event.currentTarget.value);246 onChange={(event) => {
247 setActiveResultIndex(0);247 setQuery(event.currentTarget.value);
248 }}248 setActiveResultIndex(0);
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();
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" />287 No settings found
276 </Button>288 </p>
277 ) : (289 ) : null}
278 <Kbd>/</Kbd>290 {isSearching ? (
279 )}291 <SidebarMenu
280 </div>292 id={hasResults ? "settings-search-results" : undefined}
281 {isSearching && results.length === 0 ? (293 role={hasResults ? "listbox" : undefined}
282 <p294 aria-label={hasResults ? "Settings search results" : undefined}
283 role="status"295 >
284 className="px-2 py-6 text-center text-xs text-sidebar-muted-foreground"296 {results.map((item, index) => (
285 >297 <SidebarMenuItem key={item.id} role="presentation">
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}>
332 <SidebarMenuButton298 <SidebarMenuButton
333 isActive={isActive}299 id={`settings-search-result-${item.id}`}
334 onClick={() => handleSectionClick(item.to)}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 />309 <SettingsSectionIcon to={item.to} />
337 <span className="truncate">{item.label}</span>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 </SidebarMenuButton>318 </SidebarMenuButton>
339 </SidebarMenuItem>319 </SidebarMenuItem>
340 );320 ))}
341 })}321 </SidebarMenu>
342 </SidebarMenu>322 ) : (
343 )}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 </SidebarGroup>348 </SidebarGroup>
345 </SidebarContent>349 </SidebarContent>
346 <SidebarFooter>350 <SidebarFooter>
apps/web/src/components/settings/ThemeColorPicker.tsx+2-2
...@@ -284,7 +284,7 @@ function ThemeColorPicker({...@@ -284,7 +284,7 @@ function ThemeColorPicker({
284 </Tooltip>284 </Tooltip>
285 <PopoverPopup285 <PopoverPopup
286 align="end"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 data-theme-editor-panel=""288 data-theme-editor-panel=""
289 side="bottom"289 side="bottom"
290 sideOffset={10}290 sideOffset={10}
...@@ -353,7 +353,7 @@ export const ThemeColorField = memo(function ThemeColorField({...@@ -353,7 +353,7 @@ export const ThemeColorField = memo(function ThemeColorField({
353 <Input353 <Input
354 aria-invalid={!isColorValue}354 aria-invalid={!isColorValue}
355 aria-label={`${label} hex value`}355 aria-label={`${label} hex value`}
356 className="w-28 shrink-0 [&_[data-slot=input]]:text-right"356 className="w-28 shrink-0"
357 font="mono"357 font="mono"
358 id={`${role}-hex`}358 id={`${role}-hex`}
359 nativeInput359 nativeInput
apps/web/src/components/settings/ThemeEditorPanel.tsx+1-5
...@@ -947,11 +947,7 @@ export function ThemeEditorPanel({...@@ -947,11 +947,7 @@ export function ThemeEditorPanel({
947 // A locked mode stays hoverable so the tooltip can say why it is off;947 // A locked mode stays hoverable so the tooltip can say why it is off;
948 // a real disabled attribute would swallow the pointer events.948 // a real disabled attribute would swallow the pointer events.
949 const button = (949 const button = (
950 <Toggle950 <Toggle aria-disabled={lockReason !== null} value={appearance}>
951 aria-disabled={lockReason !== null}
952 value={appearance}
953 className={lockReason !== null ? "opacity-50" : undefined}
954 >
955 {appearance === "light" ? "Light" : "Dark"}951 {appearance === "light" ? "Light" : "Dark"}
956 </Toggle>952 </Toggle>
957 );953 );
apps/web/src/components/sidebar/SidebarChrome.tsx+7-7
...@@ -17,7 +17,6 @@ import {...@@ -17,7 +17,6 @@ import {
17import { Badge } from "../ui/badge";17import { Badge } from "../ui/badge";
18import {18import {
19 SidebarFooter,19 SidebarFooter,
20 SidebarHeader,
21 SidebarMenu,20 SidebarMenu,
22 SidebarMenuButton,21 SidebarMenuButton,
23 SidebarMenuItem,22 SidebarMenuItem,
...@@ -48,18 +47,19 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({...@@ -48,18 +47,19 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
48 : null;47 : null;
4948
50 return (49 return (
51 <SidebarHeader50 // The titlebar row, not a padded SidebarHeader: it aligns to the window controls.
51 <div
52 className={cn(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 isElectron && "drag-region",54 isElectron && "drag-region",
55 )}55 )}
56 >56 >
57 {backdropVariant ? <SidebarStageBackdrop variant={backdropVariant} /> : null}57 {backdropVariant ? <SidebarStageBackdrop variant={backdropVariant} /> : null}
58 <SidebarTrigger58 <SidebarTrigger
59 // Over the stage artwork: the media viewer's control-on-imagery treatment.
60 variant={backdropVariant ? "media-navigation" : "ghost"}
59 className={cn(61 className={cn(
60 "relative z-10 md:hidden",62 "relative top-auto z-10 translate-y-0 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",
63 backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant),63 backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant),
64 )}64 )}
65 />65 />
...@@ -74,7 +74,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({...@@ -74,7 +74,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
74 {pillLabel}74 {pillLabel}
75 </Badge>75 </Badge>
76 ) : null}76 ) : null}
77 </SidebarHeader>77 </div>
78 );78 );
79});79});
8080
apps/web/src/components/sidebar/SidebarThreadHeader.tsx+1-4
...@@ -187,10 +187,7 @@ export function SidebarHeaderIconButton({...@@ -187,10 +187,7 @@ export function SidebarHeaderIconButton({
187 type="button"187 type="button"
188 aria-label={label}188 aria-label={label}
189 {...rest}189 {...rest}
190 className={cn(190 className={cn("relative size-7 shrink-0", className)}
191 "relative size-7 shrink-0 focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar",
192 className,
193 )}
194 />191 />
195 }192 }
196 >193 >
apps/web/src/components/sidebar/SidebarUpdatePill.tsx-1
...@@ -393,7 +393,6 @@ function SidebarUpdateControl() {...@@ -393,7 +393,6 @@ function SidebarUpdateControl() {
393 <PopoverPopup393 <PopoverPopup
394 align="center"394 align="center"
395 aria-label="Nightly update release notes"395 aria-label="Nightly update release notes"
396 className="text-balance shadow-xl shadow-black/25"
397 initialFocus={false}396 initialFocus={false}
398 onKeyDownCapture={(event) => {397 onKeyDownCapture={(event) => {
399 if (398 if (
apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx+2-2
...@@ -85,7 +85,7 @@ export function SidebarUpdateReleaseNotes({...@@ -85,7 +85,7 @@ export function SidebarUpdateReleaseNotes({
8585
86 return (86 return (
87 <div key={releaseNote.version}>87 <div key={releaseNote.version}>
88 {index > 0 && <Separator className="my-3 bg-border/60" />}88 {index > 0 && <Separator className="my-3" />}
89 <section>89 <section>
90 <h3 className="text-foreground text-xs leading-4 font-semibold">90 <h3 className="text-foreground text-xs leading-4 font-semibold">
91 {index === 0 ? "What's changed" : `Changes in ${releaseNote.version}`}91 {index === 0 ? "What's changed" : `Changes in ${releaseNote.version}`}
...@@ -108,7 +108,7 @@ export function SidebarUpdateReleaseNotes({...@@ -108,7 +108,7 @@ export function SidebarUpdateReleaseNotes({
108 })}108 })}
109 {state.omittedReleaseCount > 0 ? (109 {state.omittedReleaseCount > 0 ? (
110 <div>110 <div>
111 <Separator className="my-3 bg-border/60" />111 <Separator className="my-3" />
112 <ReleaseLink releaseUrl={getDesktopUpdateReleaseHistoryUrl()} shell={shell}>112 <ReleaseLink releaseUrl={getDesktopUpdateReleaseHistoryUrl()} shell={shell}>
113 {`${state.omittedReleaseCount} older ${state.omittedReleaseCount === 1 ? "release" : "releases"} on GitHub`}113 {`${state.omittedReleaseCount} older ${state.omittedReleaseCount === 1 ? "release" : "releases"} on GitHub`}
114 </ReleaseLink>114 </ReleaseLink>
apps/web/src/components/ui/toggle.tsx+1-1
...@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";...@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
6import { cn } from "~/lib/utils";6import { cn } from "~/lib/utils";
77
8const toggleVariants = cva(8const 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 defaultVariants: {11 defaultVariants: {
12 size: "default",12 size: "default",
apps/web/src/components/usage/UsageLimitsPooled.tsx+2-1
...@@ -345,7 +345,8 @@ function LegendRow({...@@ -345,7 +345,8 @@ function LegendRow({
345 return (345 return (
346 <PopoverTrigger346 <PopoverTrigger
347 style={{ gridColumn: "1 / -1", gridRow: index + 1 }}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 <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 <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 <span352 <span
apps/web/src/components/usage/UsagePriceOverrides.tsx+18-21
...@@ -323,12 +323,12 @@ export function UsagePriceOverrides({...@@ -323,12 +323,12 @@ export function UsagePriceOverrides({
323 <col className="w-10" />323 <col className="w-10" />
324 </colgroup>324 </colgroup>
325 <TableHeader>325 <TableHeader>
326 <TableRow className="hover:bg-transparent">326 <TableRow>
327 <TableHead className="pl-3">Model ID</TableHead>327 <TableHead>Model ID</TableHead>
328 {USAGE_PRICE_FIELDS.map((field) => (328 {USAGE_PRICE_FIELDS.map((field) => (
329 <TableHead key={field.key}>{field.label}</TableHead>329 <TableHead key={field.key}>{field.label}</TableHead>
330 ))}330 ))}
331 <TableHead className="px-1">331 <TableHead>
332 <Button332 <Button
333 size="icon-xs"333 size="icon-xs"
334 variant="ghost"334 variant="ghost"
...@@ -347,24 +347,19 @@ export function UsagePriceOverrides({...@@ -347,24 +347,19 @@ export function UsagePriceOverrides({
347 </TableHeader>347 </TableHeader>
348 <TableBody>348 <TableBody>
349 {rows.length === 0 ? (349 {rows.length === 0 ? (
350 <TableRow className="hover:bg-transparent">350 <TableRow>
351 <TableCell351 <TableCell colSpan={6} className="text-center whitespace-normal">
352 colSpan={6}352 <p className="py-6 text-muted-foreground">
353 className="py-8 text-center whitespace-normal text-muted-foreground"353 {selected.some((environment) => environment.prices === null)
354 >354 ? "Some environment prices are unavailable."
355 {selected.some((environment) => environment.prices === null)355 : "No custom prices. Add a row to override automatic pricing."}
356 ? "Some environment prices are unavailable."356 </p>
357 : "No custom prices. Add a row to override automatic pricing."}
358 </TableCell>357 </TableCell>
359 </TableRow>358 </TableRow>
360 ) : (359 ) : (
361 rows.map((row) => (360 rows.map((row) => (
362 <TableRow361 <TableRow key={row.id} data-row-id={row.id}>
363 key={row.id}362 <TableCell className="whitespace-normal">
364 data-row-id={row.id}
365 className="hover:bg-transparent"
366 >
367 <TableCell className="pl-3 whitespace-normal">
368 {row.isNew ? (363 {row.isNew ? (
369 <Input364 <Input
370 size="compact"365 size="compact"
...@@ -407,14 +402,16 @@ export function UsagePriceOverrides({...@@ -407,14 +402,16 @@ export function UsagePriceOverrides({
407 ) : null}402 ) : null}
408 </TableCell>403 </TableCell>
409 {row.removed ? (404 {row.removed ? (
410 <TableCell colSpan={4} className="text-muted-foreground">405 <TableCell colSpan={4}>
411 Automatic pricing after saving406 <span className="text-muted-foreground">
407 Automatic pricing after saving
408 </span>
412 </TableCell>409 </TableCell>
413 ) : (410 ) : (
414 USAGE_PRICE_FIELDS.map((field) => {411 USAGE_PRICE_FIELDS.map((field) => {
415 const cell = usagePriceCell(selected, row.model, field.key);412 const cell = usagePriceCell(selected, row.model, field.key);
416 return (413 return (
417 <TableCell key={field.key} className="px-1">414 <TableCell key={field.key}>
418 <Input415 <Input
419 size="compact"416 size="compact"
420 inputMode="decimal"417 inputMode="decimal"
...@@ -438,7 +435,7 @@ export function UsagePriceOverrides({...@@ -438,7 +435,7 @@ export function UsagePriceOverrides({
438 );435 );
439 })436 })
440 )}437 )}
441 <TableCell className="px-1">438 <TableCell>
442 <Tooltip>439 <Tooltip>
443 <TooltipTrigger440 <TooltipTrigger
444 render={<Button size="icon-xs" variant="ghost" />}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,7 +218,7 @@ function PullRequestGroupHeader({
218 <Icon aria-hidden className="size-3.5 shrink-0" />218 <Icon aria-hidden className="size-3.5 shrink-0" />
219 <h2 className="shrink-0">{group.label}</h2>219 <h2 className="shrink-0">{group.label}</h2>
220 <span className="shrink-0 tabular-nums text-muted-foreground/50">{group.entries.length}</span>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 </div>222 </div>
223 );223 );
224}224}
scripts/lint-restyle-ceiling.ts+1-1
...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
10// migrate a file, and delete this script when the rule becomes an error.10// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 402;11export const RESTYLE_CEILING = 250;
1212
13const RULE = "shadcn(no-restyle)";13const RULE = "shadcn(no-restyle)";
1414