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() {
125125 <TooltipTrigger
126126 render={
127127 <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"}
128131 className={cn(
129132 "pointer-events-auto",
130 isSidebarVisible &&
131 stageBackdropVariant &&
132 "focus-visible:ring-white/90 [&_svg]:stroke-white/90! [&_svg]:opacity-100! [&_svg]:hover:stroke-white! [:hover,[data-pressed]]:bg-white/15",
133 isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0",
133134 isSidebarVisible &&
134135 stageBackdropVariant &&
135136 resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),
......@@ -244,7 +245,6 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {
244245 side="left"
245246 collapsible="offcanvas"
246247 data-app-sidebar=""
247 className="border-r border-sidebar-border"
248248 resizable={{
249249 maxWidth: sidebarMaximumWidth,
250250 minWidth: THREAD_SIDEBAR_MIN_WIDTH,
apps/web/src/components/BranchToolbarBranchSelector.tsx+4-3
......@@ -817,11 +817,12 @@ export function BranchToolbarBranchSelector({
817817 data-composer-label
818818 className="min-w-0 max-w-[240px] group-data-[compact]/composer-context:max-w-0"
819819 >
820 <MiddleTruncate
821 value={triggerLabel}
820 <span
822821 data-composer-label-motion
823822 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>
825826 </span>
826827 <ChevronDownIcon className="size-3 shrink-0 opacity-50" />
827828 </ComboboxTrigger>
apps/web/src/components/CustomSnoozeDialog.tsx+3-9
......@@ -93,7 +93,7 @@ function CustomSnoozeDialog() {
9393 <DialogTitle>Custom snooze</DialogTitle>
9494 <DialogDescription>Choose when snoozed threads return to your inbox.</DialogDescription>
9595 </DialogHeader>
96 <DialogPanel className="text-base sm:text-sm">
96 <DialogPanel>
9797 <div className="flex flex-col gap-4">
9898 <ToggleGroup
9999 aria-label="Schedule type"
......@@ -147,10 +147,7 @@ function CustomSnoozeDialog() {
147147 </PopoverPopup>
148148 </Popover>
149149 </div>
150 <Label
151 className="flex min-w-0 flex-col items-stretch gap-1.5"
152 htmlFor={`${id}-time`}
153 >
150 <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-time`}>
154151 Time
155152 <Input
156153 nativeInput
......@@ -185,10 +182,7 @@ function CustomSnoozeDialog() {
185182 <NumberFieldIncrement aria-label="Increase duration" />
186183 </NumberFieldGroup>
187184 </NumberField>
188 <Label
189 className="flex min-w-0 flex-col items-stretch gap-1.5"
190 htmlFor={`${id}-unit`}
191 >
185 <Label className="flex min-w-0 flex-col items-stretch" htmlFor={`${id}-unit`}>
192186 Unit
193187 <Select
194188 value={unit}
apps/web/src/components/GitActionsControl.tsx+59-53
......@@ -627,7 +627,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
627627 setPublishRepositoryOverride(null);
628628 }}
629629 aria-labelledby="publish-provider-cards-label"
630 className="grid grid-cols-2 gap-2.5"
630 className="grid grid-cols-2"
631631 >
632632 {sortedPublishProviderOptions.map((option) => {
633633 const readiness = publishProviderReadiness[option.value];
......@@ -737,7 +737,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
737737 }
738738 aria-labelledby="publish-visibility-cards-label"
739739 disabled={publishRepositoryAction.isPending}
740 className="grid grid-cols-2 gap-2.5"
740 className="grid grid-cols-2"
741741 >
742742 {[
743743 {
......@@ -1915,57 +1915,63 @@ export default function GitActionsControl({
19151915 <p className="font-medium">none</p>
19161916 ) : (
19171917 <div className="space-y-2">
1918 <ScrollArea className="h-44 rounded-lg bg-card ring-1 ring-black/5 dark:bg-white/[0.025] dark:ring-white/5">
1919 <div className="space-y-1 p-1">
1920 {allFiles.map((file) => {
1921 const isExcluded = excludedFiles.has(file.path);
1922 return (
1923 <div
1924 key={file.path}
1925 className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50"
1926 >
1927 {isEditingFiles && (
1928 <Checkbox
1929 checked={!excludedFiles.has(file.path)}
1930 onCheckedChange={() => {
1931 setExcludedFiles((prev) => {
1932 const next = new Set(prev);
1933 if (next.has(file.path)) {
1934 next.delete(file.path);
1935 } else {
1936 next.add(file.path);
1937 }
1938 return next;
1939 });
1940 }}
1941 />
1942 )}
1943 <button
1944 type="button"
1945 className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left"
1946 onClick={() => openChangedFileInEditor(file.path)}
1918 <div className="h-44 rounded-lg bg-card ring-1 ring-black/5 dark:bg-white/[0.025] dark:ring-white/5">
1919 <ScrollArea>
1920 <div className="space-y-1 p-1">
1921 {allFiles.map((file) => {
1922 const isExcluded = excludedFiles.has(file.path);
1923 return (
1924 <div
1925 key={file.path}
1926 className="flex w-full items-center gap-2 rounded-md px-2 py-1 font-mono hover:bg-accent/50"
19471927 >
1948 <StartTruncatedPath
1949 path={file.path}
1950 className={`flex-1${isExcluded ? " text-muted-foreground" : ""}`}
1951 />
1952 <span className="shrink-0">
1953 {isExcluded ? (
1954 <span className="text-muted-foreground">Excluded</span>
1955 ) : (
1956 <>
1957 <span className="text-diff-addition">+{file.insertions}</span>
1958 <span className="text-muted-foreground"> / </span>
1959 <span className="text-diff-deletion">-{file.deletions}</span>
1960 </>
1961 )}
1962 </span>
1963 </button>
1964 </div>
1965 );
1966 })}
1967 </div>
1968 </ScrollArea>
1928 {isEditingFiles && (
1929 <Checkbox
1930 checked={!excludedFiles.has(file.path)}
1931 onCheckedChange={() => {
1932 setExcludedFiles((prev) => {
1933 const next = new Set(prev);
1934 if (next.has(file.path)) {
1935 next.delete(file.path);
1936 } else {
1937 next.add(file.path);
1938 }
1939 return next;
1940 });
1941 }}
1942 />
1943 )}
1944 <button
1945 type="button"
1946 className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left"
1947 onClick={() => openChangedFileInEditor(file.path)}
1948 >
1949 <StartTruncatedPath
1950 path={file.path}
1951 className={`flex-1${isExcluded ? " text-muted-foreground" : ""}`}
1952 />
1953 <span className="shrink-0">
1954 {isExcluded ? (
1955 <span className="text-muted-foreground">Excluded</span>
1956 ) : (
1957 <>
1958 <span className="text-diff-addition">
1959 +{file.insertions}
1960 </span>
1961 <span className="text-muted-foreground"> / </span>
1962 <span className="text-diff-deletion">
1963 -{file.deletions}
1964 </span>
1965 </>
1966 )}
1967 </span>
1968 </button>
1969 </div>
1970 );
1971 })}
1972 </div>
1973 </ScrollArea>
1974 </div>
19691975 <div className="flex justify-end font-mono">
19701976 <span className="text-diff-addition">
19711977 +{selectedFiles.reduce((sum, f) => sum + f.insertions, 0)}
......@@ -2040,7 +2046,7 @@ export default function GitActionsControl({
20402046 </DialogTitle>
20412047 <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription>
20422048 </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">
20442050 <Button
20452051 className="w-full sm:mr-auto sm:w-auto"
20462052 variant="outline"
apps/web/src/components/LegacySidebar.tsx+7-11
......@@ -1038,7 +1038,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
10381038 return (
10391039 <SidebarMenuSub
10401040 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"
10421042 >
10431043 {shouldShowThreadPanel && showEmptyThreadState ? (
10441044 <SidebarMenuSubItem className="w-full" data-thread-selection-safe>
......@@ -2357,9 +2357,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
23572357 <div className="group/project-header relative">
23582358 <SidebarMenuButton
23592359 ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined}
2360 className={`pr-8 group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${
2361 isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : ""
2362 }`}
2360 className={isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : undefined}
23632361 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})}
23642362 {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.listeners : {})}
23652363 onPointerDownCapture={handleProjectButtonPointerDownCapture}
......@@ -2408,6 +2406,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
24082406 </span>
24092407 ) : null}
24102408 </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" />
24112412 </SidebarMenuButton>
24122413 {/* Environment badge – visible by default, crossfades with the
24132414 "new thread" button on hover using the same pointer-events +
......@@ -2618,7 +2619,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
26182619
26192620const SidebarProjectListRow = memo(function SidebarProjectListRow(props: SidebarProjectItemProps) {
26202621 return (
2621 <SidebarMenuItem className="rounded-md">
2622 <SidebarMenuItem>
26222623 <SidebarProjectItem {...props} />
26232624 </SidebarMenuItem>
26242625 );
......@@ -2963,12 +2964,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
29632964 <SidebarMenu>
29642965 <SidebarMenuItem>
29652966 <CommandDialogTrigger
2966 render={
2967 <SidebarMenuButton
2968 className="focus-visible:ring-0"
2969 data-testid="command-palette-trigger"
2970 />
2971 }
2967 render={<SidebarMenuButton data-testid="command-palette-trigger" />}
29722968 >
29732969 <SearchIcon />
29742970 <span className="flex-1 truncate">Search</span>
apps/web/src/components/PullRequestThreadDialog.tsx+2-2
......@@ -196,8 +196,8 @@ export function PullRequestThreadDialog({
196196 >
197197 <DialogPopup className="max-w-xl">
198198 <DialogHeader>
199 <DialogTitle className="flex items-center gap-2">
200 <SourceControlIcon className="size-4" />
199 <DialogTitle className="flex items-center">
200 <SourceControlIcon className="me-2 size-4" />
201201 Checkout {terminology.singular}
202202 </DialogTitle>
203203 <DialogDescription>
apps/web/src/components/Sidebar.tsx+9-16
......@@ -345,14 +345,9 @@ function SidebarThreadTooltip({
345345 const driverKind = providerEntry?.driverKind ?? null;
346346 const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId);
347347 return (
348 <TooltipPopup
349 side="right"
350 align="start"
351 sideOffset={4}
352 variant="glass"
353 className="[&_[data-slot=tooltip-viewport]]:p-0"
354 >
355 <div className="flex min-w-0 max-w-80 flex-col gap-2 p-[var(--floating-content-inset)]">
348 <TooltipPopup side="right" align="start" sideOffset={4} variant="glass">
349 {/* The viewport's own inset (py-1 px-2) plus this one make the floating inset. */}
350 <div className="flex min-w-0 max-w-80 flex-col gap-2 px-1 py-2">
356351 <div className="min-w-0 truncate text-xs leading-tight font-medium text-foreground">
357352 {thread.title}
358353 </div>
......@@ -373,9 +368,9 @@ function SidebarThreadTooltip({
373368 </div>
374369 ) : null}
375370 {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">
377372 <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" />
379374 </div>
380375 ) : null}
381376 {branchMismatch ? (
......@@ -1920,11 +1915,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
19201915 {thread.branch ? (
19211916 <>
19221917 <ThreadWorktreeIndicator thread={thread} />
1923 <MiddleTruncate
1924 value={thread.branch}
1925 showTitle={false}
1926 className="flex-1 text-muted-foreground/40"
1927 />
1918 <span className="flex min-w-0 flex-1 text-muted-foreground/40">
1919 <MiddleTruncate value={thread.branch} showTitle={false} />
1920 </span>
19281921 </>
19291922 ) : (
19301923 <span className="flex-1" />
......@@ -4544,7 +4537,7 @@ export default function Sidebar() {
45444537 </SidebarGroup>
45454538 }
45464539 >
4547 <SidebarGroup className="flex-1 ps-[calc(var(--sidebar-content-inset)+1px)]">
4540 <SidebarGroup className="flex-1">
45484541 {isSearchingThreads ? (
45494542 threadSearchResults.length > 0 ? (
45504543 <TooltipProvider
apps/web/src/components/chat/ChangedFilesTree.tsx+3-4
......@@ -247,10 +247,9 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
247247 theme={resolvedTheme}
248248 className="size-3.5 text-muted-foreground/70"
249249 />
250 <MiddleTruncate
251 value={node.name}
252 className="font-mono text-xs text-foreground/85 group-hover:text-foreground"
253 />
250 <span className="flex min-w-0 font-mono text-xs text-foreground/85 group-hover:text-foreground">
251 <MiddleTruncate value={node.name} />
252 </span>
254253 {node.stat && (
255254 <span className="ml-auto shrink-0 font-mono text-[10px] tabular-nums">
256255 <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
279279}
280280
281281/** 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>) {
283283 return (
284284 <ScrollArea
285285 radius="none"
286286 scrollFade
287 className={cn(
288 "h-auto max-h-[min(24rem,40dvh)] [&>[data-slot=scroll-area-viewport][data-has-overflow-y]]:pe-2",
289 className,
290 )}
287 className={cn("h-auto max-h-[min(24rem,40dvh)]", className)}
291288 {...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>
293293 );
294294}
295295
apps/web/src/components/chat/ComposerBannerStack.tsx+1-1
......@@ -306,7 +306,7 @@ function NoticeDescription({ children, compact }: { children: ReactNode; compact
306306 aria-label="Notice details"
307307 tooltipStyle
308308 side="top"
309 className="max-w-80 whitespace-normal text-pretty wrap-anywhere"
309 className="max-w-80 whitespace-normal wrap-anywhere"
310310 >
311311 <ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]">
312312 {children}
apps/web/src/components/chat/MessagesTimeline.tsx+1-7
......@@ -2562,13 +2562,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap
25622562 <Spinner size="xs" className="shrink-0" />
25632563 <span className="truncate">{scriptName}</span>
25642564 </PopoverTrigger>
2565 <PopoverPopup
2566 side="bottom"
2567 align="end"
2568 width="lg"
2569 className="surface-glass!"
2570 padding="compact"
2571 >
2565 <PopoverPopup side="bottom" align="end" width="lg" padding="compact">
25722566 <WorktreeSetupCard
25732567 snapshot={snapshot}
25742568 embedded
apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx+2-2
......@@ -97,10 +97,10 @@ export function useAutoBalanceUpdateBanner(
9797 <Popover>
9898 <PopoverTrigger
9999 render={<InlineButton />}
100 className="block max-w-full truncate"
100 className="max-w-full"
101101 aria-label={`${title}. View machines`}
102102 >
103 {title}
103 <span className="min-w-0 truncate">{title}</span>
104104 </PopoverTrigger>
105105 <PopoverPopup side="top" align="start" width="md">
106106 <div className="space-y-3 text-xs">
apps/web/src/components/onboarding/WelcomeWizard.tsx+2-2
......@@ -584,8 +584,8 @@ function PairingForm({
584584 {isPairing ? "Pairing..." : "Pair"}
585585 </Button>
586586 </div>
587 <CollapsiblePanel className="pt-3">
588 <p className="text-sm text-muted-foreground">
587 <CollapsiblePanel>
588 <p className="pt-3 text-sm text-muted-foreground">
589589 Run this on the computer with your code.
590590 </p>
591591 <CommandBlock command="npx t3 pair" className="mt-2" />
apps/web/src/components/projectScriptEditor.tsx+1-1
......@@ -431,7 +431,7 @@ export function ProjectScriptEditorDialog({
431431 </fieldset>
432432 </form>
433433 </DialogPanel>
434 <DialogFooter className="dark:border-transparent dark:bg-transparent">
434 <DialogFooter variant="bare">
435435 {isEditing && (
436436 <Button
437437 type="button"
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+3-7
......@@ -2252,8 +2252,8 @@ export function PullRequestDetailPanel({
22522252 <PullRequestActorLabel
22532253 actor={detail.author}
22542254 profileUrl={authorProfileUrl}
2255 className="shrink-0 rounded-full"
2256 labelClassName="sr-only"
2255 variant="avatar"
2256 className="shrink-0"
22572257 />
22582258 <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>
22592259 </span>
......@@ -2428,11 +2428,7 @@ export function PullRequestDetailPanel({
24282428 )}
24292429 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">
24302430 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">
2431 <PullRequestActorLabel
2432 actor={detail.author}
2433 profileUrl={authorProfileUrl}
2434 className="font-medium"
2435 />
2431 <PullRequestActorLabel actor={detail.author} profileUrl={authorProfileUrl} />
24362432 <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span>
24372433 </PullRequestMetaLine>
24382434 {checkoutCommand ? (
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-5
......@@ -216,11 +216,7 @@ export function PullRequestDetailGhost({
216216 <div className="mt-2 flex min-h-5 min-w-0 items-center gap-2 text-xs text-muted-foreground">
217217 {seed ? (
218218 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">
219 <PullRequestActorLabel
220 actor={seed.author ?? null}
221 className="font-medium"
222 tooltip={false}
223 />
219 <PullRequestActorLabel actor={seed.author ?? null} tooltip={false} />
224220 <span>updated {formatRelativeTimeLabel(seed.updatedAt)}</span>
225221 </PullRequestMetaLine>
226222 ) : (
apps/web/src/components/pullRequest/PullRequestLinkPreview.tsx+33-29
......@@ -96,37 +96,41 @@ export function PullRequestLinkPreview({
9696 <PreviewCard open={open} onOpenChange={setOpen}>
9797 <PreviewCardTrigger render={trigger} delay={350} closeDelay={120} />
9898 {detail !== null || detailQuery.error !== null ? (
99 <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)] p-3">
100 {detail === null ? (
101 (fallback ?? (
102 <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere">
103 {originalUrl}
104 </p>
105 ))
106 ) : (
107 <div className="min-w-0">
108 <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
109 <span className="min-w-0 truncate">{detail.repository}</span>
110 <span className="shrink-0">#{detail.number}</span>
111 <span aria-hidden>·</span>
112 {state === null ? null : (
113 <span className="inline-flex shrink-0 items-center gap-1">
114 <state.Icon aria-hidden className={`size-3 ${state.toneClassName}`} />
115 {state.label}
99 <PreviewCardPopup align="center" className="w-80 max-w-[calc(100vw-2rem)]">
100 <div className="p-3">
101 {detail === null ? (
102 (fallback ?? (
103 <p className="text-xs leading-relaxed text-muted-foreground wrap-anywhere">
104 {originalUrl}
105 </p>
106 ))
107 ) : (
108 <div className="min-w-0">
109 <div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
110 <span className="min-w-0 truncate">{detail.repository}</span>
111 <span className="shrink-0">#{detail.number}</span>
112 <span aria-hidden>·</span>
113 {state === null ? null : (
114 <span className="inline-flex shrink-0 items-center gap-1">
115 <state.Icon aria-hidden className={`size-3 ${state.toneClassName}`} />
116 {state.label}
117 </span>
118 )}
119 </div>
120 <p className="mt-1 text-sm font-medium leading-snug text-foreground text-pretty">
121 {detail.title}
122 </p>
123 <div className="mt-2 flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
124 <PullRequestActorAvatar actor={detail.author} className="size-4" />
125 <span className="min-w-0 truncate">{authorLabel}</span>
126 <span aria-hidden>·</span>
127 <span className="shrink-0">
128 opened {formatRelativeTimeLabel(detail.createdAt)}
116129 </span>
117 )}
130 </div>
118131 </div>
119 <p className="mt-1 text-sm font-medium leading-snug text-foreground text-pretty">
120 {detail.title}
121 </p>
122 <div className="mt-2 flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
123 <PullRequestActorAvatar actor={detail.author} className="size-4" />
124 <span className="min-w-0 truncate">{authorLabel}</span>
125 <span aria-hidden>·</span>
126 <span className="shrink-0">opened {formatRelativeTimeLabel(detail.createdAt)}</span>
127 </div>
128 </div>
129 )}
132 )}
133 </div>
130134 </PreviewCardPopup>
131135 ) : null}
132136 </PreviewCard>
apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx+1-1
......@@ -265,7 +265,7 @@ export function ReviewThreadCard({
265265 {comments.map((comment) => (
266266 <article key={comment.id} className="group min-w-0">
267267 <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} />
269269 <span>{formatRelativeTimeLabel(comment.createdAt)}</span>
270270 <PullRequestReactionBar
271271 className="ml-auto justify-end"
apps/web/src/components/pullRequest/PullRequestReviewerPicker.tsx+1-1
......@@ -120,7 +120,7 @@ export function PullRequestReviewerPicker({
120120 >
121121 {(candidate) => (
122122 <>
123 <PullRequestActorLabel actor={candidate} className="min-w-0 flex-1 truncate" />
123 <PullRequestActorLabel actor={candidate} className="flex-1" />
124124 {candidate.kind === "team" ? (
125125 <span className="shrink-0 text-muted-foreground">team</span>
126126 ) : null}
apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx+21-28
......@@ -78,11 +78,7 @@ function CommentIdentity({
7878 : null;
7979 return (
8080 <div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs">
81 <PullRequestActorLabel
82 actor={actor}
83 profileUrl={profileUrl}
84 className="max-w-full font-medium text-foreground [&>img]:size-6 [&>span:first-child]:size-6"
85 />
81 <PullRequestActorLabel actor={actor} profileUrl={profileUrl} className="max-w-full" />
8682 <Tooltip>
8783 <TooltipTrigger
8884 render={
......@@ -383,22 +379,26 @@ function CommentGroup({
383379 <div className="flex items-center gap-3 pl-3">
384380 <div className="flex shrink-0 -space-x-1.5">
385381 {authors.slice(0, 3).map((actor) => (
386 <PullRequestActorLabel
382 // The ring separates the overlapping faces; it belongs to the stack, not the actor.
383 <span
387384 key={actor?.login ?? "ghost"}
388 actor={actor}
389 profileUrl={
390 detail.provider === "github" && actor
391 ? new URL(
392 actor.isBot || actor.login.endsWith("[bot]")
393 ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}`
394 : `/${encodeURIComponent(actor.login)}`,
395 detail.url,
396 ).toString()
397 : null
398 }
399 labelClassName="sr-only"
400 className="relative rounded-full bg-background ring-2 ring-background hover:z-10 focus-visible:z-10 [&>img]:size-6 [&>span:first-child]:size-6"
401 />
385 className="relative flex rounded-full ring-2 ring-background hover:z-10 focus-within:z-10"
386 >
387 <PullRequestActorLabel
388 actor={actor}
389 profileUrl={
390 detail.provider === "github" && actor
391 ? new URL(
392 actor.isBot || actor.login.endsWith("[bot]")
393 ? `/apps/${encodeURIComponent(actor.login.replace(/\[bot\]$/, ""))}`
394 : `/${encodeURIComponent(actor.login)}`,
395 detail.url,
396 ).toString()
397 : null
398 }
399 variant="avatar"
400 />
401 </span>
402402 ))}
403403 {authors.length > 3 ? (
404404 <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({
752752 <PullRequestActorLabel
753753 actor={entry.actor}
754754 tooltip={false}
755 className={cn(
756 "gap-0 [&>span:last-child]:sr-only",
757 // Only where the wrapper is not already drawing one, or the opaque
758 // separator would cover the verdict in the band they share.
759 entry.outcome
760 ? undefined
761 : "[&>img]:ring-2 [&>img]:ring-background [&>span:first-child]:ring-2 [&>span:first-child]:ring-background",
762 )}
755 variant="avatar"
763756 />
764757 {/* Colour alone says nothing to a reader who cannot see it, and the
765758 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
2323import { cn } from "~/lib/utils";
2424
2525import { Badge } from "../ui/badge";
26import { InlineButton } from "../ui/button";
2627import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
2728import type { PullRequestReviewOutcome } from "./pullRequestDetail.logic";
2829import { pullRequestLabelColor } from "./pullRequestList.logic";
......@@ -445,51 +446,56 @@ export function PullRequestActorAvatar({
445446 );
446447}
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 */
449455export function PullRequestActorLabel({
450456 actor,
451457 className,
452 labelClassName,
458 variant = "label",
453459 tooltip = true,
454460 profileUrl,
455461}: {
456462 actor: PullRequestActor | null;
457463 className?: string;
458 labelClassName?: string;
464 variant?: "label" | "avatar";
459465 tooltip?: boolean;
460466 profileUrl?: string | null;
461467}) {
462468 const login = actor?.login ?? "ghost";
463469 const label = (
464 <>
470 <span className={cn("flex min-w-0 items-center", variant === "label" && "gap-1.5")}>
465471 <PullRequestActorAvatar actor={actor} />
466 <span className={cn("truncate", labelClassName)}>{login}</span>
467 </>
472 <span className={variant === "label" ? "truncate font-medium text-foreground" : "sr-only"}>
473 {login}
474 </span>
475 </span>
468476 );
469 if (!tooltip) {
470 return <span className={cn("flex min-w-0 items-center gap-1.5", className)}>{label}</span>;
471 }
477 const placement = cn("flex min-w-0 shrink", className);
478 if (!tooltip) return <span className={placement}>{label}</span>;
472479 return (
473480 <Tooltip>
474481 <TooltipTrigger
475482 render={
476483 profileUrl ? (
477 <a
478 href={profileUrl}
479 target="_blank"
480 rel="noopener noreferrer"
481 aria-label={`Open ${login}'s profile`}
484 <InlineButton
485 className={placement}
486 render={
487 <a
488 href={profileUrl}
489 target="_blank"
490 rel="noopener noreferrer"
491 aria-label={`Open ${login}'s profile`}
492 />
493 }
482494 />
483495 ) : (
484 <span />
496 <span className={placement} />
485497 )
486498 }
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 )}
493499 >
494500 {label}
495501 </TooltipTrigger>
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-4
......@@ -67,14 +67,13 @@ function SearchOptionButton(props: {
6767 <Toggle
6868 aria-label={props.label}
6969 pressed={props.active}
70 className="size-8 rounded-[5px] font-mono text-muted-foreground data-pressed:text-foreground sm:size-7"
71 size="compact"
72 variant="ghost"
70 size="segmented"
71 variant="segmented"
7372 onClick={props.onClick}
7473 />
7574 }
7675 >
77 {props.children}
76 <span className="font-mono">{props.children}</span>
7877 </TooltipTrigger>
7978 <TooltipPopup side="top">{props.label}</TooltipPopup>
8079 </Tooltip>
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+1-1
......@@ -251,7 +251,7 @@ export function AddProviderInstanceDialog({
251251 value={driver}
252252 onValueChange={(value) => setDriver(ProviderDriverKind.make(value))}
253253 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"
255255 >
256256 {DRIVER_OPTIONS.map((option) => {
257257 const IconComponent = option.icon;
apps/web/src/components/settings/DiagnosticsSettings.tsx+158-154
......@@ -344,84 +344,86 @@ function ProcessDiagnosticsTable({
344344 }, []);
345345
346346 return (
347 <ScrollArea
348 radius="none"
349 chainVerticalScroll
350 scrollFade
351 hideScrollbars
352 className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60"
353 >
354 <table className="w-full min-w-[1040px] table-fixed text-left text-xs">
355 <colgroup>
356 <col className="w-[24%]" />
357 <col className="w-[8%]" />
358 <col className="w-[10%]" />
359 <col className="w-[33%]" />
360 <col className="w-[8%]" />
361 <col className="w-[11%]" />
362 <col className="w-[6%]" />
363 </colgroup>
364 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70">
365 <tr>
366 <th className="px-4 py-2 font-semibold sm:pl-5">Name</th>
367 <th className="px-3 py-2 text-right font-semibold">CPU</th>
368 <th className="px-3 py-2 text-right font-semibold">Memory</th>
369 <th className="px-3 py-2 font-semibold">Command</th>
370 <th className="px-3 py-2 text-right font-semibold">PID</th>
371 <th className="px-3 py-2 font-semibold">Type</th>
372 <th className="p-2 text-right font-semibold sm:pr-4">Kill</th>
373 </tr>
374 </thead>
375 <tbody className="divide-y divide-border/50">
376 {visibleProcesses.length === 0 ? (
347 <div className="border-t border-border/60">
348 <ScrollArea
349 radius="none"
350 chainVerticalScroll
351 scrollFade
352 hideScrollbars
353 className="max-h-[min(64vh,44rem)] w-full max-w-full"
354 >
355 <table className="w-full min-w-[1040px] table-fixed text-left text-xs">
356 <colgroup>
357 <col className="w-[24%]" />
358 <col className="w-[8%]" />
359 <col className="w-[10%]" />
360 <col className="w-[33%]" />
361 <col className="w-[8%]" />
362 <col className="w-[11%]" />
363 <col className="w-[6%]" />
364 </colgroup>
365 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70">
377366 <tr>
378 <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
379 {emptyLabel ?? "No live descendant processes found."}
380 </td>
367 <th className="px-4 py-2 font-semibold sm:pl-5">Name</th>
368 <th className="px-3 py-2 text-right font-semibold">CPU</th>
369 <th className="px-3 py-2 text-right font-semibold">Memory</th>
370 <th className="px-3 py-2 font-semibold">Command</th>
371 <th className="px-3 py-2 text-right font-semibold">PID</th>
372 <th className="px-3 py-2 font-semibold">Type</th>
373 <th className="p-2 text-right font-semibold sm:pr-4">Kill</th>
381374 </tr>
382 ) : null}
383 {visibleProcesses.map((process) => (
384 <tr key={process.pid} className="hover:bg-muted/20">
385 <td className="px-4 py-2 align-middle sm:pl-5">
386 <ProcessNameCell
387 process={process}
388 isExpanded={!collapsedPids.has(process.pid)}
389 onToggle={toggleProcess}
390 />
391 </td>
392 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
393 {process.cpuPercent.toFixed(1)}%
394 </td>
395 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
396 {formatBytes(process.rssBytes)}
397 </td>
398 <td className="px-3 py-2 align-middle text-muted-foreground">
399 <Tooltip>
400 <TooltipTrigger
401 render={<span className="block truncate">{process.command}</span>}
375 </thead>
376 <tbody className="divide-y divide-border/50">
377 {visibleProcesses.length === 0 ? (
378 <tr>
379 <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
380 {emptyLabel ?? "No live descendant processes found."}
381 </td>
382 </tr>
383 ) : null}
384 {visibleProcesses.map((process) => (
385 <tr key={process.pid} className="hover:bg-muted/20">
386 <td className="px-4 py-2 align-middle sm:pl-5">
387 <ProcessNameCell
388 process={process}
389 isExpanded={!collapsedPids.has(process.pid)}
390 onToggle={toggleProcess}
402391 />
403 <TooltipPopup side="top" variant="code">
404 {process.command}
405 </TooltipPopup>
406 </Tooltip>
407 </td>
408 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground">
409 {process.pid}
410 </td>
411 <td className="truncate px-3 py-2 align-middle text-muted-foreground">
412 {formatProcessType(process)}
413 </td>
414 <td className="p-2 align-middle sm:pr-4">
415 <ProcessSignalActions
416 disabled={signalingPid === process.pid}
417 onSignal={(signal) => onSignal(process.pid, signal)}
418 />
419 </td>
420 </tr>
421 ))}
422 </tbody>
423 </table>
424 </ScrollArea>
392 </td>
393 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
394 {process.cpuPercent.toFixed(1)}%
395 </td>
396 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
397 {formatBytes(process.rssBytes)}
398 </td>
399 <td className="px-3 py-2 align-middle text-muted-foreground">
400 <Tooltip>
401 <TooltipTrigger
402 render={<span className="block truncate">{process.command}</span>}
403 />
404 <TooltipPopup side="top" variant="code">
405 {process.command}
406 </TooltipPopup>
407 </Tooltip>
408 </td>
409 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground">
410 {process.pid}
411 </td>
412 <td className="truncate px-3 py-2 align-middle text-muted-foreground">
413 {formatProcessType(process)}
414 </td>
415 <td className="p-2 align-middle sm:pr-4">
416 <ProcessSignalActions
417 disabled={signalingPid === process.pid}
418 onSignal={(signal) => onSignal(process.pid, signal)}
419 />
420 </td>
421 </tr>
422 ))}
423 </tbody>
424 </table>
425 </ScrollArea>
426 </div>
425427 );
426428}
427429
......@@ -568,88 +570,90 @@ function ProcessResourceHistoryTable({
568570 }, null);
569571
570572 return (
571 <ScrollArea
572 chainVerticalScroll
573 scrollFade
574 hideScrollbars
575 className="max-h-[min(64vh,44rem)] w-full max-w-full border-t border-border/60"
576 >
577 <table className="w-full min-w-[980px] table-fixed text-left text-xs">
578 <colgroup>
579 <col className="w-[24%]" />
580 <col className="w-[10%]" />
581 <col className="w-[10%]" />
582 <col className="w-[10%]" />
583 <col className="w-[10%]" />
584 <col className="w-[10%]" />
585 <col className="w-[16%]" />
586 <col className="w-[10%]" />
587 </colgroup>
588 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70">
589 <tr>
590 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
591 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
592 <th className="px-3 py-2 text-right font-semibold">Current</th>
593 <th className="px-3 py-2 text-right font-semibold">Average</th>
594 <th className="px-3 py-2 text-right font-semibold">Peak</th>
595 <th className="px-3 py-2 text-right font-semibold">Max Mem</th>
596 <th className="px-3 py-2 font-semibold">Command</th>
597 <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th>
598 </tr>
599 </thead>
600 <tbody className="divide-y divide-border/50">
601 {processes.length === 0 ? (
573 <div className="border-t border-border/60">
574 <ScrollArea
575 chainVerticalScroll
576 scrollFade
577 hideScrollbars
578 className="max-h-[min(64vh,44rem)] w-full max-w-full"
579 >
580 <table className="w-full min-w-[980px] table-fixed text-left text-xs">
581 <colgroup>
582 <col className="w-[24%]" />
583 <col className="w-[10%]" />
584 <col className="w-[10%]" />
585 <col className="w-[10%]" />
586 <col className="w-[10%]" />
587 <col className="w-[10%]" />
588 <col className="w-[16%]" />
589 <col className="w-[10%]" />
590 </colgroup>
591 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70">
602592 <tr>
603 <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
604 {emptyLabel}
605 </td>
593 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
594 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
595 <th className="px-3 py-2 text-right font-semibold">Current</th>
596 <th className="px-3 py-2 text-right font-semibold">Average</th>
597 <th className="px-3 py-2 text-right font-semibold">Peak</th>
598 <th className="px-3 py-2 text-right font-semibold">Max Mem</th>
599 <th className="px-3 py-2 font-semibold">Command</th>
600 <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th>
606601 </tr>
607 ) : null}
608 {processes.map((process) => (
609 <tr key={process.processKey} className="hover:bg-muted/20">
610 <td className="px-4 py-2 align-middle sm:pl-5">
611 <ResourceHistoryProcessNameCell
612 process={process}
613 visualDepth={
614 process.isServerRoot || shallowestChildDepth === null
615 ? 0
616 : Math.max(1, process.depth - shallowestChildDepth + 1)
617 }
618 />
619 </td>
620 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
621 {formatCpuTime(process.cpuSecondsApprox)}
622 </td>
623 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
624 {process.currentCpuPercent.toFixed(1)}%
625 </td>
626 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
627 {process.avgCpuPercent.toFixed(1)}%
628 </td>
629 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
630 {process.maxCpuPercent.toFixed(1)}%
631 </td>
632 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
633 {formatBytes(process.maxRssBytes)}
634 </td>
635 <td className="px-3 py-2 align-middle text-muted-foreground">
636 <Tooltip>
637 <TooltipTrigger
638 render={<span className="block truncate">{process.command}</span>}
602 </thead>
603 <tbody className="divide-y divide-border/50">
604 {processes.length === 0 ? (
605 <tr>
606 <td colSpan={8} className="px-4 py-4 text-xs text-muted-foreground sm:px-5">
607 {emptyLabel}
608 </td>
609 </tr>
610 ) : null}
611 {processes.map((process) => (
612 <tr key={process.processKey} className="hover:bg-muted/20">
613 <td className="px-4 py-2 align-middle sm:pl-5">
614 <ResourceHistoryProcessNameCell
615 process={process}
616 visualDepth={
617 process.isServerRoot || shallowestChildDepth === null
618 ? 0
619 : Math.max(1, process.depth - shallowestChildDepth + 1)
620 }
639621 />
640 <TooltipPopup side="top" variant="code">
641 {process.command}
642 </TooltipPopup>
643 </Tooltip>
644 </td>
645 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground sm:pr-5">
646 {process.pid}
647 </td>
648 </tr>
649 ))}
650 </tbody>
651 </table>
652 </ScrollArea>
622 </td>
623 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
624 {formatCpuTime(process.cpuSecondsApprox)}
625 </td>
626 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
627 {process.currentCpuPercent.toFixed(1)}%
628 </td>
629 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
630 {process.avgCpuPercent.toFixed(1)}%
631 </td>
632 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
633 {process.maxCpuPercent.toFixed(1)}%
634 </td>
635 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums">
636 {formatBytes(process.maxRssBytes)}
637 </td>
638 <td className="px-3 py-2 align-middle text-muted-foreground">
639 <Tooltip>
640 <TooltipTrigger
641 render={<span className="block truncate">{process.command}</span>}
642 />
643 <TooltipPopup side="top" variant="code">
644 {process.command}
645 </TooltipPopup>
646 </Tooltip>
647 </td>
648 <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground sm:pr-5">
649 {process.pid}
650 </td>
651 </tr>
652 ))}
653 </tbody>
654 </table>
655 </ScrollArea>
656 </div>
653657 );
654658}
655659
apps/web/src/components/settings/IntegrationsSettings.tsx+4-7
......@@ -1278,14 +1278,11 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
12781278 onCommit={(next) => renameProfile(profile.id, next)}
12791279 />
12801280 )}
1281 {/*
1282 Dimmed with the rest of the row: a `Badge` has no disabled
1283 treatment of its own, so a solid `bg-primary` pill would
1284 otherwise sit at full strength beside a name, rename field
1285 and menu button that are all at 0.64.
1286 */}
1281 {/* Dimmed with the rest of the row, whose controls are all disabled. */}
12871282 {isDefault ? (
1288 <Badge className={cn(profileWritesDisabled && "opacity-64")}>Default</Badge>
1283 <span className={cn("flex", profileWritesDisabled && "opacity-64")}>
1284 <Badge>Default</Badge>
1285 </span>
12891286 ) : null}
12901287 </span>
12911288 <Menu>
apps/web/src/components/settings/ProviderAccentColorPicker.tsx+1-6
......@@ -90,12 +90,7 @@ function ProviderCustomColorPicker(props: {
9090 </button>
9191 }
9292 />
93 <PopoverPopup
94 side="bottom"
95 align="start"
96 sideOffset={6}
97 className="overflow-hidden rounded-md p-0 [--viewport-inline-padding:0px] [&_[data-slot=popover-viewport]]:p-0"
98 >
93 <PopoverPopup side="bottom" align="start" sideOffset={6} padding="none">
9994 <ProviderCustomColorPanel value={normalized} onCommit={props.onCommit} />
10095 <PopoverClose
10196 render={
apps/web/src/components/settings/ProviderSettingsPanel.tsx+1-1
......@@ -353,7 +353,7 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) {
353353 <Tooltip key={environment.environmentId}>
354354 <TooltipTrigger
355355 render={
356 <Toggle value={environment.environmentId} className="gap-2 text-left">
356 <Toggle value={environment.environmentId}>
357357 <EnvironmentMachineIcon
358358 kind={machine}
359359 className="size-3.5 shrink-0"
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+185-179
......@@ -455,11 +455,13 @@ function ResourceHistoryChart({
455455 </div>
456456 }
457457 />
458 <TooltipPopup side="top" className="space-y-0.5 text-left">
459 <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div>
460 <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div>
461 <div>Read {formatBytes(bucket.ioReadBytes)}</div>
462 <div>Write {formatBytes(bucket.ioWriteBytes)}</div>
458 <TooltipPopup side="top" className="text-left">
459 <div className="space-y-0.5">
460 <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div>
461 <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div>
462 <div>Read {formatBytes(bucket.ioReadBytes)}</div>
463 <div>Write {formatBytes(bucket.ioWriteBytes)}</div>
464 </div>
463465 </TooltipPopup>
464466 </Tooltip>
465467 );
......@@ -565,100 +567,102 @@ function ProcessTable({
565567 }, []);
566568
567569 return (
568 <ScrollArea
569 chainVerticalScroll
570 scrollFade
571 hideScrollbars
572 className="max-h-[min(68vh,48rem)] w-full max-w-full border-t border-border/60"
573 >
574 <table className="w-full min-w-[1320px] table-fixed text-left text-xs">
575 <colgroup>
576 <col className="w-[20%]" />
577 <col className="w-[10%]" />
578 <col className="w-[7%]" />
579 <col className="w-[8%]" />
580 <col className="w-[9%]" />
581 <col className="w-[9%]" />
582 <col className="w-[9%]" />
583 <col className="w-[10%]" />
584 <col className="w-[8%]" />
585 <col className="w-[6%]" />
586 <col className="w-[4%]" />
587 </colgroup>
588 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65">
589 <tr>
590 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
591 <th className="px-3 py-2 font-semibold">Category</th>
592 <th className="px-3 py-2 text-right font-semibold">CPU</th>
593 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
594 <th className="px-3 py-2 text-right font-semibold">Memory</th>
595 <th className="px-3 py-2 text-right font-semibold">Read/s</th>
596 <th className="px-3 py-2 text-right font-semibold">Write/s</th>
597 <th className="px-3 py-2 text-right font-semibold">Read Total</th>
598 <th className="px-3 py-2 text-right font-semibold">Write Total</th>
599 <th className="px-3 py-2 text-right font-semibold">PID</th>
600 <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th>
601 </tr>
602 </thead>
603 <tbody className="divide-y divide-border/50">
604 {visible.length === 0 ? (
570 <div className="border-t border-border/60">
571 <ScrollArea
572 chainVerticalScroll
573 scrollFade
574 hideScrollbars
575 className="max-h-[min(68vh,48rem)] w-full max-w-full"
576 >
577 <table className="w-full min-w-[1320px] table-fixed text-left text-xs">
578 <colgroup>
579 <col className="w-[20%]" />
580 <col className="w-[10%]" />
581 <col className="w-[7%]" />
582 <col className="w-[8%]" />
583 <col className="w-[9%]" />
584 <col className="w-[9%]" />
585 <col className="w-[9%]" />
586 <col className="w-[10%]" />
587 <col className="w-[8%]" />
588 <col className="w-[6%]" />
589 <col className="w-[4%]" />
590 </colgroup>
591 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65">
605592 <tr>
606 <td colSpan={11} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">
607 Waiting for the native process monitor.
608 </td>
609 </tr>
610 ) : null}
611 {visible.map((process) => (
612 <tr key={processIdentityKey(process)} className="hover:bg-muted/20">
613 <td className="px-4 py-2 sm:pl-5">
614 <ProcessTreeName
615 process={process}
616 collapsed={collapsed.has(processIdentityKey(process))}
617 onToggle={toggle}
618 />
619 </td>
620 <td className="truncate px-3 py-2 text-[11px] text-muted-foreground">
621 {categoryLabel(process.category)}
622 </td>
623 <td className="px-3 py-2 text-right font-mono tabular-nums">
624 {process.cpuPercent.toFixed(1)}%
625 </td>
626 <td className="px-3 py-2 text-right font-mono tabular-nums">
627 {formatCpuTime(process.cpuTimeMs)}
628 </td>
629 <td className="px-3 py-2 text-right font-mono tabular-nums">
630 {formatBytes(process.residentBytes)}
631 </td>
632 <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300">
633 {formatRate(process.ioReadBytesPerSecond)}
634 </td>
635 <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300">
636 {formatRate(process.ioWriteBytesPerSecond)}
637 </td>
638 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
639 {formatBytes(process.ioReadBytes)}
640 </td>
641 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
642 <Tooltip>
643 <TooltipTrigger render={<span>{formatBytes(process.ioWriteBytes)}</span>} />
644 <TooltipPopup side="top">{ioSemanticsLabel(process.ioSemantics)}</TooltipPopup>
645 </Tooltip>
646 </td>
647 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
648 {process.identity.pid}
649 </td>
650 <td className="px-2 py-2 text-right sm:pr-4">
651 <ProcessActions
652 process={process}
653 signalingKeys={signalingKeys}
654 onSignal={onSignal}
655 />
656 </td>
593 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
594 <th className="px-3 py-2 font-semibold">Category</th>
595 <th className="px-3 py-2 text-right font-semibold">CPU</th>
596 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
597 <th className="px-3 py-2 text-right font-semibold">Memory</th>
598 <th className="px-3 py-2 text-right font-semibold">Read/s</th>
599 <th className="px-3 py-2 text-right font-semibold">Write/s</th>
600 <th className="px-3 py-2 text-right font-semibold">Read Total</th>
601 <th className="px-3 py-2 text-right font-semibold">Write Total</th>
602 <th className="px-3 py-2 text-right font-semibold">PID</th>
603 <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th>
657604 </tr>
658 ))}
659 </tbody>
660 </table>
661 </ScrollArea>
605 </thead>
606 <tbody className="divide-y divide-border/50">
607 {visible.length === 0 ? (
608 <tr>
609 <td colSpan={11} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">
610 Waiting for the native process monitor.
611 </td>
612 </tr>
613 ) : null}
614 {visible.map((process) => (
615 <tr key={processIdentityKey(process)} className="hover:bg-muted/20">
616 <td className="px-4 py-2 sm:pl-5">
617 <ProcessTreeName
618 process={process}
619 collapsed={collapsed.has(processIdentityKey(process))}
620 onToggle={toggle}
621 />
622 </td>
623 <td className="truncate px-3 py-2 text-[11px] text-muted-foreground">
624 {categoryLabel(process.category)}
625 </td>
626 <td className="px-3 py-2 text-right font-mono tabular-nums">
627 {process.cpuPercent.toFixed(1)}%
628 </td>
629 <td className="px-3 py-2 text-right font-mono tabular-nums">
630 {formatCpuTime(process.cpuTimeMs)}
631 </td>
632 <td className="px-3 py-2 text-right font-mono tabular-nums">
633 {formatBytes(process.residentBytes)}
634 </td>
635 <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300">
636 {formatRate(process.ioReadBytesPerSecond)}
637 </td>
638 <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300">
639 {formatRate(process.ioWriteBytesPerSecond)}
640 </td>
641 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
642 {formatBytes(process.ioReadBytes)}
643 </td>
644 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
645 <Tooltip>
646 <TooltipTrigger render={<span>{formatBytes(process.ioWriteBytes)}</span>} />
647 <TooltipPopup side="top">{ioSemanticsLabel(process.ioSemantics)}</TooltipPopup>
648 </Tooltip>
649 </td>
650 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
651 {process.identity.pid}
652 </td>
653 <td className="px-2 py-2 text-right sm:pr-4">
654 <ProcessActions
655 process={process}
656 signalingKeys={signalingKeys}
657 onSignal={onSignal}
658 />
659 </td>
660 </tr>
661 ))}
662 </tbody>
663 </table>
664 </ScrollArea>
665 </div>
662666 );
663667}
664668
......@@ -668,90 +672,92 @@ function HistoryProcessTable({
668672 processes: ReadonlyArray<ResourceTelemetryProcessSummary>;
669673}) {
670674 return (
671 <ScrollArea
672 chainVerticalScroll
673 scrollFade
674 hideScrollbars
675 className="max-h-[28rem] w-full max-w-full border-t border-border/60"
676 >
677 <table className="w-full min-w-[1020px] table-fixed text-left text-xs">
678 <colgroup>
679 <col className="w-[24%]" />
680 <col className="w-[11%]" />
681 <col className="w-[10%]" />
682 <col className="w-[10%]" />
683 <col className="w-[11%]" />
684 <col className="w-[11%]" />
685 <col className="w-[11%]" />
686 <col className="w-[7%]" />
687 <col className="w-[5%]" />
688 </colgroup>
689 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65">
690 <tr>
691 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
692 <th className="px-3 py-2 font-semibold">Category</th>
693 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
694 <th className="px-3 py-2 text-right font-semibold">Peak CPU</th>
695 <th className="px-3 py-2 text-right font-semibold">Peak Mem</th>
696 <th className="px-3 py-2 text-right font-semibold">Read</th>
697 <th className="px-3 py-2 text-right font-semibold">Write</th>
698 <th className="px-3 py-2 text-right font-semibold">Samples</th>
699 <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th>
700 </tr>
701 </thead>
702 <tbody className="divide-y divide-border/50">
703 {processes.length === 0 ? (
675 <div className="border-t border-border/60">
676 <ScrollArea
677 chainVerticalScroll
678 scrollFade
679 hideScrollbars
680 className="max-h-[28rem] w-full max-w-full"
681 >
682 <table className="w-full min-w-[1020px] table-fixed text-left text-xs">
683 <colgroup>
684 <col className="w-[24%]" />
685 <col className="w-[11%]" />
686 <col className="w-[10%]" />
687 <col className="w-[10%]" />
688 <col className="w-[11%]" />
689 <col className="w-[11%]" />
690 <col className="w-[11%]" />
691 <col className="w-[7%]" />
692 <col className="w-[5%]" />
693 </colgroup>
694 <thead className="sticky top-0 z-10 border-b border-border/60 bg-card text-[10px] uppercase tracking-[0.08em] text-muted-foreground/65">
704695 <tr>
705 <td colSpan={9} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">
706 No retained process samples in this window.
707 </td>
708 </tr>
709 ) : null}
710 {processes.map((process) => (
711 <tr key={processSummaryIdentityKey(process)} className="hover:bg-muted/20">
712 <td className="px-4 py-2 sm:pl-5">
713 <Tooltip>
714 <TooltipTrigger
715 render={
716 <span className="block truncate font-medium text-foreground">
717 {process.name || process.command}
718 </span>
719 }
720 />
721 <TooltipPopup side="top" variant="code">
722 {process.command || process.name}
723 </TooltipPopup>
724 </Tooltip>
725 </td>
726 <td className="truncate px-3 py-2 text-[11px] text-muted-foreground">
727 {categoryLabel(process.category)}
728 </td>
729 <td className="px-3 py-2 text-right font-mono tabular-nums">
730 {formatCpuTime(process.cpuTimeMs)}
731 </td>
732 <td className="px-3 py-2 text-right font-mono tabular-nums">
733 {process.maxCpuPercent.toFixed(1)}%
734 </td>
735 <td className="px-3 py-2 text-right font-mono tabular-nums">
736 {formatBytes(process.peakRssBytes)}
737 </td>
738 <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300">
739 {formatBytes(process.ioReadBytes)}
740 </td>
741 <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300">
742 {formatBytes(process.ioWriteBytes)}
743 </td>
744 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
745 {process.sampleCount}
746 </td>
747 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground sm:pr-5">
748 {process.identity.pid}
749 </td>
696 <th className="px-4 py-2 font-semibold sm:pl-5">Process</th>
697 <th className="px-3 py-2 font-semibold">Category</th>
698 <th className="px-3 py-2 text-right font-semibold">CPU Time</th>
699 <th className="px-3 py-2 text-right font-semibold">Peak CPU</th>
700 <th className="px-3 py-2 text-right font-semibold">Peak Mem</th>
701 <th className="px-3 py-2 text-right font-semibold">Read</th>
702 <th className="px-3 py-2 text-right font-semibold">Write</th>
703 <th className="px-3 py-2 text-right font-semibold">Samples</th>
704 <th className="px-3 py-2 text-right font-semibold sm:pr-5">PID</th>
750705 </tr>
751 ))}
752 </tbody>
753 </table>
754 </ScrollArea>
706 </thead>
707 <tbody className="divide-y divide-border/50">
708 {processes.length === 0 ? (
709 <tr>
710 <td colSpan={9} className="px-4 py-5 text-xs text-muted-foreground sm:px-5">
711 No retained process samples in this window.
712 </td>
713 </tr>
714 ) : null}
715 {processes.map((process) => (
716 <tr key={processSummaryIdentityKey(process)} className="hover:bg-muted/20">
717 <td className="px-4 py-2 sm:pl-5">
718 <Tooltip>
719 <TooltipTrigger
720 render={
721 <span className="block truncate font-medium text-foreground">
722 {process.name || process.command}
723 </span>
724 }
725 />
726 <TooltipPopup side="top" variant="code">
727 {process.command || process.name}
728 </TooltipPopup>
729 </Tooltip>
730 </td>
731 <td className="truncate px-3 py-2 text-[11px] text-muted-foreground">
732 {categoryLabel(process.category)}
733 </td>
734 <td className="px-3 py-2 text-right font-mono tabular-nums">
735 {formatCpuTime(process.cpuTimeMs)}
736 </td>
737 <td className="px-3 py-2 text-right font-mono tabular-nums">
738 {process.maxCpuPercent.toFixed(1)}%
739 </td>
740 <td className="px-3 py-2 text-right font-mono tabular-nums">
741 {formatBytes(process.peakRssBytes)}
742 </td>
743 <td className="px-3 py-2 text-right font-mono tabular-nums text-sky-700 dark:text-sky-300">
744 {formatBytes(process.ioReadBytes)}
745 </td>
746 <td className="px-3 py-2 text-right font-mono tabular-nums text-amber-700 dark:text-amber-300">
747 {formatBytes(process.ioWriteBytes)}
748 </td>
749 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground">
750 {process.sampleCount}
751 </td>
752 <td className="px-3 py-2 text-right font-mono tabular-nums text-muted-foreground sm:pr-5">
753 {process.identity.pid}
754 </td>
755 </tr>
756 ))}
757 </tbody>
758 </table>
759 </ScrollArea>
760 </div>
755761 );
756762}
757763
apps/web/src/components/settings/SettingsScopeNotice.tsx+1-1
......@@ -66,7 +66,7 @@ export function SettingsScopeNotice({
6666 <Alert role="status">
6767 <AlertDescription>
6868 <p>{children}</p>
69 <AlertAction className="flex-wrap gap-2">
69 <AlertAction className="flex-wrap">
7070 {choices.map((choice) => (
7171 <Button
7272 key={JSON.stringify(choice.search)}
apps/web/src/components/settings/SettingsSidebarNav.tsx+105-101
......@@ -234,113 +234,117 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) {
234234 return (
235235 <>
236236 <SidebarContent className="overflow-x-hidden">
237 <SidebarGroup className="gap-2">
238 <div className="flex h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground">
239 <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" />
240 <SidebarInput
241 ref={searchInputRef}
242 nativeInput
243 type="search"
244 value={query}
245 onChange={(event) => {
246 setQuery(event.currentTarget.value);
247 setActiveResultIndex(0);
248 }}
249 onKeyDown={handleSearchKeyDown}
250 placeholder="Search"
251 aria-label="Search settings"
252 role="combobox"
253 aria-autocomplete="list"
254 aria-expanded={isSearching && hasResults}
255 aria-controls={isSearching && hasResults ? "settings-search-results" : undefined}
256 aria-activedescendant={
257 isSearching && results[activeResultIndex]
258 ? `settings-search-result-${results[activeResultIndex].id}`
259 : undefined
260 }
261 className="min-w-0 flex-1"
262 />
263 {isSearching ? (
264 <Button
265 type="button"
266 size="icon-micro"
267 variant="ghost"
268 className="shrink-0 text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground"
269 aria-label="Clear settings search"
270 onClick={() => {
271 clearSearch();
272 searchInputRef.current?.focus();
237 <SidebarGroup>
238 <div className="flex flex-col gap-2">
239 <div className="flex h-8 items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-sidebar-muted-foreground hover:bg-sidebar-row-hover hover:text-sidebar-foreground">
240 <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" />
241 <SidebarInput
242 ref={searchInputRef}
243 nativeInput
244 type="search"
245 value={query}
246 onChange={(event) => {
247 setQuery(event.currentTarget.value);
248 setActiveResultIndex(0);
273249 }}
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"
274286 >
275 <XIcon className="size-3" />
276 </Button>
277 ) : (
278 <Kbd>/</Kbd>
279 )}
280 </div>
281 {isSearching && results.length === 0 ? (
282 <p
283 role="status"
284 className="px-2 py-6 text-center text-xs text-sidebar-muted-foreground"
285 >
286 No settings found
287 </p>
288 ) : null}
289 {isSearching ? (
290 <SidebarMenu
291 className="ps-px"
292 id={hasResults ? "settings-search-results" : undefined}
293 role={hasResults ? "listbox" : undefined}
294 aria-label={hasResults ? "Settings search results" : undefined}
295 >
296 {results.map((item, index) => (
297 <SidebarMenuItem key={item.id} role="presentation">
298 <SidebarMenuButton
299 id={`settings-search-result-${item.id}`}
300 role="option"
301 aria-selected={index === activeResultIndex}
302 tabIndex={-1}
303 size="sm"
304 isActive={index === activeResultIndex}
305 className="h-auto min-h-10 items-start gap-2 rounded-md px-2 py-2 text-left hover:bg-sidebar-row-hover hover:text-sidebar-foreground"
306 onMouseMove={() => setActiveResultIndex(index)}
307 onClick={() => handleSearchResultClick(item)}
308 >
309 <SettingsSectionIcon to={item.to} />
310 <span className="min-w-0 flex-1">
311 <span className="block truncate text-sm font-medium text-sidebar-foreground">
312 {item.title}
313 </span>
314 <span className="block truncate text-[11px] text-sidebar-muted-foreground/75">
315 {SETTINGS_SECTION_LABELS[item.to]}
316 </span>
317 </span>
318 </SidebarMenuButton>
319 </SidebarMenuItem>
320 ))}
321 </SidebarMenu>
322 ) : (
323 <SidebarMenu className="ps-px">
324 {navItems.map((item) => {
325 const Icon = item.icon;
326 const isGeneralDetailPage =
327 item.to === "/settings/general" && pathname === "/settings/open-source-licenses";
328 const isActive =
329 isGeneralDetailPage || pathname === item.to || pathname.startsWith(`${item.to}/`);
330 return (
331 <SidebarMenuItem key={item.to}>
287 No settings found
288 </p>
289 ) : null}
290 {isSearching ? (
291 <SidebarMenu
292 id={hasResults ? "settings-search-results" : undefined}
293 role={hasResults ? "listbox" : undefined}
294 aria-label={hasResults ? "Settings search results" : undefined}
295 >
296 {results.map((item, index) => (
297 <SidebarMenuItem key={item.id} role="presentation">
332298 <SidebarMenuButton
333 isActive={isActive}
334 onClick={() => handleSectionClick(item.to)}
299 id={`settings-search-result-${item.id}`}
300 role="option"
301 aria-selected={index === activeResultIndex}
302 tabIndex={-1}
303 size="sm"
304 isActive={index === activeResultIndex}
305 className="h-auto min-h-10 items-start"
306 onMouseMove={() => setActiveResultIndex(index)}
307 onClick={() => handleSearchResultClick(item)}
335308 >
336 <Icon />
337 <span className="truncate">{item.label}</span>
309 <SettingsSectionIcon to={item.to} />
310 <span className="min-w-0 flex-1">
311 <span className="block truncate text-sm font-medium text-sidebar-foreground">
312 {item.title}
313 </span>
314 <span className="block truncate text-[11px] text-sidebar-muted-foreground/75">
315 {SETTINGS_SECTION_LABELS[item.to]}
316 </span>
317 </span>
338318 </SidebarMenuButton>
339319 </SidebarMenuItem>
340 );
341 })}
342 </SidebarMenu>
343 )}
320 ))}
321 </SidebarMenu>
322 ) : (
323 <SidebarMenu>
324 {navItems.map((item) => {
325 const Icon = item.icon;
326 const isGeneralDetailPage =
327 item.to === "/settings/general" &&
328 pathname === "/settings/open-source-licenses";
329 const isActive =
330 isGeneralDetailPage ||
331 pathname === item.to ||
332 pathname.startsWith(`${item.to}/`);
333 return (
334 <SidebarMenuItem key={item.to}>
335 <SidebarMenuButton
336 isActive={isActive}
337 onClick={() => handleSectionClick(item.to)}
338 >
339 <Icon />
340 <span className="truncate">{item.label}</span>
341 </SidebarMenuButton>
342 </SidebarMenuItem>
343 );
344 })}
345 </SidebarMenu>
346 )}
347 </div>
344348 </SidebarGroup>
345349 </SidebarContent>
346350 <SidebarFooter>
apps/web/src/components/settings/ThemeColorPicker.tsx+2-2
......@@ -284,7 +284,7 @@ function ThemeColorPicker({
284284 </Tooltip>
285285 <PopoverPopup
286286 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"
288288 data-theme-editor-panel=""
289289 side="bottom"
290290 sideOffset={10}
......@@ -353,7 +353,7 @@ export const ThemeColorField = memo(function ThemeColorField({
353353 <Input
354354 aria-invalid={!isColorValue}
355355 aria-label={`${label} hex value`}
356 className="w-28 shrink-0 [&_[data-slot=input]]:text-right"
356 className="w-28 shrink-0"
357357 font="mono"
358358 id={`${role}-hex`}
359359 nativeInput
apps/web/src/components/settings/ThemeEditorPanel.tsx+1-5
......@@ -947,11 +947,7 @@ export function ThemeEditorPanel({
947947 // A locked mode stays hoverable so the tooltip can say why it is off;
948948 // a real disabled attribute would swallow the pointer events.
949949 const button = (
950 <Toggle
951 aria-disabled={lockReason !== null}
952 value={appearance}
953 className={lockReason !== null ? "opacity-50" : undefined}
954 >
950 <Toggle aria-disabled={lockReason !== null} value={appearance}>
955951 {appearance === "light" ? "Light" : "Dark"}
956952 </Toggle>
957953 );
apps/web/src/components/sidebar/SidebarChrome.tsx+7-7
......@@ -17,7 +17,6 @@ import {
1717import { Badge } from "../ui/badge";
1818import {
1919 SidebarFooter,
20 SidebarHeader,
2120 SidebarMenu,
2221 SidebarMenuButton,
2322 SidebarMenuItem,
......@@ -48,18 +47,19 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
4847 : null;
4948
5049 return (
51 <SidebarHeader
50 // The titlebar row, not a padded SidebarHeader: it aligns to the window controls.
51 <div
5252 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",
5454 isElectron && "drag-region",
5555 )}
5656 >
5757 {backdropVariant ? <SidebarStageBackdrop variant={backdropVariant} /> : null}
5858 <SidebarTrigger
59 // Over the stage artwork: the media viewer's control-on-imagery treatment.
60 variant={backdropVariant ? "media-navigation" : "ghost"}
5961 className={cn(
60 "relative z-10 md:hidden",
61 backdropVariant &&
62 "focus-visible:ring-white/90 [&_svg]:stroke-white/90! [&_svg]:opacity-100! [&_svg]:hover:stroke-white! [:hover,[data-pressed]]:bg-white/15",
62 "relative top-auto z-10 translate-y-0 md:hidden",
6363 backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant),
6464 )}
6565 />
......@@ -74,7 +74,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
7474 {pillLabel}
7575 </Badge>
7676 ) : null}
77 </SidebarHeader>
77 </div>
7878 );
7979});
8080
apps/web/src/components/sidebar/SidebarThreadHeader.tsx+1-4
......@@ -187,10 +187,7 @@ export function SidebarHeaderIconButton({
187187 type="button"
188188 aria-label={label}
189189 {...rest}
190 className={cn(
191 "relative size-7 shrink-0 focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar",
192 className,
193 )}
190 className={cn("relative size-7 shrink-0", className)}
194191 />
195192 }
196193 >
apps/web/src/components/sidebar/SidebarUpdatePill.tsx-1
......@@ -393,7 +393,6 @@ function SidebarUpdateControl() {
393393 <PopoverPopup
394394 align="center"
395395 aria-label="Nightly update release notes"
396 className="text-balance shadow-xl shadow-black/25"
397396 initialFocus={false}
398397 onKeyDownCapture={(event) => {
399398 if (
apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx+2-2
......@@ -85,7 +85,7 @@ export function SidebarUpdateReleaseNotes({
8585
8686 return (
8787 <div key={releaseNote.version}>
88 {index > 0 && <Separator className="my-3 bg-border/60" />}
88 {index > 0 && <Separator className="my-3" />}
8989 <section>
9090 <h3 className="text-foreground text-xs leading-4 font-semibold">
9191 {index === 0 ? "What's changed" : `Changes in ${releaseNote.version}`}
......@@ -108,7 +108,7 @@ export function SidebarUpdateReleaseNotes({
108108 })}
109109 {state.omittedReleaseCount > 0 ? (
110110 <div>
111 <Separator className="my-3 bg-border/60" />
111 <Separator className="my-3" />
112112 <ReleaseLink releaseUrl={getDesktopUpdateReleaseHistoryUrl()} shell={shell}>
113113 {`${state.omittedReleaseCount} older ${state.omittedReleaseCount === 1 ? "release" : "releases"} on GitHub`}
114114 </ReleaseLink>
apps/web/src/components/ui/toggle.tsx+1-1
......@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
66import { cn } from "~/lib/utils";
77
88const 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",
1010 {
1111 defaultVariants: {
1212 size: "default",
apps/web/src/components/usage/UsageLimitsPooled.tsx+2-1
......@@ -345,7 +345,8 @@ function LegendRow({
345345 return (
346346 <PopoverTrigger
347347 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"
349350 >
350351 <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">
351352 <span
apps/web/src/components/usage/UsagePriceOverrides.tsx+18-21
......@@ -323,12 +323,12 @@ export function UsagePriceOverrides({
323323 <col className="w-10" />
324324 </colgroup>
325325 <TableHeader>
326 <TableRow className="hover:bg-transparent">
327 <TableHead className="pl-3">Model ID</TableHead>
326 <TableRow>
327 <TableHead>Model ID</TableHead>
328328 {USAGE_PRICE_FIELDS.map((field) => (
329329 <TableHead key={field.key}>{field.label}</TableHead>
330330 ))}
331 <TableHead className="px-1">
331 <TableHead>
332332 <Button
333333 size="icon-xs"
334334 variant="ghost"
......@@ -347,24 +347,19 @@ export function UsagePriceOverrides({
347347 </TableHeader>
348348 <TableBody>
349349 {rows.length === 0 ? (
350 <TableRow className="hover:bg-transparent">
351 <TableCell
352 colSpan={6}
353 className="py-8 text-center whitespace-normal text-muted-foreground"
354 >
355 {selected.some((environment) => environment.prices === null)
356 ? "Some environment prices are unavailable."
357 : "No custom prices. Add a row to override automatic pricing."}
350 <TableRow>
351 <TableCell colSpan={6} className="text-center whitespace-normal">
352 <p className="py-6 text-muted-foreground">
353 {selected.some((environment) => environment.prices === null)
354 ? "Some environment prices are unavailable."
355 : "No custom prices. Add a row to override automatic pricing."}
356 </p>
358357 </TableCell>
359358 </TableRow>
360359 ) : (
361360 rows.map((row) => (
362 <TableRow
363 key={row.id}
364 data-row-id={row.id}
365 className="hover:bg-transparent"
366 >
367 <TableCell className="pl-3 whitespace-normal">
361 <TableRow key={row.id} data-row-id={row.id}>
362 <TableCell className="whitespace-normal">
368363 {row.isNew ? (
369364 <Input
370365 size="compact"
......@@ -407,14 +402,16 @@ export function UsagePriceOverrides({
407402 ) : null}
408403 </TableCell>
409404 {row.removed ? (
410 <TableCell colSpan={4} className="text-muted-foreground">
411 Automatic pricing after saving
405 <TableCell colSpan={4}>
406 <span className="text-muted-foreground">
407 Automatic pricing after saving
408 </span>
412409 </TableCell>
413410 ) : (
414411 USAGE_PRICE_FIELDS.map((field) => {
415412 const cell = usagePriceCell(selected, row.model, field.key);
416413 return (
417 <TableCell key={field.key} className="px-1">
414 <TableCell key={field.key}>
418415 <Input
419416 size="compact"
420417 inputMode="decimal"
......@@ -438,7 +435,7 @@ export function UsagePriceOverrides({
438435 );
439436 })
440437 )}
441 <TableCell className="px-1">
438 <TableCell>
442439 <Tooltip>
443440 <TooltipTrigger
444441 render={<Button size="icon-xs" variant="ghost" />}
apps/web/src/routes/_chat.pull-requests.tsx+1-1
......@@ -218,7 +218,7 @@ function PullRequestGroupHeader({
218218 <Icon aria-hidden className="size-3.5 shrink-0" />
219219 <h2 className="shrink-0">{group.label}</h2>
220220 <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" />
222222 </div>
223223 );
224224}
scripts/lint-restyle-ceiling.ts+1-1
......@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
88// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
99// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
1010// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 402;
11export const RESTYLE_CEILING = 250;
1212
1313const RULE = "shadcn(no-restyle)";
1414