| author | |
| committer | |
| log | d81278aa6b272bb3d3314266c6d17f22504d19d6 |
| tree | 8778a96861d26deb211be26f8b2b3d8e568652f5 |
| parent | 1bbca0e78202c8ece73351fccd29f3c99070b82e |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>22 files changed, 208 insertions(+), 1036 deletions(-)
apps/desktop/src/settings/DesktopClientSettings.test.ts-1| ... | @@ -57,7 +57,6 @@ const clientSettings: ClientSettings = { | ... | @@ -57,7 +57,6 @@ const clientSettings: ClientSettings = { |
| 57 | proactivePanelsEnabled: true, | 57 | proactivePanelsEnabled: true, |
| 58 | showSkillsInSlashMenu: false, | 58 | showSkillsInSlashMenu: false, |
| 59 | providerModelPreferences: {}, | 59 | providerModelPreferences: {}, |
| 60 | sidebarCompactThreadRows: false, | ||
| 61 | sidebarProjectGroupingMode: "repository_path", | 60 | sidebarProjectGroupingMode: "repository_path", |
| 62 | sidebarProjectGroupingOverrides: { | 61 | sidebarProjectGroupingOverrides: { |
| 63 | "environment-1:/tmp/project-a": "separate", | 62 | "environment-1:/tmp/project-a": "separate", |
apps/web/src/components/AppSidebarLayout.tsx+2-7| ... | @@ -14,11 +14,7 @@ import { getLocalStorageItem, removeLocalStorageItem } from "../hooks/useLocalSt | ... | @@ -14,11 +14,7 @@ import { getLocalStorageItem, removeLocalStorageItem } from "../hooks/useLocalSt |
| 14 | import { resolveShortcutCommand, shortcutLabelForCommand } from "../keybindings"; | 14 | import { resolveShortcutCommand, shortcutLabelForCommand } from "../keybindings"; |
| 15 | import { cn, isMacPlatform } from "../lib/utils"; | 15 | import { cn, isMacPlatform } from "../lib/utils"; |
| 16 | import { primaryServerKeybindingsAtom } from "../state/server"; | 16 | import { primaryServerKeybindingsAtom } from "../state/server"; |
| 17 | import { | 17 | import { useEnvironmentIdentificationMode, useLegacySidebarEnabled } from "../hooks/useSettings"; |
| 18 | useCompactSidebarEnabled, | ||
| 19 | useEnvironmentIdentificationMode, | ||
| 20 | useLegacySidebarEnabled, | ||
| 21 | } from "../hooks/useSettings"; | ||
| 22 | import { | 18 | import { |
| 23 | PanelAnimationSuppressionProvider, | 19 | PanelAnimationSuppressionProvider, |
| 24 | usePanelAnimationSettings, | 20 | usePanelAnimationSettings, |
| ... | @@ -148,7 +144,6 @@ function ProjectProjectionRetention() { | ... | @@ -148,7 +144,6 @@ function ProjectProjectionRetention() { |
| 148 | export function AppSidebarLayout({ children }: { children: ReactNode }) { | 144 | export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 149 | const navigate = useNavigate(); | 145 | const navigate = useNavigate(); |
| 150 | const legacySidebarEnabled = useLegacySidebarEnabled(); | 146 | const legacySidebarEnabled = useLegacySidebarEnabled(); |
| 151 | const compactSidebarEnabled = useCompactSidebarEnabled(); | ||
| 152 | const { active: panelAnimationsActive, durationMs: panelAnimationDurationMs } = | 147 | const { active: panelAnimationsActive, durationMs: panelAnimationDurationMs } = |
| 153 | usePanelAnimationSettings(); | 148 | usePanelAnimationSettings(); |
| 154 | // Settings routes show the settings nav in place of whichever thread | 149 | // Settings routes show the settings nav in place of whichever thread |
| ... | @@ -234,7 +229,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { | ... | @@ -234,7 +229,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { |
| 234 | <ProjectProjectionRetention /> | 229 | <ProjectProjectionRetention /> |
| 235 | <Sidebar | 230 | <Sidebar |
| 236 | side="left" | 231 | side="left" |
| 237 | collapsible={compactSidebarEnabled ? "icon" : "offcanvas"} | 232 | collapsible="offcanvas" |
| 238 | data-app-sidebar="" | 233 | data-app-sidebar="" |
| 239 | className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground" | 234 | className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground" |
| 240 | resizable={{ | 235 | resizable={{ |
apps/web/src/components/LegacySidebar.tsx+25-52| ... | @@ -199,11 +199,7 @@ import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrom | ... | @@ -199,11 +199,7 @@ import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrom |
| 199 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; | 199 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; |
| 200 | import { useIsMobile } from "~/hooks/useMediaQuery"; | 200 | import { useIsMobile } from "~/hooks/useMediaQuery"; |
| 201 | import { CommandDialogTrigger } from "./ui/command"; | 201 | import { CommandDialogTrigger } from "./ui/command"; |
| 202 | import { | 202 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; |
| 203 | useClientSettings, | ||
| 204 | useCompactSidebarEnabled, | ||
| 205 | useUpdateClientSettings, | ||
| 206 | } from "~/hooks/useSettings"; | ||
| 207 | import { primaryServerKeybindingsAtom } from "../state/server"; | 203 | import { primaryServerKeybindingsAtom } from "../state/server"; |
| 208 | import { | 204 | import { |
| 209 | derivePhysicalProjectKey, | 205 | derivePhysicalProjectKey, |
| ... | @@ -1198,9 +1194,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1198,9 +1194,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1198 | const router = useRouter(); | 1194 | const router = useRouter(); |
| 1199 | const queuePendingFileDrop = useSidebarPendingFileDropStore((s) => s.queuePendingFileDrop); | 1195 | const queuePendingFileDrop = useSidebarPendingFileDropStore((s) => s.queuePendingFileDrop); |
| 1200 | const clearPendingFileDrop = useSidebarPendingFileDropStore((s) => s.clearPendingFileDrop); | 1196 | const clearPendingFileDrop = useSidebarPendingFileDropStore((s) => s.clearPendingFileDrop); |
| 1201 | const { isMobile, setOpenMobile, state, setOpen } = useSidebar(); | 1197 | const { isMobile, setOpenMobile } = useSidebar(); |
| 1202 | const compactSidebarEnabled = useCompactSidebarEnabled(); | ||
| 1203 | const isCompact = compactSidebarEnabled && !isMobile && state === "collapsed"; | ||
| 1204 | const markThreadUnread = useUiStateStore((state) => state.markThreadUnread); | 1198 | const markThreadUnread = useUiStateStore((state) => state.markThreadUnread); |
| 1205 | const setProjectExpanded = useUiStateStore((state) => state.setProjectExpanded); | 1199 | const setProjectExpanded = useUiStateStore((state) => state.setProjectExpanded); |
| 1206 | const toggleThreadSelection = useThreadSelectionStore((state) => state.toggleThread); | 1200 | const toggleThreadSelection = useThreadSelectionStore((state) => state.toggleThread); |
| ... | @@ -1451,13 +1445,10 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1451,13 +1445,10 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1451 | if (useThreadSelectionStore.getState().hasSelection()) { | 1445 | if (useThreadSelectionStore.getState().hasSelection()) { |
| 1452 | clearSelection(); | 1446 | clearSelection(); |
| 1453 | } | 1447 | } |
| 1454 | setProjectExpanded(projectPreferenceKeys, isCompact || !projectExpanded); | 1448 | setProjectExpanded(projectPreferenceKeys, !projectExpanded); |
| 1455 | if (isCompact) setOpen(true); | ||
| 1456 | }, | 1449 | }, |
| 1457 | [ | 1450 | [ |
| 1458 | clearSelection, | 1451 | clearSelection, |
| 1459 | isCompact, | ||
| 1460 | setOpen, | ||
| 1461 | dragInProgressRef, | 1452 | dragInProgressRef, |
| 1462 | projectExpanded, | 1453 | projectExpanded, |
| 1463 | projectPreferenceKeys, | 1454 | projectPreferenceKeys, |
| ... | @@ -1474,17 +1465,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1474,17 +1465,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1474 | if (dragInProgressRef.current) { | 1465 | if (dragInProgressRef.current) { |
| 1475 | return; | 1466 | return; |
| 1476 | } | 1467 | } |
| 1477 | setProjectExpanded(projectPreferenceKeys, isCompact || !projectExpanded); | 1468 | setProjectExpanded(projectPreferenceKeys, !projectExpanded); |
| 1478 | if (isCompact) setOpen(true); | ||
| 1479 | }, | 1469 | }, |
| 1480 | [ | 1470 | [dragInProgressRef, projectExpanded, projectPreferenceKeys, setProjectExpanded], |
| 1481 | dragInProgressRef, | ||
| 1482 | isCompact, | ||
| 1483 | projectExpanded, | ||
| 1484 | projectPreferenceKeys, | ||
| 1485 | setOpen, | ||
| 1486 | setProjectExpanded, | ||
| 1487 | ], | ||
| 1488 | ); | 1471 | ); |
| 1489 | 1472 | ||
| 1490 | const handleProjectButtonPointerDownCapture = useCallback( | 1473 | const handleProjectButtonPointerDownCapture = useCallback( |
| ... | @@ -2376,10 +2359,8 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2376,10 +2359,8 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2376 | <> | 2359 | <> |
| 2377 | <div className="group/project-header relative"> | 2360 | <div className="group/project-header relative"> |
| 2378 | <SidebarMenuButton | 2361 | <SidebarMenuButton |
| 2379 | tooltip={project.displayName} | ||
| 2380 | aria-label={project.displayName} | ||
| 2381 | ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined} | 2362 | ref={isManualProjectSorting ? dragHandleProps?.setActivatorNodeRef : undefined} |
| 2382 | className={`pr-8 group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 group-data-[collapsible=icon]:justify-center ${ | 2363 | className={`pr-8 group-hover/project-header:bg-sidebar-row-hover group-hover/project-header:text-sidebar-foreground max-sm:pr-14 ${ |
| 2383 | isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "" | 2364 | isManualProjectSorting ? "cursor-grab active:cursor-grabbing" : "" |
| 2384 | }`} | 2365 | }`} |
| 2385 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} | 2366 | {...(isManualProjectSorting && dragHandleProps ? dragHandleProps.attributes : {})} |
| ... | @@ -2389,7 +2370,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2389,7 +2370,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2389 | onKeyDown={handleProjectButtonKeyDown} | 2370 | onKeyDown={handleProjectButtonKeyDown} |
| 2390 | onContextMenu={handleProjectButtonContextMenu} | 2371 | onContextMenu={handleProjectButtonContextMenu} |
| 2391 | > | 2372 | > |
| 2392 | {isCompact ? null : !projectExpanded && projectStatus ? ( | 2373 | {!projectExpanded && projectStatus ? ( |
| 2393 | <Tooltip> | 2374 | <Tooltip> |
| 2394 | <TooltipTrigger | 2375 | <TooltipTrigger |
| 2395 | render={ | 2376 | render={ |
| ... | @@ -2420,7 +2401,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2420,7 +2401,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2420 | <span className="flex shrink-0"> | 2401 | <span className="flex shrink-0"> |
| 2421 | <ProjectFavicon project={project} /> | 2402 | <ProjectFavicon project={project} /> |
| 2422 | </span> | 2403 | </span> |
| 2423 | <span className="flex min-w-0 flex-1 items-center gap-2 group-data-[collapsible=icon]:hidden"> | 2404 | <span className="flex min-w-0 flex-1 items-center gap-2"> |
| 2424 | <span className="truncate text-sm font-medium text-sidebar-foreground/90"> | 2405 | <span className="truncate text-sm font-medium text-sidebar-foreground/90"> |
| 2425 | {project.displayName} | 2406 | {project.displayName} |
| 2426 | </span> | 2407 | </span> |
| ... | @@ -2434,7 +2415,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2434,7 +2415,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2434 | {/* Environment badge – visible by default, crossfades with the | 2415 | {/* Environment badge – visible by default, crossfades with the |
| 2435 | "new thread" button on hover using the same pointer-events + | 2416 | "new thread" button on hover using the same pointer-events + |
| 2436 | opacity pattern as the thread row archive/timestamp swap. */} | 2417 | opacity pattern as the thread row archive/timestamp swap. */} |
| 2437 | {!isCompact && project.environmentPresence === "remote-only" && ( | 2418 | {project.environmentPresence === "remote-only" && ( |
| 2438 | <Tooltip> | 2419 | <Tooltip> |
| 2439 | <TooltipTrigger | 2420 | <TooltipTrigger |
| 2440 | render={ | 2421 | render={ |
| ... | @@ -2460,7 +2441,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -2460,7 +2441,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 2460 | <Tooltip> | 2441 | <Tooltip> |
| 2461 | <TooltipTrigger | 2442 | <TooltipTrigger |
| 2462 | render={ | 2443 | render={ |
| 2463 | <div className="pointer-events-none absolute top-[calc(50%+1px)] right-0.5 -translate-y-1/2 opacity-0 transition-opacity duration-150 max-sm:pointer-events-auto max-sm:opacity-100 group-hover/project-header:pointer-events-auto group-hover/project-header:opacity-100 group-focus-within/project-header:pointer-events-auto group-focus-within/project-header:opacity-100 group-data-[collapsible=icon]:hidden"> | 2444 | <div className="pointer-events-none absolute top-[calc(50%+1px)] right-0.5 -translate-y-1/2 opacity-0 transition-opacity duration-150 max-sm:pointer-events-auto max-sm:opacity-100 group-hover/project-header:pointer-events-auto group-hover/project-header:opacity-100 group-focus-within/project-header:pointer-events-auto group-focus-within/project-header:opacity-100"> |
| 2464 | <button | 2445 | <button |
| 2465 | type="button" | 2446 | type="button" |
| 2466 | aria-label={`Create new thread in ${project.displayName}`} | 2447 | aria-label={`Create new thread in ${project.displayName}`} |
| ... | @@ -2693,7 +2674,7 @@ function LocalSecondaryStatus() { | ... | @@ -2693,7 +2674,7 @@ function LocalSecondaryStatus() { |
| 2693 | } | 2674 | } |
| 2694 | 2675 | ||
| 2695 | return ( | 2676 | return ( |
| 2696 | <SidebarGroup className="px-2 pt-2 pb-0 group-data-[collapsible=icon]:hidden"> | 2677 | <SidebarGroup className="px-2 pt-2 pb-0"> |
| 2697 | {connecting.length > 0 ? ( | 2678 | {connecting.length > 0 ? ( |
| 2698 | <Alert | 2679 | <Alert |
| 2699 | variant="default" | 2680 | variant="default" |
| ... | @@ -2997,16 +2978,14 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2997,16 +2978,14 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2997 | render={ | 2978 | render={ |
| 2998 | <SidebarMenuButton | 2979 | <SidebarMenuButton |
| 2999 | className="focus-visible:ring-0" | 2980 | className="focus-visible:ring-0" |
| 3000 | tooltip="Search" | ||
| 3001 | aria-label="Search" | ||
| 3002 | data-testid="command-palette-trigger" | 2981 | data-testid="command-palette-trigger" |
| 3003 | /> | 2982 | /> |
| 3004 | } | 2983 | } |
| 3005 | > | 2984 | > |
| 3006 | <SearchIcon /> | 2985 | <SearchIcon /> |
| 3007 | <span className="flex-1 truncate group-data-[collapsible=icon]:hidden">Search</span> | 2986 | <span className="flex-1 truncate">Search</span> |
| 3008 | {commandPaletteShortcutLabel ? ( | 2987 | {commandPaletteShortcutLabel ? ( |
| 3009 | <Kbd className="h-4 min-w-0 rounded-sm px-1.5 text-[10px] group-data-[collapsible=icon]:hidden"> | 2988 | <Kbd className="h-4 min-w-0 rounded-sm px-1.5 text-[10px]"> |
| 3010 | {commandPaletteShortcutLabel} | 2989 | {commandPaletteShortcutLabel} |
| 3011 | </Kbd> | 2990 | </Kbd> |
| 3012 | ) : null} | 2991 | ) : null} |
| ... | @@ -3017,7 +2996,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -3017,7 +2996,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 3017 | } | 2996 | } |
| 3018 | > | 2997 | > |
| 3019 | {showArm64IntelBuildWarning && arm64IntelBuildWarningDescription ? ( | 2998 | {showArm64IntelBuildWarning && arm64IntelBuildWarningDescription ? ( |
| 3020 | <SidebarGroup className="px-2 pt-2 pb-0 group-data-[collapsible=icon]:hidden"> | 2999 | <SidebarGroup className="px-2 pt-2 pb-0"> |
| 3021 | <Alert variant="warning" className="rounded-2xl border-warning/40 bg-warning/8"> | 3000 | <Alert variant="warning" className="rounded-2xl border-warning/40 bg-warning/8"> |
| 3022 | <TriangleAlertIcon /> | 3001 | <TriangleAlertIcon /> |
| 3023 | <AlertTitle>Intel build on Apple Silicon</AlertTitle> | 3002 | <AlertTitle>Intel build on Apple Silicon</AlertTitle> |
| ... | @@ -3041,21 +3020,17 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -3041,21 +3020,17 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 3041 | ) : null} | 3020 | ) : null} |
| 3042 | <LocalSecondaryStatus /> | 3021 | <LocalSecondaryStatus /> |
| 3043 | <SidebarGroup className="px-2 py-2"> | 3022 | <SidebarGroup className="px-2 py-2"> |
| 3044 | <div className="mb-1 flex items-center justify-between pl-2 pr-1.5 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0"> | 3023 | <div className="mb-1 flex items-center justify-between pl-2 pr-1.5"> |
| 3045 | <span className="text-xs font-medium text-sidebar-muted-foreground/80 group-data-[collapsible=icon]:hidden"> | 3024 | <span className="text-xs font-medium text-sidebar-muted-foreground/80">Projects</span> |
| 3046 | Projects | ||
| 3047 | </span> | ||
| 3048 | <div className="flex items-center gap-1"> | 3025 | <div className="flex items-center gap-1"> |
| 3049 | <div className="group-data-[collapsible=icon]:hidden"> | 3026 | <ProjectSortMenu |
| 3050 | <ProjectSortMenu | 3027 | projectSortOrder={projectSortOrder} |
| 3051 | projectSortOrder={projectSortOrder} | 3028 | threadSortOrder={threadSortOrder} |
| 3052 | threadSortOrder={threadSortOrder} | 3029 | threadPreviewCount={threadPreviewCount} |
| 3053 | threadPreviewCount={threadPreviewCount} | 3030 | onProjectSortOrderChange={handleProjectSortOrderChange} |
| 3054 | onProjectSortOrderChange={handleProjectSortOrderChange} | 3031 | onThreadSortOrderChange={handleThreadSortOrderChange} |
| 3055 | onThreadSortOrderChange={handleThreadSortOrderChange} | 3032 | onThreadPreviewCountChange={handleThreadPreviewCountChange} |
| 3056 | onThreadPreviewCountChange={handleThreadPreviewCountChange} | 3033 | /> |
| 3057 | /> | ||
| 3058 | </div> | ||
| 3059 | <Tooltip> | 3034 | <Tooltip> |
| 3060 | <TooltipTrigger | 3035 | <TooltipTrigger |
| 3061 | render={ | 3036 | render={ |
| ... | @@ -3152,9 +3127,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -3152,9 +3127,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 3152 | )} | 3127 | )} |
| 3153 | 3128 | ||
| 3154 | {projectsLength === 0 && ( | 3129 | {projectsLength === 0 && ( |
| 3155 | <div className="px-2 pt-4 text-center text-secondary-label text-xs group-data-[collapsible=icon]:hidden"> | 3130 | <div className="px-2 pt-4 text-center text-secondary-label text-xs">No projects yet</div> |
| 3156 | No projects yet | ||
| 3157 | </div> | ||
| 3158 | )} | 3131 | )} |
| 3159 | </SidebarGroup> | 3132 | </SidebarGroup> |
| 3160 | </SidebarContent> | 3133 | </SidebarContent> |
apps/web/src/components/Sidebar.drag.ts+6-14| ... | @@ -103,7 +103,6 @@ export function createSidebarSortingStrategy(input: { | ... | @@ -103,7 +103,6 @@ export function createSidebarSortingStrategy(input: { |
| 103 | snoozedThreadCount?: number; | 103 | snoozedThreadCount?: number; |
| 104 | cardHeight?: number; | 104 | cardHeight?: number; |
| 105 | slimHeight?: number; | 105 | slimHeight?: number; |
| 106 | compact?: boolean; | ||
| 107 | /** Space each pinned boundary opens for its label while dragging. The | 106 | /** Space each pinned boundary opens for its label while dragging. The |
| 108 | * markers stay zero height at rest, so nothing is reserved until pickup. */ | 107 | * markers stay zero height at rest, so nothing is reserved until pickup. */ |
| 109 | boundaryLabelHeight?: number; | 108 | boundaryLabelHeight?: number; |
| ... | @@ -141,14 +140,11 @@ export function createSidebarSortingStrategy(input: { | ... | @@ -141,14 +140,11 @@ export function createSidebarSortingStrategy(input: { |
| 141 | else slimHeight ??= rects[index]?.height; | 140 | else slimHeight ??= rects[index]?.height; |
| 142 | if (item.key !== active.key) groups[item.section].push(item); | 141 | if (item.key !== active.key) groups[item.section].push(item); |
| 143 | } | 142 | } |
| 144 | // Compact icons use h-7; expanded cards include their vertical padding. | 143 | // Cards are 4.875rem + 0.25rem padding; slim rows/placeholders are h-9. |
| 145 | const scale = input.compact | 144 | const scale = |
| 146 | ? (cardHeight ?? slimHeight ?? 28) / 28 | 145 | slimHeight !== undefined ? slimHeight / 36 : (headerScale ?? (cardHeight ?? 82) / 82); |
| 147 | : slimHeight !== undefined | 146 | cardHeight ??= 82 * scale; |
| 148 | ? slimHeight / 36 | 147 | slimHeight ??= 36 * scale; |
| 149 | : (headerScale ?? (cardHeight ?? 82) / 82); | ||
| 150 | cardHeight ??= (input.compact ? 28 : 82) * scale; | ||
| 151 | slimHeight ??= (input.compact ? 28 : 36) * scale; | ||
| 152 | const labelHeight = (input.boundaryLabelHeight ?? 0) * scale; | 148 | const labelHeight = (input.boundaryLabelHeight ?? 0) * scale; |
| 153 | const group = groups[target.section]; | 149 | const group = groups[target.section]; |
| 154 | const order = | 150 | const order = |
| ... | @@ -222,11 +218,7 @@ export function createSidebarSortingStrategy(input: { | ... | @@ -222,11 +218,7 @@ export function createSidebarSortingStrategy(input: { |
| 222 | // Consume the shelf's auto margin as drag labels and resized rows need | 218 | // Consume the shelf's auto margin as drag labels and resized rows need |
| 223 | // room, keeping the combined shelves at their measured bottom. | 219 | // room, keeping the combined shelves at their measured bottom. |
| 224 | let shelfSpace = | 220 | let shelfSpace = |
| 225 | !input.compact && | 221 | shelfRect && beforeShelf && lastRect && shelfRect.top > beforeShelf.bottom + 1 |
| 226 | shelfRect && | ||
| 227 | beforeShelf && | ||
| 228 | lastRect && | ||
| 229 | shelfRect.top > beforeShelf.bottom + 1 | ||
| 230 | ? Math.max( | 222 | ? Math.max( |
| 231 | 0, | 223 | 0, |
| 232 | lastRect.bottom - rects[0].top - heights.reduce((sum, height) => sum + height + 1, -1), | 224 | lastRect.bottom - rects[0].top - heights.reduce((sum, height) => sum + height + 1, -1), |
apps/web/src/components/Sidebar.tsx+139-603| ... | @@ -1,12 +1,10 @@ | ... | @@ -1,12 +1,10 @@ |
| 1 | import { useSupportsMultiplePullRequests } from "~/hooks/useSupportsMultiplePullRequests"; | 1 | import { useSupportsMultiplePullRequests } from "~/hooks/useSupportsMultiplePullRequests"; |
| 2 | import { useCompactSidebarEnabled } from "../hooks/useSettings"; | ||
| 3 | import { resolveThreadCurrentPullRequestLink } from "@t3tools/shared/threadPullRequests"; | 2 | import { resolveThreadCurrentPullRequestLink } from "@t3tools/shared/threadPullRequests"; |
| 4 | import { useAtomValue } from "@effect/atom-react"; | 3 | import { useAtomValue } from "@effect/atom-react"; |
| 5 | import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; | 4 | import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; |
| 6 | import * as Schema from "effect/Schema"; | 5 | import * as Schema from "effect/Schema"; |
| 7 | import { | 6 | import { |
| 8 | DndContext, | 7 | DndContext, |
| 9 | DragOverlay, | ||
| 10 | useSensor, | 8 | useSensor, |
| 11 | useSensors, | 9 | useSensors, |
| 12 | type DragEndEvent, | 10 | type DragEndEvent, |
| ... | @@ -74,7 +72,6 @@ import { | ... | @@ -74,7 +72,6 @@ import { |
| 74 | type MouseEvent as ReactMouseEvent, | 72 | type MouseEvent as ReactMouseEvent, |
| 75 | type ReactNode, | 73 | type ReactNode, |
| 76 | } from "react"; | 74 | } from "react"; |
| 77 | import { createPortal } from "react-dom"; | ||
| 78 | import { useParams, useRouter } from "@tanstack/react-router"; | 75 | import { useParams, useRouter } from "@tanstack/react-router"; |
| 79 | 76 | ||
| 80 | import { useRightPanelStore } from "../rightPanelStore"; | 77 | import { useRightPanelStore } from "../rightPanelStore"; |
| ... | @@ -124,7 +121,6 @@ import { startNewThreadFromContext } from "../lib/chatThreadActions"; | ... | @@ -124,7 +121,6 @@ import { startNewThreadFromContext } from "../lib/chatThreadActions"; |
| 124 | import { useClientSettings } from "../hooks/useSettings"; | 121 | import { useClientSettings } from "../hooks/useSettings"; |
| 125 | import { useCopyToClipboard } from "../hooks/useCopyToClipboard"; | 122 | import { useCopyToClipboard } from "../hooks/useCopyToClipboard"; |
| 126 | import { useLocalStorage } from "../hooks/useLocalStorage"; | 123 | import { useLocalStorage } from "../hooks/useLocalStorage"; |
| 127 | import { SidebarCompletedTime } from "./sidebar/SidebarCompletedTime"; | ||
| 128 | import { useNowMinute } from "../hooks/useNowMinute"; | 124 | import { useNowMinute } from "../hooks/useNowMinute"; |
| 129 | import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; | 125 | import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; |
| 130 | import { | 126 | import { |
| ... | @@ -325,7 +321,6 @@ function SidebarThreadTooltip({ | ... | @@ -325,7 +321,6 @@ function SidebarThreadTooltip({ |
| 325 | branchMismatch, | 321 | branchMismatch, |
| 326 | terminalStatus, | 322 | terminalStatus, |
| 327 | terminalProcessCount, | 323 | terminalProcessCount, |
| 328 | compactStatus, | ||
| 329 | }: { | 324 | }: { |
| 330 | thread: SidebarThreadSummary; | 325 | thread: SidebarThreadSummary; |
| 331 | project: ProjectFaviconProject | null; | 326 | project: ProjectFaviconProject | null; |
| ... | @@ -342,7 +337,6 @@ function SidebarThreadTooltip({ | ... | @@ -342,7 +337,6 @@ function SidebarThreadTooltip({ |
| 342 | } | null; | 337 | } | null; |
| 343 | terminalStatus: TerminalStatusIndicator | null; | 338 | terminalStatus: TerminalStatusIndicator | null; |
| 344 | terminalProcessCount: number; | 339 | terminalProcessCount: number; |
| 345 | compactStatus?: string | undefined; | ||
| 346 | }) { | 340 | }) { |
| 347 | const driverKind = providerEntry?.driverKind ?? null; | 341 | const driverKind = providerEntry?.driverKind ?? null; |
| 348 | const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId); | 342 | const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId); |
| ... | @@ -359,7 +353,6 @@ function SidebarThreadTooltip({ | ... | @@ -359,7 +353,6 @@ function SidebarThreadTooltip({ |
| 359 | {thread.title} | 353 | {thread.title} |
| 360 | </div> | 354 | </div> |
| 361 | <div className="grid gap-1.5 pl-0.5 text-xs text-muted-foreground"> | 355 | <div className="grid gap-1.5 pl-0.5 text-xs text-muted-foreground"> |
| 362 | {compactStatus ? <div>{compactStatus}</div> : null} | ||
| 363 | {projectDisplayName ? ( | 356 | {projectDisplayName ? ( |
| 364 | <div className="flex min-w-0 items-center gap-2"> | 357 | <div className="flex min-w-0 items-center gap-2"> |
| 365 | {project ? <ProjectFavicon project={project} className="size-3 shrink-0" /> : null} | 358 | {project ? <ProjectFavicon project={project} className="size-3 shrink-0" /> : null} |
| ... | @@ -509,7 +502,7 @@ function SnoozePopoverButton(props: { | ... | @@ -509,7 +502,7 @@ function SnoozePopoverButton(props: { |
| 509 | type SortableThreadRowBag = Pick< | 502 | type SortableThreadRowBag = Pick< |
| 510 | ReturnType<typeof useSortable>, | 503 | ReturnType<typeof useSortable>, |
| 511 | "listeners" | "setNodeRef" | "transform" | "transition" | "isDragging" | 504 | "listeners" | "setNodeRef" | "transform" | "transition" | "isDragging" |
| 512 | > & { hidden?: boolean }; | 505 | >; |
| 513 | 506 | ||
| 514 | function SortableThreadRow(props: { | 507 | function SortableThreadRow(props: { |
| 515 | id: string; | 508 | id: string; |
| ... | @@ -590,18 +583,7 @@ function SidebarSectionPlaceholder(props: { | ... | @@ -590,18 +583,7 @@ function SidebarSectionPlaceholder(props: { |
| 590 | props.isDropTarget && "border-primary/40 bg-primary/5 text-primary", | 583 | props.isDropTarget && "border-primary/40 bg-primary/5 text-primary", |
| 591 | )} | 584 | )} |
| 592 | > | 585 | > |
| 593 | <span className="group-data-[collapsible=icon]:sr-only">{props.label}</span> | 586 | {props.label} |
| 594 | {props.marker === "settled-placeholder" ? ( | ||
| 595 | <CheckIcon | ||
| 596 | aria-hidden | ||
| 597 | className="hidden size-3.5 group-data-[collapsible=icon]:block" | ||
| 598 | /> | ||
| 599 | ) : ( | ||
| 600 | <Undo2Icon | ||
| 601 | aria-hidden | ||
| 602 | className="hidden size-3.5 group-data-[collapsible=icon]:block" | ||
| 603 | /> | ||
| 604 | )} | ||
| 605 | </div> | 587 | </div> |
| 606 | ) : null} | 588 | ) : null} |
| 607 | </SortableSidebarMarker> | 589 | </SortableSidebarMarker> |
| ... | @@ -625,30 +607,19 @@ function SidebarDragBoundary(props: { | ... | @@ -625,30 +607,19 @@ function SidebarDragBoundary(props: { |
| 625 | className="pointer-events-none relative mx-0.5 -mb-px h-0" | 607 | className="pointer-events-none relative mx-0.5 -mb-px h-0" |
| 626 | > | 608 | > |
| 627 | {props.visible ? ( | 609 | {props.visible ? ( |
| 628 | <div className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-2 group-data-[collapsible=icon]:inset-x-0 group-data-[collapsible=icon]:justify-center"> | 610 | <div className="sidebar-drag-boundary-label absolute inset-x-2 top-1 flex h-4 items-center gap-2"> |
| 629 | <span | 611 | <span |
| 630 | className={cn( | 612 | className={cn( |
| 631 | "shrink-0 text-xs font-medium", | 613 | "shrink-0 text-xs font-medium", |
| 632 | props.isDropTarget ? "text-primary" : "text-sidebar-foreground/80", | 614 | props.isDropTarget ? "text-primary" : "text-sidebar-foreground/80", |
| 633 | )} | 615 | )} |
| 634 | > | 616 | > |
| 635 | <span className="group-data-[collapsible=icon]:sr-only">{props.label}</span> | 617 | {props.label} |
| 636 | {props.marker === "pinned-header" ? ( | ||
| 637 | <PinIcon | ||
| 638 | aria-hidden | ||
| 639 | className="hidden size-3.5 group-data-[collapsible=icon]:block" | ||
| 640 | /> | ||
| 641 | ) : ( | ||
| 642 | <Undo2Icon | ||
| 643 | aria-hidden | ||
| 644 | className="hidden size-3.5 group-data-[collapsible=icon]:block" | ||
| 645 | /> | ||
| 646 | )} | ||
| 647 | </span> | 618 | </span> |
| 648 | <span | 619 | <span |
| 649 | aria-hidden | 620 | aria-hidden |
| 650 | className={cn( | 621 | className={cn( |
| 651 | "h-px flex-1 group-data-[collapsible=icon]:hidden", | 622 | "h-px flex-1", |
| 652 | props.isDropTarget ? "bg-primary/50" : "bg-sidebar-foreground/25", | 623 | props.isDropTarget ? "bg-primary/50" : "bg-sidebar-foreground/25", |
| 653 | )} | 624 | )} |
| 654 | /> | 625 | /> |
| ... | @@ -669,32 +640,20 @@ function SidebarSectionHeader(props: { | ... | @@ -669,32 +640,20 @@ function SidebarSectionHeader(props: { |
| 669 | isDropTarget?: boolean; | 640 | isDropTarget?: boolean; |
| 670 | toggle: { expanded: boolean; onToggle: () => void }; | 641 | toggle: { expanded: boolean; onToggle: () => void }; |
| 671 | }) { | 642 | }) { |
| 672 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 673 | const { state, isMobile } = useSidebar(); | ||
| 674 | const compact = compactEnabled && state === "collapsed" && !isMobile; | ||
| 675 | const snoozed = props.marker === "snoozed-header"; | 643 | const snoozed = props.marker === "snoozed-header"; |
| 676 | const className = cn( | 644 | const className = cn( |
| 677 | "flex h-full w-full items-center gap-2 px-2 text-left text-xs font-medium", | 645 | "flex h-full w-full items-center gap-2 px-2 text-left text-xs font-medium", |
| 678 | compact && "justify-center px-0", | ||
| 679 | snoozed ? "text-blue-600 dark:text-blue-400" : "text-sidebar-muted-foreground/60", | 646 | snoozed ? "text-blue-600 dark:text-blue-400" : "text-sidebar-muted-foreground/60", |
| 680 | props.dragging && "text-sidebar-foreground/80", | 647 | props.dragging && "text-sidebar-foreground/80", |
| 681 | props.isDropTarget && "text-primary", | 648 | props.isDropTarget && "text-primary", |
| 682 | ); | 649 | ); |
| 683 | const content = ( | 650 | const content = ( |
| 684 | <> | 651 | <> |
| 685 | <span className={compact ? "sr-only" : "shrink-0"}>{props.label}</span> | 652 | <span className="shrink-0">{props.label}</span> |
| 686 | {compact ? ( | ||
| 687 | snoozed ? ( | ||
| 688 | <AlarmClockIcon aria-hidden className="size-3.5" /> | ||
| 689 | ) : ( | ||
| 690 | <CheckIcon aria-hidden className="size-3.5" /> | ||
| 691 | ) | ||
| 692 | ) : null} | ||
| 693 | <span | 653 | <span |
| 694 | aria-hidden | 654 | aria-hidden |
| 695 | className={cn( | 655 | className={cn( |
| 696 | "h-px min-w-2 flex-1", | 656 | "h-px min-w-2 flex-1", |
| 697 | compact && "hidden", | ||
| 698 | snoozed ? "bg-blue-500/20 dark:bg-blue-400/15" : "bg-sidebar-border/60", | 657 | snoozed ? "bg-blue-500/20 dark:bg-blue-400/15" : "bg-sidebar-border/60", |
| 699 | props.dragging && "bg-sidebar-foreground/25", | 658 | props.dragging && "bg-sidebar-foreground/25", |
| 700 | props.isDropTarget && "bg-primary/50", | 659 | props.isDropTarget && "bg-primary/50", |
| ... | @@ -704,7 +663,6 @@ function SidebarSectionHeader(props: { | ... | @@ -704,7 +663,6 @@ function SidebarSectionHeader(props: { |
| 704 | aria-hidden | 663 | aria-hidden |
| 705 | className={cn( | 664 | className={cn( |
| 706 | "size-3 shrink-0 transition-transform", | 665 | "size-3 shrink-0 transition-transform", |
| 707 | compact && "hidden", | ||
| 708 | props.toggle.expanded && "rotate-180", | 666 | props.toggle.expanded && "rotate-180", |
| 709 | )} | 667 | )} |
| 710 | /> | 668 | /> |
| ... | @@ -716,22 +674,15 @@ function SidebarSectionHeader(props: { | ... | @@ -716,22 +674,15 @@ function SidebarSectionHeader(props: { |
| 716 | data-testid={`sidebar-${props.marker}`} | 674 | data-testid={`sidebar-${props.marker}`} |
| 717 | className={cn("mx-0.5 h-8", props.className)} | 675 | className={cn("mx-0.5 h-8", props.className)} |
| 718 | > | 676 | > |
| 719 | <Tooltip disabled={!compact}> | 677 | <button |
| 720 | <TooltipTrigger | 678 | type="button" |
| 721 | render={ | 679 | onClick={props.toggle.onToggle} |
| 722 | <button | 680 | aria-expanded={props.toggle.expanded} |
| 723 | type="button" | 681 | data-testid={`sidebar-${snoozed ? "snoozed" : "settled"}-shelf-toggle`} |
| 724 | onClick={props.toggle.onToggle} | 682 | className={cn(className, "cursor-pointer")} |
| 725 | aria-expanded={props.toggle.expanded} | 683 | > |
| 726 | data-testid={`sidebar-${snoozed ? "snoozed" : "settled"}-shelf-toggle`} | 684 | {content} |
| 727 | className={cn(className, "cursor-pointer")} | 685 | </button> |
| 728 | /> | ||
| 729 | } | ||
| 730 | > | ||
| 731 | {content} | ||
| 732 | </TooltipTrigger> | ||
| 733 | <TooltipPopup side="right">{props.label}</TooltipPopup> | ||
| 734 | </Tooltip> | ||
| 735 | </SortableSidebarMarker> | 686 | </SortableSidebarMarker> |
| 736 | ); | 687 | ); |
| 737 | } | 688 | } |
| ... | @@ -754,9 +705,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: { | ... | @@ -754,9 +705,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: { |
| 754 | onDiscard: (draftId: DraftId) => void; | 705 | onDiscard: (draftId: DraftId) => void; |
| 755 | }) { | 706 | }) { |
| 756 | const { composer, draftId, onDiscard, onNavigate, session } = props; | 707 | const { composer, draftId, onDiscard, onNavigate, session } = props; |
| 757 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 758 | const { state, isMobile } = useSidebar(); | ||
| 759 | const compact = compactEnabled && state === "collapsed" && !isMobile; | ||
| 760 | const promptPreview = | 708 | const promptPreview = |
| 761 | replaceComposerContextReferences(composer.prompt, (occurrence) => occurrence.label) | 709 | replaceComposerContextReferences(composer.prompt, (occurrence) => occurrence.label) |
| 762 | .trim() | 710 | .trim() |
| ... | @@ -795,34 +743,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: { | ... | @@ -795,34 +743,6 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: { |
| 795 | }, | 743 | }, |
| 796 | [draftId, onDiscard], | 744 | [draftId, onDiscard], |
| 797 | ); | 745 | ); |
| 798 | if (compact) { | ||
| 799 | return ( | ||
| 800 | <li className="list-none"> | ||
| 801 | <Tooltip> | ||
| 802 | <TooltipTrigger | ||
| 803 | render={ | ||
| 804 | <button | ||
| 805 | type="button" | ||
| 806 | data-testid="sidebar-draft-row" | ||
| 807 | aria-label={`Draft: ${preview}`} | ||
| 808 | onClick={handleActivate} | ||
| 809 | className={cn( | ||
| 810 | "relative flex h-7 w-full cursor-pointer items-center justify-center rounded-md outline-none hover:bg-sidebar-row-hover focus-visible:ring-2 focus-visible:ring-ring", | ||
| 811 | props.isActive ? "bg-sidebar-row-active" : draftSurfaceClassName, | ||
| 812 | )} | ||
| 813 | /> | ||
| 814 | } | ||
| 815 | > | ||
| 816 | <SquarePenIcon aria-hidden className={cn(draftPenClassName, "size-4")} /> | ||
| 817 | </TooltipTrigger> | ||
| 818 | <TooltipPopup side="right" className="max-w-80 whitespace-normal"> | ||
| 819 | <div className="font-medium">{props.projectDisplayName}</div> | ||
| 820 | <div>{preview}</div> | ||
| 821 | </TooltipPopup> | ||
| 822 | </Tooltip> | ||
| 823 | </li> | ||
| 824 | ); | ||
| 825 | } | ||
| 826 | return ( | 746 | return ( |
| 827 | <li className="list-none py-0.5"> | 747 | <li className="list-none py-0.5"> |
| 828 | <div | 748 | <div |
| ... | @@ -1032,7 +952,6 @@ const dropVerbBadge: Record<SidebarDropVerb, ReactNode> = { | ... | @@ -1032,7 +952,6 @@ const dropVerbBadge: Record<SidebarDropVerb, ReactNode> = { |
| 1032 | const SidebarThreadRow = memo(function SidebarThreadRow(props: { | 952 | const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1033 | thread: SidebarThreadSummary; | 953 | thread: SidebarThreadSummary; |
| 1034 | variant: "card" | "slim"; | 954 | variant: "card" | "slim"; |
| 1035 | compact: boolean; | ||
| 1036 | // Slim rows are either settled (action: un-settle) or merely quiet | 955 | // Slim rows are either settled (action: un-settle) or merely quiet |
| 1037 | // (seen Ready threads — action: settle). | 956 | // (seen Ready threads — action: settle). |
| 1038 | variantAction: "settle" | "unsettle" | "unsnooze"; | 957 | variantAction: "settle" | "unsettle" | "unsnooze"; |
| ... | @@ -1113,9 +1032,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1113,9 +1032,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1113 | variant, | 1032 | variant, |
| 1114 | variantAction, | 1033 | variantAction, |
| 1115 | } = props; | 1034 | } = props; |
| 1116 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 1117 | const { state, isMobile } = useSidebar(); | ||
| 1118 | const compact = compactEnabled && state === "collapsed" && !isMobile; | ||
| 1119 | const threadRef = useMemo( | 1035 | const threadRef = useMemo( |
| 1120 | () => scopeThreadRef(thread.environmentId, thread.id), | 1036 | () => scopeThreadRef(thread.environmentId, thread.id), |
| 1121 | [thread.environmentId, thread.id], | 1037 | [thread.environmentId, thread.id], |
| ... | @@ -1293,16 +1209,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1293,16 +1209,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1293 | branchMismatch={branchMismatch} | 1209 | branchMismatch={branchMismatch} |
| 1294 | terminalStatus={terminalStatus} | 1210 | terminalStatus={terminalStatus} |
| 1295 | terminalProcessCount={terminalProcessCount} | 1211 | terminalProcessCount={terminalProcessCount} |
| 1296 | compactStatus={ | ||
| 1297 | compact || (props.compact && variant === "card") | ||
| 1298 | ? (topStatus?.label ?? | ||
| 1299 | (variantAction === "unsnooze" | ||
| 1300 | ? "Snoozed" | ||
| 1301 | : variantAction === "unsettle" | ||
| 1302 | ? "Settled" | ||
| 1303 | : "Ready")) | ||
| 1304 | : undefined | ||
| 1305 | } | ||
| 1306 | /> | 1212 | /> |
| 1307 | ); | 1213 | ); |
| 1308 | 1214 | ||
| ... | @@ -1349,7 +1255,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1349,7 +1255,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1349 | [isRenaming, onStartRename, thread.title, threadRef], | 1255 | [isRenaming, onStartRename, thread.title, threadRef], |
| 1350 | ); | 1256 | ); |
| 1351 | const [isFileDragOver, setIsFileDragOver] = useState(false); | 1257 | const [isFileDragOver, setIsFileDragOver] = useState(false); |
| 1352 | const [tooltipOpen, setTooltipOpen] = useState(false); | ||
| 1353 | const fileDropHandlers = useMemo( | 1258 | const fileDropHandlers = useMemo( |
| 1354 | () => | 1259 | () => |
| 1355 | onFileDropThreads | 1260 | onFileDropThreads |
| ... | @@ -1512,7 +1417,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1512,7 +1417,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1512 | // A zero-height boundary also makes dnd-kit scale the source to | 1417 | // A zero-height boundary also makes dnd-kit scale the source to |
| 1513 | // zero. Only projected peers use scaleY as a visibility sentinel. | 1418 | // zero. Only projected peers use scaleY as a visibility sentinel. |
| 1514 | visibility: | 1419 | visibility: |
| 1515 | sortable.hidden || (!sortable.isDragging && sortable.transform?.scaleY === 0) | 1420 | !sortable.isDragging && sortable.transform?.scaleY === 0 |
| 1516 | ? ("hidden" as const) | 1421 | ? ("hidden" as const) |
| 1517 | : undefined, | 1422 | : undefined, |
| 1518 | }, | 1423 | }, |
| ... | @@ -1584,21 +1489,18 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1584,21 +1489,18 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1584 | useRightPanelStore.getState().open(threadRef, "pull-requests"); | 1489 | useRightPanelStore.getState().open(threadRef, "pull-requests"); |
| 1585 | if (!props.isActive) onThreadActivate(threadRef); | 1490 | if (!props.isActive) onThreadActivate(threadRef); |
| 1586 | }, [onThreadActivate, props.isActive, threadRef]); | 1491 | }, [onThreadActivate, props.isActive, threadRef]); |
| 1587 | const renderPrBadge = (iconOnly: boolean, variant: "underline" | "badge" = "underline") => | 1492 | const prBadge = |
| 1588 | prBadgeShape?.kind === "stack" || pr || currentLinkedPr ? ( | 1493 | prBadgeShape?.kind === "stack" || pr || currentLinkedPr ? ( |
| 1589 | <ThreadPullRequestBadgeControl | 1494 | <ThreadPullRequestBadgeControl |
| 1590 | variant={variant} | 1495 | variant="underline" |
| 1591 | badge={prBadgeShape} | 1496 | badge={prBadgeShape} |
| 1592 | number={pr?.number ?? currentLinkedPr?.number} | 1497 | number={pr?.number ?? currentLinkedPr?.number} |
| 1593 | url={pr?.url ?? currentLinkedPr?.url} | 1498 | url={pr?.url ?? currentLinkedPr?.url} |
| 1594 | status={prStatus} | 1499 | status={prStatus} |
| 1595 | iconOnly={iconOnly} | ||
| 1596 | onOpenStack={handlePrStackClick} | 1500 | onOpenStack={handlePrStackClick} |
| 1597 | onOpenPullRequest={handlePrClick} | 1501 | onOpenPullRequest={handlePrClick} |
| 1598 | /> | 1502 | /> |
| 1599 | ) : null; | 1503 | ) : null; |
| 1600 | const hasPrBadge = prBadgeShape?.kind === "stack" || pr !== null || currentLinkedPr !== null; | ||
| 1601 | const prBadge = renderPrBadge(false); | ||
| 1602 | const terminalStatusIcon = terminalStatus ? ( | 1504 | const terminalStatusIcon = terminalStatus ? ( |
| 1603 | <span | 1505 | <span |
| 1604 | role="img" | 1506 | role="img" |
| ... | @@ -1656,102 +1558,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1656,102 +1558,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1656 | ) | 1558 | ) |
| 1657 | ) : null; | 1559 | ) : null; |
| 1658 | 1560 | ||
| 1659 | if (compact) { | ||
| 1660 | return ( | ||
| 1661 | <li | ||
| 1662 | data-thread-item | ||
| 1663 | {...sortableRootProps} | ||
| 1664 | {...(fileDropHandlers ?? {})} | ||
| 1665 | className={cn("list-none", sortable?.isDragging && "relative z-20")} | ||
| 1666 | > | ||
| 1667 | <Tooltip | ||
| 1668 | open={ | ||
| 1669 | sortable?.hidden ? false : sortable?.isDragging ? dragDestination !== null : tooltipOpen | ||
| 1670 | } | ||
| 1671 | onOpenChange={setTooltipOpen} | ||
| 1672 | > | ||
| 1673 | <TooltipTrigger | ||
| 1674 | render={ | ||
| 1675 | <div | ||
| 1676 | ref={rowRef} | ||
| 1677 | role="button" | ||
| 1678 | tabIndex={0} | ||
| 1679 | aria-label={`${thread.title}${topStatus ? `, ${topStatus.label}` : ""}`} | ||
| 1680 | aria-current={props.isActive ? "page" : undefined} | ||
| 1681 | aria-busy={isRegeneratingTitle || undefined} | ||
| 1682 | className={cn( | ||
| 1683 | rowSurfaceClassName, | ||
| 1684 | "flex h-7 items-center justify-center focus-visible:ring-2 focus-visible:ring-ring", | ||
| 1685 | )} | ||
| 1686 | onClick={handleClick} | ||
| 1687 | onDoubleClick={handleDoubleClick} | ||
| 1688 | onKeyDown={handleKeyDown} | ||
| 1689 | onContextMenu={handleContextMenu} | ||
| 1690 | /> | ||
| 1691 | } | ||
| 1692 | > | ||
| 1693 | <span className="relative inline-flex size-4 shrink-0 items-center justify-center"> | ||
| 1694 | {props.project ? ( | ||
| 1695 | <ProjectFavicon project={props.project} className="size-4" /> | ||
| 1696 | ) : driverKind ? ( | ||
| 1697 | <ProviderInstanceIcon | ||
| 1698 | driverKind={driverKind} | ||
| 1699 | displayName={providerEntry?.displayName ?? modelInstanceId} | ||
| 1700 | iconClassName="size-4" | ||
| 1701 | /> | ||
| 1702 | ) : ( | ||
| 1703 | <SquarePenIcon aria-hidden className="size-4" /> | ||
| 1704 | )} | ||
| 1705 | {isRemote ? ( | ||
| 1706 | <Tooltip> | ||
| 1707 | <TooltipTrigger | ||
| 1708 | render={ | ||
| 1709 | <span | ||
| 1710 | role="img" | ||
| 1711 | aria-label={props.environmentLabel ?? "Remote environment"} | ||
| 1712 | className="absolute -left-1 -bottom-1 inline-flex size-3 items-center justify-center rounded-full bg-sidebar text-sidebar-muted-foreground ring-1 ring-sidebar" | ||
| 1713 | /> | ||
| 1714 | } | ||
| 1715 | > | ||
| 1716 | <EnvironmentMachineIcon kind={props.environmentMachine} className="size-2.5" /> | ||
| 1717 | </TooltipTrigger> | ||
| 1718 | <TooltipPopup side="right"> | ||
| 1719 | {props.environmentLabel ?? "Remote environment"} | ||
| 1720 | </TooltipPopup> | ||
| 1721 | </Tooltip> | ||
| 1722 | ) : null} | ||
| 1723 | {hasPrBadge ? ( | ||
| 1724 | <span className="absolute -top-1 -right-1 inline-flex"> | ||
| 1725 | {renderPrBadge(true, "badge")} | ||
| 1726 | </span> | ||
| 1727 | ) : null} | ||
| 1728 | </span> | ||
| 1729 | {topStatus ? ( | ||
| 1730 | <span | ||
| 1731 | aria-hidden | ||
| 1732 | className={cn( | ||
| 1733 | "absolute right-0.5 bottom-0.5 size-1.5 rounded-full bg-current", | ||
| 1734 | topStatus.className, | ||
| 1735 | )} | ||
| 1736 | /> | ||
| 1737 | ) : hasUnsentDraft ? ( | ||
| 1738 | <span | ||
| 1739 | aria-hidden | ||
| 1740 | className="absolute right-0.5 bottom-0.5 size-1.5 rounded-full bg-primary" | ||
| 1741 | /> | ||
| 1742 | ) : null} | ||
| 1743 | {props.jumpLabel ? <JumpHintBadge label={props.jumpLabel} /> : null} | ||
| 1744 | </TooltipTrigger> | ||
| 1745 | {sortable?.isDragging ? ( | ||
| 1746 | <TooltipPopup side="right">{dragDestination}</TooltipPopup> | ||
| 1747 | ) : ( | ||
| 1748 | detailsTooltip | ||
| 1749 | )} | ||
| 1750 | </Tooltip> | ||
| 1751 | </li> | ||
| 1752 | ); | ||
| 1753 | } | ||
| 1754 | |||
| 1755 | if (variant === "slim") { | 1561 | if (variant === "slim") { |
| 1756 | return ( | 1562 | return ( |
| 1757 | <li | 1563 | <li |
| ... | @@ -1764,11 +1570,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1764,11 +1570,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1764 | sortable?.isDragging && "relative z-20", | 1570 | sortable?.isDragging && "relative z-20", |
| 1765 | )} | 1571 | )} |
| 1766 | > | 1572 | > |
| 1767 | <Tooltip | 1573 | <Tooltip disabled={sortable?.isDragging}> |
| 1768 | open={!sortable?.isDragging && tooltipOpen} | ||
| 1769 | onOpenChange={setTooltipOpen} | ||
| 1770 | disabled={sortable?.isDragging} | ||
| 1771 | > | ||
| 1772 | <TooltipTrigger | 1574 | <TooltipTrigger |
| 1773 | render={ | 1575 | render={ |
| 1774 | <div | 1576 | <div |
| ... | @@ -1908,22 +1710,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1908,22 +1710,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1908 | ); | 1710 | ); |
| 1909 | } | 1711 | } |
| 1910 | 1712 | ||
| 1911 | const compactRows = props.compact; | ||
| 1912 | const CompactStatusIcon = topStatus | ||
| 1913 | ? { | ||
| 1914 | working: CircleDashedIcon, | ||
| 1915 | monitoring: EyeIcon, | ||
| 1916 | approval: ShieldQuestionIcon, | ||
| 1917 | input: MessageCircleQuestionIcon, | ||
| 1918 | failed: CircleAlertIcon, | ||
| 1919 | woke: AlarmClockIcon, | ||
| 1920 | done: CircleCheckIcon, | ||
| 1921 | }[topStatus.icon] | ||
| 1922 | : null; | ||
| 1923 | const compactCompletedAt = | ||
| 1924 | compactRows && status === "ready" && !isWokeStatus | ||
| 1925 | ? (thread.latestTurn?.completedAt ?? null) | ||
| 1926 | : null; | ||
| 1927 | const diff = latestTurnDiff(thread); | 1713 | const diff = latestTurnDiff(thread); |
| 1928 | 1714 | ||
| 1929 | return ( | 1715 | return ( |
| ... | @@ -1932,25 +1718,19 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1932,25 +1718,19 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1932 | {...sortableRootProps} | 1718 | {...sortableRootProps} |
| 1933 | {...(fileDropHandlers ?? {})} | 1719 | {...(fileDropHandlers ?? {})} |
| 1934 | className={cn( | 1720 | className={cn( |
| 1935 | "list-none [content-visibility:auto]", | 1721 | // Matches the h-[4.875rem] content box; the py-0.5 padding is added on top. |
| 1936 | compactRows | 1722 | "list-none py-0.5 [content-visibility:auto] [contain-intrinsic-size:auto_78px]", |
| 1937 | ? "[contain-intrinsic-size:auto_36px]" | ||
| 1938 | : "py-0.5 [contain-intrinsic-size:auto_78px]", | ||
| 1939 | sortable?.isDragging && "relative z-20", | 1723 | sortable?.isDragging && "relative z-20", |
| 1940 | )} | 1724 | )} |
| 1941 | > | 1725 | > |
| 1942 | <Tooltip | 1726 | <Tooltip disabled={snoozeMenuOpen || sortable?.isDragging}> |
| 1943 | open={!snoozeMenuOpen && !sortable?.isDragging && tooltipOpen} | ||
| 1944 | onOpenChange={setTooltipOpen} | ||
| 1945 | disabled={snoozeMenuOpen || sortable?.isDragging} | ||
| 1946 | > | ||
| 1947 | <TooltipTrigger | 1727 | <TooltipTrigger |
| 1948 | render={ | 1728 | render={ |
| 1949 | <div | 1729 | <div |
| 1950 | ref={rowRef} | 1730 | ref={rowRef} |
| 1951 | role="button" | 1731 | role="button" |
| 1952 | tabIndex={0} | 1732 | tabIndex={0} |
| 1953 | data-testid={compactRows ? "sidebar-row-compact" : "sidebar-row-card"} | 1733 | data-testid="sidebar-row-card" |
| 1954 | aria-busy={isRegeneratingTitle || undefined} | 1734 | aria-busy={isRegeneratingTitle || undefined} |
| 1955 | className={rowSurfaceClassName} | 1735 | className={rowSurfaceClassName} |
| 1956 | onClick={handleClick} | 1736 | onClick={handleClick} |
| ... | @@ -1960,62 +1740,13 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -1960,62 +1740,13 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1960 | /> | 1740 | /> |
| 1961 | } | 1741 | } |
| 1962 | > | 1742 | > |
| 1963 | <div | 1743 | <div className="relative z-10 h-[4.875rem] px-[var(--sidebar-row-content-inset)] py-[var(--sidebar-content-inset)]"> |
| 1964 | className={cn( | 1744 | <div className="flex h-5 min-w-0 items-center gap-1.5"> |
| 1965 | "relative z-10", | ||
| 1966 | compactRows | ||
| 1967 | ? "flex h-9 items-center px-2.5" | ||
| 1968 | : "h-[4.875rem] px-[var(--sidebar-row-content-inset)] py-[var(--sidebar-content-inset)]", | ||
| 1969 | )} | ||
| 1970 | > | ||
| 1971 | <div className="flex h-5 w-full min-w-0 items-center gap-1.5"> | ||
| 1972 | {draftIndicator} | 1745 | {draftIndicator} |
| 1973 | {props.project ? ( | 1746 | {props.project ? ( |
| 1974 | <span className="relative inline-flex size-4 shrink-0 items-center justify-center"> | 1747 | <ProjectFavicon project={props.project} className="size-4 shrink-0" /> |
| 1975 | <ProjectFavicon project={props.project} className="size-4" /> | ||
| 1976 | {compactRows && isRemote ? ( | ||
| 1977 | <Tooltip> | ||
| 1978 | <TooltipTrigger | ||
| 1979 | render={ | ||
| 1980 | <span | ||
| 1981 | role="img" | ||
| 1982 | aria-label={props.environmentLabel ?? "Remote environment"} | ||
| 1983 | className="absolute -right-1 -bottom-1 inline-flex size-3 items-center justify-center rounded-full bg-sidebar text-sidebar-muted-foreground ring-1 ring-sidebar" | ||
| 1984 | /> | ||
| 1985 | } | ||
| 1986 | > | ||
| 1987 | <EnvironmentMachineIcon | ||
| 1988 | kind={props.environmentMachine} | ||
| 1989 | className="size-2.5" | ||
| 1990 | /> | ||
| 1991 | </TooltipTrigger> | ||
| 1992 | <TooltipPopup side="top"> | ||
| 1993 | {props.environmentLabel ?? "Remote environment"} | ||
| 1994 | </TooltipPopup> | ||
| 1995 | </Tooltip> | ||
| 1996 | ) : null} | ||
| 1997 | </span> | ||
| 1998 | ) : compactRows && isRemote ? ( | ||
| 1999 | <Tooltip> | ||
| 2000 | <TooltipTrigger | ||
| 2001 | render={ | ||
| 2002 | <span | ||
| 2003 | role="img" | ||
| 2004 | aria-label={props.environmentLabel ?? "Remote environment"} | ||
| 2005 | className="inline-flex size-4 shrink-0 items-center justify-center text-sidebar-muted-foreground/70" | ||
| 2006 | /> | ||
| 2007 | } | ||
| 2008 | > | ||
| 2009 | <EnvironmentMachineIcon kind={props.environmentMachine} className="size-3.5" /> | ||
| 2010 | </TooltipTrigger> | ||
| 2011 | <TooltipPopup side="top"> | ||
| 2012 | {props.environmentLabel ?? "Remote environment"} | ||
| 2013 | </TooltipPopup> | ||
| 2014 | </Tooltip> | ||
| 2015 | ) : null} | 1748 | ) : null} |
| 2016 | {compactRows ? ( | 1749 | {props.projectDisplayName ? ( |
| 2017 | title | ||
| 2018 | ) : props.projectDisplayName ? ( | ||
| 2019 | <span | 1750 | <span |
| 2020 | className={cn( | 1751 | className={cn( |
| 2021 | "min-w-0 flex-1 truncate text-secondary-label text-xs", | 1752 | "min-w-0 flex-1 truncate text-secondary-label text-xs", |
| ... | @@ -2028,38 +1759,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -2028,38 +1759,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 2028 | <span className="flex-1" /> | 1759 | <span className="flex-1" /> |
| 2029 | )} | 1760 | )} |
| 2030 | {pinIndicator} | 1761 | {pinIndicator} |
| 2031 | {compactRows ? ( | ||
| 2032 | <> | ||
| 2033 | {terminalStatusIcon} | ||
| 2034 | {topStatus && CompactStatusIcon ? ( | ||
| 2035 | isWokeStatus ? ( | ||
| 2036 | <button | ||
| 2037 | type="button" | ||
| 2038 | aria-label="Dismiss Woke notification" | ||
| 2039 | onClick={handleAcknowledgeWokeClick} | ||
| 2040 | className={cn( | ||
| 2041 | "shrink-0 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring", | ||
| 2042 | topStatus.className, | ||
| 2043 | )} | ||
| 2044 | > | ||
| 2045 | <CompactStatusIcon aria-hidden className="size-3.5" /> | ||
| 2046 | <span role="status" className="sr-only"> | ||
| 2047 | {topStatus.label} | ||
| 2048 | </span> | ||
| 2049 | </button> | ||
| 2050 | ) : ( | ||
| 2051 | <span | ||
| 2052 | role="status" | ||
| 2053 | className={cn("inline-flex shrink-0", topStatus.className)} | ||
| 2054 | > | ||
| 2055 | <CompactStatusIcon aria-hidden className="size-3.5" /> | ||
| 2056 | <span className="sr-only">{topStatus.label}</span> | ||
| 2057 | </span> | ||
| 2058 | ) | ||
| 2059 | ) : null} | ||
| 2060 | {renderPrBadge(true)} | ||
| 2061 | </> | ||
| 2062 | ) : null} | ||
| 2063 | {/* The visible state owns this slot's width: status at rest, | 1762 | {/* The visible state owns this slot's width: status at rest, |
| 2064 | actions on hover/keyboard focus or while the popover is open. Keeping | 1763 | actions on hover/keyboard focus or while the popover is open. Keeping |
| 2065 | the hidden state out of flow lets the project label reclaim | 1764 | the hidden state out of flow lets the project label reclaim |
| ... | @@ -2067,35 +1766,20 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -2067,35 +1766,20 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 2067 | {sortable?.isDragging ? ( | 1766 | {sortable?.isDragging ? ( |
| 2068 | dragDestination | 1767 | dragDestination |
| 2069 | ) : ( | 1768 | ) : ( |
| 2070 | <span | 1769 | <span className="group/sidebar-status-slot relative ml-auto flex h-5 min-w-8 shrink-0 items-stretch justify-end text-xs"> |
| 2071 | className={cn( | ||
| 2072 | "group/sidebar-status-slot relative ml-auto flex h-5 shrink-0 items-stretch justify-end text-xs", | ||
| 2073 | !compactRows && "min-w-8", | ||
| 2074 | )} | ||
| 2075 | > | ||
| 2076 | {/* Read-only status labels yield to the hover actions. Woke is | 1770 | {/* Read-only status labels yield to the hover actions. Woke is |
| 2077 | itself an action, so it stays pointer-enabled and visible | 1771 | itself an action, so it stays pointer-enabled and visible |
| 2078 | while the other controls appear beside it. */} | 1772 | while the other controls appear beside it. */} |
| 2079 | <span | 1773 | <span |
| 2080 | className={cn( | 1774 | className={cn( |
| 2081 | isWokeStatus && !compactRows | 1775 | isWokeStatus |
| 2082 | ? "pointer-events-auto" | 1776 | ? "pointer-events-auto" |
| 2083 | : "pointer-events-none group-has-[:focus-visible]/sidebar-status-slot:absolute group-has-[:focus-visible]/sidebar-status-slot:right-0 group-has-[:focus-visible]/sidebar-status-slot:opacity-0 group-hover/sidebar-row:absolute group-hover/sidebar-row:right-0 group-hover/sidebar-row:opacity-0", | 1777 | : "pointer-events-none group-has-[:focus-visible]/sidebar-status-slot:absolute group-has-[:focus-visible]/sidebar-status-slot:right-0 group-has-[:focus-visible]/sidebar-status-slot:opacity-0 group-hover/sidebar-row:absolute group-hover/sidebar-row:right-0 group-hover/sidebar-row:opacity-0", |
| 2084 | "flex items-center self-center justify-self-end tabular-nums text-secondary-label transition-opacity", | 1778 | "flex items-center self-center justify-self-end tabular-nums text-secondary-label transition-opacity", |
| 2085 | snoozeMenuOpen && "pointer-events-none absolute right-0 opacity-0", | 1779 | snoozeMenuOpen && "pointer-events-none absolute right-0 opacity-0", |
| 2086 | )} | 1780 | )} |
| 2087 | > | 1781 | > |
| 2088 | {compactRows ? ( | 1782 | {topStatus ? ( |
| 2089 | status === "working" ? ( | ||
| 2090 | <span className={topStatus?.className}> | ||
| 2091 | <WorkingDuration startedAt={resolveWorkingStartedAt(thread)} /> | ||
| 2092 | </span> | ||
| 2093 | ) : compactCompletedAt ? ( | ||
| 2094 | <SidebarCompletedTime completedAt={compactCompletedAt} /> | ||
| 2095 | ) : ( | ||
| 2096 | threadTimeLabel(thread) | ||
| 2097 | ) | ||
| 2098 | ) : topStatus ? ( | ||
| 2099 | isWokeStatus ? ( | 1783 | isWokeStatus ? ( |
| 2100 | <Tooltip> | 1784 | <Tooltip> |
| 2101 | <TooltipTrigger | 1785 | <TooltipTrigger |
| ... | @@ -2201,7 +1885,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -2201,7 +1885,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 2201 | } | 1885 | } |
| 2202 | > | 1886 | > |
| 2203 | <CheckIcon className="size-3.5" /> | 1887 | <CheckIcon className="size-3.5" /> |
| 2204 | {compactRows ? null : "Settle"} | 1888 | Settle |
| 2205 | </TooltipTrigger> | 1889 | </TooltipTrigger> |
| 2206 | <TooltipPopup>Settle thread</TooltipPopup> | 1890 | <TooltipPopup>Settle thread</TooltipPopup> |
| 2207 | </Tooltip> | 1891 | </Tooltip> |
| ... | @@ -2211,70 +1895,68 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | ... | @@ -2211,70 +1895,68 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 2211 | </span> | 1895 | </span> |
| 2212 | )} | 1896 | )} |
| 2213 | </div> | 1897 | </div> |
| 2214 | {isRegeneratingTitle ? ( | 1898 | <div className="mt-1 flex min-w-0"> |
| 2215 | <span role="status" className="sr-only"> | 1899 | {title} |
| 2216 | Regenerating title | 1900 | {isRegeneratingTitle ? ( |
| 2217 | </span> | 1901 | <span role="status" className="sr-only"> |
| 2218 | ) : null} | 1902 | Regenerating title |
| 2219 | {compactRows ? null : ( | 1903 | </span> |
| 2220 | <> | 1904 | ) : null} |
| 2221 | <div className="mt-1 flex min-w-0">{title}</div> | 1905 | </div> |
| 2222 | <div className="mt-0.5 flex min-w-0 items-center gap-1.5 text-secondary-label text-xs"> | 1906 | <div className="mt-0.5 flex min-w-0 items-center gap-1.5 text-secondary-label text-xs"> |
| 2223 | {/* Always the branch. The plan step used to take this slot while | 1907 | {/* Always the branch. The plan step used to take this slot while |
| 2224 | working, but it truncated to a half-sentence and dropped the | 1908 | working, but it truncated to a half-sentence and dropped the |
| 2225 | branch, so the row lost its most stable identifier. */} | 1909 | branch, so the row lost its most stable identifier. */} |
| 2226 | {thread.branch ? ( | 1910 | {thread.branch ? ( |
| 2227 | <> | 1911 | <> |
| 2228 | <ThreadWorktreeIndicator thread={thread} /> | 1912 | <ThreadWorktreeIndicator thread={thread} /> |
| 2229 | <span className="min-w-0 flex-1 truncate whitespace-nowrap text-muted-foreground/40"> | 1913 | <span className="min-w-0 flex-1 truncate whitespace-nowrap text-muted-foreground/40"> |
| 2230 | {thread.branch} | 1914 | {thread.branch} |
| 2231 | </span> | ||
| 2232 | </> | ||
| 2233 | ) : ( | ||
| 2234 | <span className="flex-1" /> | ||
| 2235 | )} | ||
| 2236 | {terminalStatusIcon} | ||
| 2237 | {prBadge} | ||
| 2238 | {diff ? ( | ||
| 2239 | <span className="shrink-0 font-mono"> | ||
| 2240 | <span className="text-diff-addition-foreground">+{diff.insertions}</span>{" "} | ||
| 2241 | <span className="text-diff-deletion-foreground">−{diff.deletions}</span> | ||
| 2242 | </span> | ||
| 2243 | ) : null} | ||
| 2244 | <span | ||
| 2245 | aria-hidden | ||
| 2246 | className="pointer-events-none ml-auto inline-flex shrink-0 items-center gap-1" | ||
| 2247 | > | ||
| 2248 | {isRemote ? ( | ||
| 2249 | <span className="inline-flex shrink-0 items-center text-sidebar-muted-foreground/70"> | ||
| 2250 | <EnvironmentMachineIcon | ||
| 2251 | aria-hidden | ||
| 2252 | kind={props.environmentMachine} | ||
| 2253 | className="size-3.5" | ||
| 2254 | /> | ||
| 2255 | </span> | ||
| 2256 | ) : null} | ||
| 2257 | {driverKind ? ( | ||
| 2258 | <span className="inline-flex shrink-0 items-center"> | ||
| 2259 | <ProviderInstanceIcon | ||
| 2260 | driverKind={driverKind} | ||
| 2261 | displayName={ | ||
| 2262 | providerEntry?.displayName ?? | ||
| 2263 | thread.session?.providerName ?? | ||
| 2264 | modelInstanceId | ||
| 2265 | } | ||
| 2266 | accentColor={providerEntry?.accentColor} | ||
| 2267 | showBadge={showInstanceBadge} | ||
| 2268 | // Glyph dims, badge stays saturated; offset matches the composer trigger. | ||
| 2269 | iconClassName="size-3.5 opacity-60" | ||
| 2270 | badgeClassName="right-[-0.1875rem] bottom-[-0.1875rem] h-3 min-w-3 px-0.5 text-[7px]" | ||
| 2271 | /> | ||
| 2272 | </span> | ||
| 2273 | ) : null} | ||
| 2274 | </span> | 1915 | </span> |
| 2275 | </div> | 1916 | </> |
| 2276 | </> | 1917 | ) : ( |
| 2277 | )} | 1918 | <span className="flex-1" /> |
| 1919 | )} | ||
| 1920 | {terminalStatusIcon} | ||
| 1921 | {prBadge} | ||
| 1922 | {diff ? ( | ||
| 1923 | <span className="shrink-0 font-mono"> | ||
| 1924 | <span className="text-diff-addition-foreground">+{diff.insertions}</span>{" "} | ||
| 1925 | <span className="text-diff-deletion-foreground">−{diff.deletions}</span> | ||
| 1926 | </span> | ||
| 1927 | ) : null} | ||
| 1928 | <span | ||
| 1929 | aria-hidden | ||
| 1930 | className="pointer-events-none ml-auto inline-flex shrink-0 items-center gap-1" | ||
| 1931 | > | ||
| 1932 | {isRemote ? ( | ||
| 1933 | <span className="inline-flex shrink-0 items-center text-sidebar-muted-foreground/70"> | ||
| 1934 | <EnvironmentMachineIcon | ||
| 1935 | aria-hidden | ||
| 1936 | kind={props.environmentMachine} | ||
| 1937 | className="size-3.5" | ||
| 1938 | /> | ||
| 1939 | </span> | ||
| 1940 | ) : null} | ||
| 1941 | {driverKind ? ( | ||
| 1942 | <span className="inline-flex shrink-0 items-center"> | ||
| 1943 | <ProviderInstanceIcon | ||
| 1944 | driverKind={driverKind} | ||
| 1945 | displayName={ | ||
| 1946 | providerEntry?.displayName ?? | ||
| 1947 | thread.session?.providerName ?? | ||
| 1948 | modelInstanceId | ||
| 1949 | } | ||
| 1950 | accentColor={providerEntry?.accentColor} | ||
| 1951 | showBadge={showInstanceBadge} | ||
| 1952 | // Glyph dims, badge stays saturated; offset matches the composer trigger. | ||
| 1953 | iconClassName="size-3.5 opacity-60" | ||
| 1954 | badgeClassName="right-[-0.1875rem] bottom-[-0.1875rem] h-3 min-w-3 px-0.5 text-[7px]" | ||
| 1955 | /> | ||
| 1956 | </span> | ||
| 1957 | ) : null} | ||
| 1958 | </span> | ||
| 1959 | </div> | ||
| 2278 | </div> | 1960 | </div> |
| 2279 | {props.jumpLabel ? <JumpHintBadge label={props.jumpLabel} /> : null} | 1961 | {props.jumpLabel ? <JumpHintBadge label={props.jumpLabel} /> : null} |
| 2280 | </TooltipTrigger> | 1962 | </TooltipTrigger> |
| ... | @@ -2308,9 +1990,6 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | ... | @@ -2308,9 +1990,6 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2308 | onFileDropThreads: (threadRef: ScopedThreadRef, files: File[]) => void; | 1990 | onFileDropThreads: (threadRef: ScopedThreadRef, files: File[]) => void; |
| 2309 | }) { | 1991 | }) { |
| 2310 | const { thread } = props; | 1992 | const { thread } = props; |
| 2311 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 2312 | const { state, isMobile } = useSidebar(); | ||
| 2313 | const compact = compactEnabled && state === "collapsed" && !isMobile; | ||
| 2314 | const threadRef = useMemo( | 1993 | const threadRef = useMemo( |
| 2315 | () => scopeThreadRef(thread.environmentId, thread.id), | 1994 | () => scopeThreadRef(thread.environmentId, thread.id), |
| 2316 | [thread.environmentId, thread.id], | 1995 | [thread.environmentId, thread.id], |
| ... | @@ -2396,7 +2075,6 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | ... | @@ -2396,7 +2075,6 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2396 | onClick={props.onSelect} | 2075 | onClick={props.onSelect} |
| 2397 | className={cn( | 2076 | className={cn( |
| 2398 | "flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm outline-none", | 2077 | "flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm outline-none", |
| 2399 | compact && "justify-center px-0", | ||
| 2400 | props.isHighlighted || props.isRouteActive | 2078 | props.isHighlighted || props.isRouteActive |
| 2401 | ? "bg-sidebar-row-active text-sidebar-foreground" | 2079 | ? "bg-sidebar-row-active text-sidebar-foreground" |
| 2402 | : "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", | 2080 | : "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", |
| ... | @@ -2408,15 +2086,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | ... | @@ -2408,15 +2086,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2408 | > | 2086 | > |
| 2409 | {props.project ? ( | 2087 | {props.project ? ( |
| 2410 | <ProjectFavicon project={props.project} className="size-4 shrink-0" /> | 2088 | <ProjectFavicon project={props.project} className="size-4 shrink-0" /> |
| 2411 | ) : compact ? ( | ||
| 2412 | <SquarePenIcon aria-hidden className="size-4" /> | ||
| 2413 | ) : null} | 2089 | ) : null} |
| 2414 | <span className={compact ? "sr-only" : "min-w-0 flex-1 truncate"}>{thread.title}</span> | 2090 | <span className="min-w-0 flex-1 truncate">{thread.title}</span> |
| 2415 | <span | 2091 | <span className="shrink-0 text-xs text-muted-foreground/55 tabular-nums"> |
| 2416 | className={ | ||
| 2417 | compact ? "sr-only" : "shrink-0 text-xs text-muted-foreground/55 tabular-nums" | ||
| 2418 | } | ||
| 2419 | > | ||
| 2420 | {threadTimeLabel(thread)} | 2092 | {threadTimeLabel(thread)} |
| 2421 | </span> | 2093 | </span> |
| 2422 | </TooltipTrigger> | 2094 | </TooltipTrigger> |
| ... | @@ -2444,12 +2116,8 @@ export default function Sidebar() { | ... | @@ -2444,12 +2116,8 @@ export default function Sidebar() { |
| 2444 | const projectOrder = useUiStateStore((store) => store.projectOrder); | 2116 | const projectOrder = useUiStateStore((store) => store.projectOrder); |
| 2445 | const threads = useThreadShells(); | 2117 | const threads = useThreadShells(); |
| 2446 | const router = useRouter(); | 2118 | const router = useRouter(); |
| 2447 | const { isMobile, setOpenMobile, setOpen, state: sidebarState } = useSidebar(); | 2119 | const { isMobile, setOpenMobile } = useSidebar(); |
| 2448 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 2449 | const compact = compactEnabled && sidebarState === "collapsed" && !isMobile; | ||
| 2450 | const [snoozedFooter, setSnoozedFooter] = useState<HTMLUListElement | null>(null); | ||
| 2451 | const keybindings = useAtomValue(primaryServerKeybindingsAtom); | 2120 | const keybindings = useAtomValue(primaryServerKeybindingsAtom); |
| 2452 | const compactThreadRows = useClientSettings((s) => s.sidebarCompactThreadRows); | ||
| 2453 | const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete); | 2121 | const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete); |
| 2454 | const confirmThreadArchive = useClientSettings((s) => s.confirmThreadArchive); | 2122 | const confirmThreadArchive = useClientSettings((s) => s.confirmThreadArchive); |
| 2455 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); | 2123 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); |
| ... | @@ -3020,7 +2688,6 @@ export default function Sidebar() { | ... | @@ -3020,7 +2688,6 @@ export default function Sidebar() { |
| 3020 | [setSettledShelfExpanded], | 2688 | [setSettledShelfExpanded], |
| 3021 | ); | 2689 | ); |
| 3022 | const renderedSettledThreads = useMemo(() => { | 2690 | const renderedSettledThreads = useMemo(() => { |
| 3023 | if (compact) return EMPTY_THREADS; | ||
| 3024 | if (settledShelfExpanded) return visibleSettledThreads; | 2691 | if (settledShelfExpanded) return visibleSettledThreads; |
| 3025 | if (routeThreadKey === null) return EMPTY_THREADS; | 2692 | if (routeThreadKey === null) return EMPTY_THREADS; |
| 3026 | const routeThread = visibleSettledThreads.find( | 2693 | const routeThread = visibleSettledThreads.find( |
| ... | @@ -3028,7 +2695,7 @@ export default function Sidebar() { | ... | @@ -3028,7 +2695,7 @@ export default function Sidebar() { |
| 3028 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey, | 2695 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey, |
| 3029 | ); | 2696 | ); |
| 3030 | return routeThread === undefined ? EMPTY_THREADS : [routeThread]; | 2697 | return routeThread === undefined ? EMPTY_THREADS : [routeThread]; |
| 3031 | }, [compact, routeThreadKey, settledShelfExpanded, visibleSettledThreads]); | 2698 | }, [routeThreadKey, settledShelfExpanded, visibleSettledThreads]); |
| 3032 | 2699 | ||
| 3033 | // The snoozed shelf is collapsed by default: out of the way, never gone. | 2700 | // The snoozed shelf is collapsed by default: out of the way, never gone. |
| 3034 | // Collapsed threads don't render (and so don't participate in jump | 2701 | // Collapsed threads don't render (and so don't participate in jump |
| ... | @@ -3256,14 +2923,10 @@ export default function Sidebar() { | ... | @@ -3256,14 +2923,10 @@ export default function Sidebar() { |
| 3256 | 2923 | ||
| 3257 | const [renamingThreadKey, setRenamingThreadKey] = useState<string | null>(null); | 2924 | const [renamingThreadKey, setRenamingThreadKey] = useState<string | null>(null); |
| 3258 | const [renamingTitle, setRenamingTitle] = useState(""); | 2925 | const [renamingTitle, setRenamingTitle] = useState(""); |
| 3259 | const startThreadRename = useCallback( | 2926 | const startThreadRename = useCallback((threadRef: ScopedThreadRef, title: string) => { |
| 3260 | (threadRef: ScopedThreadRef, title: string) => { | 2927 | setRenamingThreadKey(scopedThreadKey(threadRef)); |
| 3261 | if (compact) setOpen(true); | 2928 | setRenamingTitle(title); |
| 3262 | setRenamingThreadKey(scopedThreadKey(threadRef)); | 2929 | }, []); |
| 3263 | setRenamingTitle(title); | ||
| 3264 | }, | ||
| 3265 | [compact, setOpen], | ||
| 3266 | ); | ||
| 3267 | const cancelThreadRename = useCallback(() => setRenamingThreadKey(null), []); | 2930 | const cancelThreadRename = useCallback(() => setRenamingThreadKey(null), []); |
| 3268 | const commitThreadRename = useCallback( | 2931 | const commitThreadRename = useCallback( |
| 3269 | (threadRef: ScopedThreadRef, title: string, originalTitle: string) => { | 2932 | (threadRef: ScopedThreadRef, title: string, originalTitle: string) => { |
| ... | @@ -3425,18 +3088,8 @@ export default function Sidebar() { | ... | @@ -3425,18 +3088,8 @@ export default function Sidebar() { |
| 3425 | const threadListRef = useRef<HTMLUListElement | null>(null); | 3088 | const threadListRef = useRef<HTMLUListElement | null>(null); |
| 3426 | const dragLabelOffsetRef = useRef(0); | 3089 | const dragLabelOffsetRef = useRef(0); |
| 3427 | const restrictBelowPins = useCallback<Modifier>( | 3090 | const restrictBelowPins = useCallback<Modifier>( |
| 3428 | (args) => | 3091 | (args) => restrictBelowSidebarLabel(args, dragLabelOffsetRef.current), |
| 3429 | restrictBelowSidebarLabel( | 3092 | [], |
| 3430 | { | ||
| 3431 | ...args, | ||
| 3432 | // The fixed snoozed shelf shares the main list's drag boundary. | ||
| 3433 | containerNodeRect: compact | ||
| 3434 | ? (threadListRef.current?.getBoundingClientRect() ?? args.containerNodeRect) | ||
| 3435 | : args.containerNodeRect, | ||
| 3436 | }, | ||
| 3437 | dragLabelOffsetRef.current, | ||
| 3438 | ), | ||
| 3439 | [compact], | ||
| 3440 | ); | 3093 | ); |
| 3441 | const listMotionRef = useRef<ReturnType<typeof createSidebarListMotion> | null>(null); | 3094 | const listMotionRef = useRef<ReturnType<typeof createSidebarListMotion> | null>(null); |
| 3442 | const attachListMotionRef = useCallback((node: HTMLUListElement | null) => { | 3095 | const attachListMotionRef = useCallback((node: HTMLUListElement | null) => { |
| ... | @@ -3658,7 +3311,7 @@ export default function Sidebar() { | ... | @@ -3658,7 +3311,7 @@ export default function Sidebar() { |
| 3658 | pinnedThreads.length + | 3311 | pinnedThreads.length + |
| 3659 | activeThreads.length + | 3312 | activeThreads.length + |
| 3660 | snoozedThreads.length + | 3313 | snoozedThreads.length + |
| 3661 | (compact ? 0 : settledThreads.length) === | 3314 | settledThreads.length === |
| 3662 | 0 | 3315 | 0 |
| 3663 | ) { | 3316 | ) { |
| 3664 | return []; | 3317 | return []; |
| ... | @@ -3674,15 +3327,13 @@ export default function Sidebar() { | ... | @@ -3674,15 +3327,13 @@ export default function Sidebar() { |
| 3674 | items.push({ kind: "marker", marker: "snoozed-header" }); | 3327 | items.push({ kind: "marker", marker: "snoozed-header" }); |
| 3675 | items.push(...rowsOf(visibleSnoozedThreads, "snoozed")); | 3328 | items.push(...rowsOf(visibleSnoozedThreads, "snoozed")); |
| 3676 | } | 3329 | } |
| 3677 | if (!compact) { | 3330 | items.push({ kind: "marker", marker: "settled-header" }); |
| 3678 | items.push({ kind: "marker", marker: "settled-header" }); | 3331 | const settledRows = rowsOf(renderedSettledThreads, "settled"); |
| 3679 | items.push({ kind: "marker", marker: "settled-placeholder" }); | 3332 | items.push({ kind: "marker", marker: "settled-placeholder" }); |
| 3680 | items.push(...rowsOf(renderedSettledThreads, "settled")); | 3333 | items.push(...settledRows); |
| 3681 | } | ||
| 3682 | return items; | 3334 | return items; |
| 3683 | }, [ | 3335 | }, [ |
| 3684 | activeThreads, | 3336 | activeThreads, |
| 3685 | compact, | ||
| 3686 | pinnedThreads, | 3337 | pinnedThreads, |
| 3687 | renderedSettledThreads, | 3338 | renderedSettledThreads, |
| 3688 | settledThreads.length, | 3339 | settledThreads.length, |
| ... | @@ -3752,7 +3403,6 @@ export default function Sidebar() { | ... | @@ -3752,7 +3403,6 @@ export default function Sidebar() { |
| 3752 | () => | 3403 | () => |
| 3753 | createSidebarSortingStrategy({ | 3404 | createSidebarSortingStrategy({ |
| 3754 | items: sidebarListItems, | 3405 | items: sidebarListItems, |
| 3755 | compact, | ||
| 3756 | boundaryLabelHeight: SIDEBAR_DRAG_LABEL_HEIGHT, | 3406 | boundaryLabelHeight: SIDEBAR_DRAG_LABEL_HEIGHT, |
| 3757 | settledOrder: draggedSettledOrder, | 3407 | settledOrder: draggedSettledOrder, |
| 3758 | settledExpanded: settledShelfExpanded, | 3408 | settledExpanded: settledShelfExpanded, |
| ... | @@ -3761,7 +3411,6 @@ export default function Sidebar() { | ... | @@ -3761,7 +3411,6 @@ export default function Sidebar() { |
| 3761 | snoozedThreadCount: snoozedThreads.length, | 3411 | snoozedThreadCount: snoozedThreads.length, |
| 3762 | }), | 3412 | }), |
| 3763 | [ | 3413 | [ |
| 3764 | compact, | ||
| 3765 | draggedSettledOrder, | 3414 | draggedSettledOrder, |
| 3766 | routeThreadKey, | 3415 | routeThreadKey, |
| 3767 | settledShelfExpanded, | 3416 | settledShelfExpanded, |
| ... | @@ -3770,22 +3419,6 @@ export default function Sidebar() { | ... | @@ -3770,22 +3419,6 @@ export default function Sidebar() { |
| 3770 | snoozedThreads.length, | 3419 | snoozedThreads.length, |
| 3771 | ], | 3420 | ], |
| 3772 | ); | 3421 | ); |
| 3773 | const draggingCompactSnoozed = compact && dragState?.activeSection === "snoozed"; | ||
| 3774 | const compactSnoozedDragThread = | ||
| 3775 | draggingCompactSnoozed && dragState ? threadByKey.get(dragState.activeKey) : undefined; | ||
| 3776 | const compactSidebarSortingStrategy = useCallback<typeof sidebarSortingStrategy>( | ||
| 3777 | (args) => { | ||
| 3778 | const item = sidebarListItems[args.index]; | ||
| 3779 | // Footer rows stay anchored while the main list previews a reorder. | ||
| 3780 | if ( | ||
| 3781 | item?.kind === "thread" ? item.section === "snoozed" : item?.marker === "snoozed-header" | ||
| 3782 | ) { | ||
| 3783 | return null; | ||
| 3784 | } | ||
| 3785 | return sidebarSortingStrategy(args); | ||
| 3786 | }, | ||
| 3787 | [sidebarListItems, sidebarSortingStrategy], | ||
| 3788 | ); | ||
| 3789 | // Hidden and filtered threads keep their keys. Reserve those slots without | 3422 | // Hidden and filtered threads keep their keys. Reserve those slots without |
| 3790 | // including the rows in the visible drop order or writing to them. | 3423 | // including the rows in the visible drop order or writing to them. |
| 3791 | const { pinnedKeysById, activeKeysById } = useMemo( | 3424 | const { pinnedKeysById, activeKeysById } = useMemo( |
| ... | @@ -4689,19 +4322,7 @@ export default function Sidebar() { | ... | @@ -4689,19 +4322,7 @@ export default function Sidebar() { |
| 4689 | <> | 4322 | <> |
| 4690 | <SidebarChromeHeader isElectron={isElectron} /> | 4323 | <SidebarChromeHeader isElectron={isElectron} /> |
| 4691 | <SidebarContent | 4324 | <SidebarContent |
| 4692 | className={cn( | 4325 | className="gap-0 min-h-full" |
| 4693 | "gap-0", | ||
| 4694 | compact ? "group-data-[collapsible=icon]:overflow-visible" : "min-h-full", | ||
| 4695 | )} | ||
| 4696 | fixedFooter={ | ||
| 4697 | compact && !isSearchingThreads && snoozedThreads.length > 0 ? ( | ||
| 4698 | <ul | ||
| 4699 | ref={setSnoozedFooter} | ||
| 4700 | aria-label="Snoozed threads" | ||
| 4701 | className="relative flex max-h-[min(30vh,16rem)] flex-col gap-px overflow-y-auto px-[var(--sidebar-content-inset)] pb-1" | ||
| 4702 | /> | ||
| 4703 | ) : null | ||
| 4704 | } | ||
| 4705 | fixedHeader={ | 4326 | fixedHeader={ |
| 4706 | // Lifted above the stage backdrop, whose fade bleeds below the | 4327 | // Lifted above the stage backdrop, whose fade bleeds below the |
| 4707 | // header and would otherwise paint across the search row's outline. | 4328 | // header and would otherwise paint across the search row's outline. |
| ... | @@ -4761,12 +4382,8 @@ export default function Sidebar() { | ... | @@ -4761,12 +4382,8 @@ export default function Sidebar() { |
| 4761 | // popup opens under the field, is at least as wide as it, | 4382 | // popup opens under the field, is at least as wide as it, |
| 4762 | // and grows to fit project names up to a cap, past which | 4383 | // and grows to fit project names up to a cap, past which |
| 4763 | // the rows truncate. | 4384 | // the rows truncate. |
| 4764 | anchor={compact ? undefined : headerSearchRef} | 4385 | anchor={headerSearchRef} |
| 4765 | side={compact ? "right" : "bottom"} | 4386 | className="max-w-[min(18rem,var(--available-width))] overflow-hidden" |
| 4766 | className={cn( | ||
| 4767 | "max-w-[min(18rem,var(--available-width))] overflow-hidden", | ||
| 4768 | compact && "min-w-56", | ||
| 4769 | )} | ||
| 4770 | > | 4387 | > |
| 4771 | <ComboboxSearchInput | 4388 | <ComboboxSearchInput |
| 4772 | aria-label="Search projects" | 4389 | aria-label="Search projects" |
| ... | @@ -4868,12 +4485,7 @@ export default function Sidebar() { | ... | @@ -4868,12 +4485,7 @@ export default function Sidebar() { |
| 4868 | </SidebarGroup> | 4485 | </SidebarGroup> |
| 4869 | } | 4486 | } |
| 4870 | > | 4487 | > |
| 4871 | <SidebarGroup | 4488 | <SidebarGroup className="ps-[calc(var(--sidebar-content-inset)+1px)] pe-[var(--sidebar-content-inset)] pb-1 pt-0 flex-1"> |
| 4872 | className={cn( | ||
| 4873 | "ps-[calc(var(--sidebar-content-inset)+1px)] pe-[var(--sidebar-content-inset)] pb-1 pt-0", | ||
| 4874 | !compact && "flex-1", | ||
| 4875 | )} | ||
| 4876 | > | ||
| 4877 | {isSearchingThreads ? ( | 4489 | {isSearchingThreads ? ( |
| 4878 | threadSearchResults.length > 0 ? ( | 4490 | threadSearchResults.length > 0 ? ( |
| 4879 | <TooltipProvider | 4491 | <TooltipProvider |
| ... | @@ -4926,10 +4538,7 @@ export default function Sidebar() { | ... | @@ -4926,10 +4538,7 @@ export default function Sidebar() { |
| 4926 | ) : ( | 4538 | ) : ( |
| 4927 | <p | 4539 | <p |
| 4928 | role="status" | 4540 | role="status" |
| 4929 | className={cn( | 4541 | className="px-2 py-6 text-center text-xs text-sidebar-muted-foreground" |
| 4930 | "px-2 py-6 text-center text-xs text-sidebar-muted-foreground", | ||
| 4931 | compact && "sr-only", | ||
| 4932 | )} | ||
| 4933 | > | 4542 | > |
| 4934 | No threads found | 4543 | No threads found |
| 4935 | </p> | 4544 | </p> |
| ... | @@ -4948,23 +4557,20 @@ export default function Sidebar() { | ... | @@ -4948,23 +4557,20 @@ export default function Sidebar() { |
| 4948 | modifiers={[ | 4557 | modifiers={[ |
| 4949 | restrictToVerticalAxis, | 4558 | restrictToVerticalAxis, |
| 4950 | restrictBelowPins, | 4559 | restrictBelowPins, |
| 4951 | ...(compact ? [] : [restrictToFirstScrollableAncestor]), | 4560 | restrictToFirstScrollableAncestor, |
| 4952 | ]} | 4561 | ]} |
| 4953 | onDragStart={handleThreadDragStart} | 4562 | onDragStart={handleThreadDragStart} |
| 4954 | onDragOver={handleThreadDragOver} | 4563 | onDragOver={handleThreadDragOver} |
| 4955 | onDragEnd={handleThreadDragEnd} | 4564 | onDragEnd={handleThreadDragEnd} |
| 4956 | > | 4565 | > |
| 4957 | <SidebarDragLifecycle onUnmount={cancelThreadDrag} /> | 4566 | <SidebarDragLifecycle onUnmount={cancelThreadDrag} /> |
| 4958 | <SortableContext | 4567 | <SortableContext items={sortableIds} strategy={sidebarSortingStrategy}> |
| 4959 | items={sortableIds} | ||
| 4960 | strategy={compact ? compactSidebarSortingStrategy : sidebarSortingStrategy} | ||
| 4961 | > | ||
| 4962 | <ul | 4568 | <ul |
| 4963 | ref={attachListMotionRef} | 4569 | ref={attachListMotionRef} |
| 4964 | role="list" | 4570 | role="list" |
| 4965 | className={cn( | 4571 | className={cn( |
| 4966 | "relative flex flex-col gap-px", | 4572 | "relative flex flex-col gap-px", |
| 4967 | !compact && sidebarListItems.length > 0 && "flex-1", | 4573 | sidebarListItems.length > 0 && "flex-1", |
| 4968 | )} | 4574 | )} |
| 4969 | > | 4575 | > |
| 4970 | {(() => { | 4576 | {(() => { |
| ... | @@ -4976,9 +4582,10 @@ export default function Sidebar() { | ... | @@ -4976,9 +4582,10 @@ export default function Sidebar() { |
| 4976 | const threadKey = scopedThreadKey( | 4582 | const threadKey = scopedThreadKey( |
| 4977 | scopeThreadRef(thread.environmentId, thread.id), | 4583 | scopeThreadRef(thread.environmentId, thread.id), |
| 4978 | ); | 4584 | ); |
| 4979 | // Settled and snoozed always use slim rows. Active and | 4585 | // Settled and snoozed are the ONLY things that collapse a |
| 4980 | // pinned threads use cards unless the user has explicitly | 4586 | // row: every other thread is a full card. Density comes |
| 4981 | // enabled the compact thread-list preference. | 4587 | // from users (or the auto rules) actually parking work, |
| 4588 | // not from the sidebar second-guessing what still matters. | ||
| 4982 | const isCard = section === "active" || section === "pinned"; | 4589 | const isCard = section === "active" || section === "pinned"; |
| 4983 | const rowVariant = isCard ? "card" : "slim"; | 4590 | const rowVariant = isCard ? "card" : "slim"; |
| 4984 | return ( | 4591 | return ( |
| ... | @@ -4988,7 +4595,6 @@ export default function Sidebar() { | ... | @@ -4988,7 +4595,6 @@ export default function Sidebar() { |
| 4988 | key={`${threadKey}:${rowVariant}`} | 4595 | key={`${threadKey}:${rowVariant}`} |
| 4989 | thread={thread} | 4596 | thread={thread} |
| 4990 | variant={rowVariant} | 4597 | variant={rowVariant} |
| 4991 | compact={compactThreadRows} | ||
| 4992 | // Snoozed rows wake, settled rows un-settle, and cards settle. | 4598 | // Snoozed rows wake, settled rows un-settle, and cards settle. |
| 4993 | variantAction={ | 4599 | variantAction={ |
| 4994 | section === "snoozed" | 4600 | section === "snoozed" |
| ... | @@ -5091,25 +4697,11 @@ export default function Sidebar() { | ... | @@ -5091,25 +4697,11 @@ export default function Sidebar() { |
| 5091 | !draggableThreadKeys.has(threadKey) || optimisticDrop !== null | 4697 | !draggableThreadKeys.has(threadKey) || optimisticDrop !== null |
| 5092 | } | 4698 | } |
| 5093 | > | 4699 | > |
| 5094 | {(bag) => | 4700 | {(bag) => renderThreadRowInner(thread, section, bag)} |
| 5095 | renderThreadRowInner( | ||
| 5096 | thread, | ||
| 5097 | section, | ||
| 5098 | draggingCompactSnoozed && bag.isDragging | ||
| 5099 | ? { ...bag, hidden: true } | ||
| 5100 | : bag, | ||
| 5101 | ) | ||
| 5102 | } | ||
| 5103 | </SortableThreadRow> | 4701 | </SortableThreadRow> |
| 5104 | ); | 4702 | ); |
| 5105 | }; | 4703 | }; |
| 5106 | const from = dragState?.activeSection ?? null; | 4704 | const from = dragState?.activeSection ?? null; |
| 5107 | const showDragLabels = | ||
| 5108 | from !== null && | ||
| 5109 | (!compact || | ||
| 5110 | dragTargetSection === "active" || | ||
| 5111 | dragTargetSection === "pinned"); | ||
| 5112 | const snoozedItems: ReactNode[] = []; | ||
| 5113 | const items: ReactNode[] = [ | 4705 | const items: ReactNode[] = [ |
| 5114 | <SidebarDraftBlock | 4706 | <SidebarDraftBlock |
| 5115 | key="draft-sessions" | 4707 | key="draft-sessions" |
| ... | @@ -5121,17 +4713,8 @@ export default function Sidebar() { | ... | @@ -5121,17 +4713,8 @@ export default function Sidebar() { |
| 5121 | />, | 4713 | />, |
| 5122 | ]; | 4714 | ]; |
| 5123 | for (const item of sidebarListItems) { | 4715 | for (const item of sidebarListItems) { |
| 5124 | const destination = | ||
| 5125 | compact && | ||
| 5126 | (item.kind === "thread" | ||
| 5127 | ? item.section === "snoozed" | ||
| 5128 | : item.marker === "snoozed-header") | ||
| 5129 | ? snoozedItems | ||
| 5130 | : items; | ||
| 5131 | if (item.kind === "thread") { | 4716 | if (item.kind === "thread") { |
| 5132 | destination.push( | 4717 | items.push(renderThreadRow(threadByKey.get(item.key)!, item.section)); |
| 5133 | renderThreadRow(threadByKey.get(item.key)!, item.section), | ||
| 5134 | ); | ||
| 5135 | continue; | 4718 | continue; |
| 5136 | } | 4719 | } |
| 5137 | switch (item.marker) { | 4720 | switch (item.marker) { |
| ... | @@ -5141,7 +4724,7 @@ export default function Sidebar() { | ... | @@ -5141,7 +4724,7 @@ export default function Sidebar() { |
| 5141 | key="pinned-header" | 4724 | key="pinned-header" |
| 5142 | marker="pinned-header" | 4725 | marker="pinned-header" |
| 5143 | label="Pinned" | 4726 | label="Pinned" |
| 5144 | visible={showDragLabels} | 4727 | visible={from !== null} |
| 5145 | isDropTarget={dragTargetSection === "pinned"} | 4728 | isDropTarget={dragTargetSection === "pinned"} |
| 5146 | />, | 4729 | />, |
| 5147 | ); | 4730 | ); |
| ... | @@ -5152,7 +4735,7 @@ export default function Sidebar() { | ... | @@ -5152,7 +4735,7 @@ export default function Sidebar() { |
| 5152 | key="pinned-divider" | 4735 | key="pinned-divider" |
| 5153 | marker="pinned-divider" | 4736 | marker="pinned-divider" |
| 5154 | label="Active" | 4737 | label="Active" |
| 5155 | visible={showDragLabels} | 4738 | visible={from !== null} |
| 5156 | isDropTarget={dragTargetSection === "active"} | 4739 | isDropTarget={dragTargetSection === "active"} |
| 5157 | />, | 4740 | />, |
| 5158 | ); | 4741 | ); |
| ... | @@ -5176,11 +4759,11 @@ export default function Sidebar() { | ... | @@ -5176,11 +4759,11 @@ export default function Sidebar() { |
| 5176 | ); | 4759 | ); |
| 5177 | break; | 4760 | break; |
| 5178 | case "snoozed-header": | 4761 | case "snoozed-header": |
| 5179 | destination.push( | 4762 | items.push( |
| 5180 | <SidebarSectionHeader | 4763 | <SidebarSectionHeader |
| 5181 | key="snoozed-shelf-header" | 4764 | key="snoozed-shelf-header" |
| 5182 | marker="snoozed-header" | 4765 | marker="snoozed-header" |
| 5183 | className={cn(!compact && "mt-auto")} | 4766 | className="mt-auto" |
| 5184 | label={ | 4767 | label={ |
| 5185 | snoozedShelfExpanded | 4768 | snoozedShelfExpanded |
| 5186 | ? "Snoozed" | 4769 | ? "Snoozed" |
| ... | @@ -5198,7 +4781,7 @@ export default function Sidebar() { | ... | @@ -5198,7 +4781,7 @@ export default function Sidebar() { |
| 5198 | <SidebarSectionHeader | 4781 | <SidebarSectionHeader |
| 5199 | key="settled-shelf-header" | 4782 | key="settled-shelf-header" |
| 5200 | marker="settled-header" | 4783 | marker="settled-header" |
| 5201 | className={cn(!compact && snoozedThreads.length === 0 && "mt-auto")} | 4784 | className={cn(snoozedThreads.length === 0 && "mt-auto")} |
| 5202 | label={ | 4785 | label={ |
| 5203 | settledShelfExpanded | 4786 | settledShelfExpanded |
| 5204 | ? "Settled" | 4787 | ? "Settled" |
| ... | @@ -5233,56 +4816,18 @@ export default function Sidebar() { | ... | @@ -5233,56 +4816,18 @@ export default function Sidebar() { |
| 5233 | break; | 4816 | break; |
| 5234 | } | 4817 | } |
| 5235 | } | 4818 | } |
| 5236 | // Keep the shelf inside this drag context while anchoring | 4819 | return items; |
| 5237 | // its DOM outside the main thread scroller. | ||
| 5238 | return [ | ||
| 5239 | ...items, | ||
| 5240 | compact && snoozedFooter | ||
| 5241 | ? createPortal(snoozedItems, snoozedFooter, "snoozed-footer") | ||
| 5242 | : null, | ||
| 5243 | compactSnoozedDragThread | ||
| 5244 | ? createPortal( | ||
| 5245 | <DragOverlay dropAnimation={null}> | ||
| 5246 | <ul className="pointer-events-none"> | ||
| 5247 | {renderThreadRowInner(compactSnoozedDragThread, "snoozed", { | ||
| 5248 | isDragging: true, | ||
| 5249 | listeners: undefined, | ||
| 5250 | setNodeRef: () => {}, | ||
| 5251 | transform: null, | ||
| 5252 | transition: undefined, | ||
| 5253 | })} | ||
| 5254 | </ul> | ||
| 5255 | </DragOverlay>, | ||
| 5256 | document.body, | ||
| 5257 | "compact-snoozed-drag", | ||
| 5258 | ) | ||
| 5259 | : null, | ||
| 5260 | ]; | ||
| 5261 | })()} | 4820 | })()} |
| 5262 | {!compact && settledShelfExpanded && hiddenSettledCount > 0 ? ( | 4821 | {settledShelfExpanded && hiddenSettledCount > 0 ? ( |
| 5263 | <li className="list-none"> | 4822 | <li className="list-none"> |
| 5264 | <Tooltip disabled={!compact}> | 4823 | <button |
| 5265 | <TooltipTrigger | 4824 | type="button" |
| 5266 | render={ | 4825 | onClick={showMoreSettled} |
| 5267 | <button | 4826 | className="flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm text-sidebar-muted-foreground/55 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 5268 | type="button" | 4827 | > |
| 5269 | onClick={showMoreSettled} | 4828 | <PlusIcon aria-hidden className="size-4 shrink-0" /> |
| 5270 | className={cn( | 4829 | Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more |
| 5271 | "flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm text-sidebar-muted-foreground/55 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", | 4830 | </button> |
| 5272 | compact && "justify-center px-0", | ||
| 5273 | )} | ||
| 5274 | /> | ||
| 5275 | } | ||
| 5276 | > | ||
| 5277 | <PlusIcon aria-hidden className="size-4 shrink-0" /> | ||
| 5278 | <span className={compact ? "sr-only" : undefined}> | ||
| 5279 | Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more | ||
| 5280 | </span> | ||
| 5281 | </TooltipTrigger> | ||
| 5282 | <TooltipPopup side="right"> | ||
| 5283 | Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more | ||
| 5284 | </TooltipPopup> | ||
| 5285 | </Tooltip> | ||
| 5286 | </li> | 4831 | </li> |
| 5287 | ) : null} | 4832 | ) : null} |
| 5288 | </ul> | 4833 | </ul> |
| ... | @@ -5297,29 +4842,20 @@ export default function Sidebar() { | ... | @@ -5297,29 +4842,20 @@ export default function Sidebar() { |
| 5297 | snoozedThreads.length + | 4842 | snoozedThreads.length + |
| 5298 | settledThreads.length === | 4843 | settledThreads.length === |
| 5299 | 0 ? ( | 4844 | 0 ? ( |
| 5300 | <div | 4845 | <div className="flex flex-col items-center gap-2 px-2 py-6 text-center text-xs text-muted-foreground/60"> |
| 5301 | className={cn( | ||
| 5302 | "flex flex-col items-center gap-2 px-2 py-6 text-center text-xs text-muted-foreground/60", | ||
| 5303 | compact && "px-0", | ||
| 5304 | )} | ||
| 5305 | > | ||
| 5306 | {projects.length === 0 ? ( | 4846 | {projects.length === 0 ? ( |
| 5307 | <> | 4847 | <> |
| 5308 | <span className={compact ? "sr-only" : undefined}>No projects yet</span> | 4848 | <span>No projects yet</span> |
| 5309 | <button | 4849 | <button |
| 5310 | type="button" | 4850 | type="button" |
| 5311 | onClick={openAddProjectCommandPalette} | 4851 | onClick={openAddProjectCommandPalette} |
| 5312 | aria-label="Add project" | 4852 | className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-sidebar-border px-2.5 py-1 text-[11px] font-medium text-sidebar-muted-foreground transition-colors hover:bg-sidebar-row-hover hover:text-sidebar-foreground" |
| 5313 | className={cn( | ||
| 5314 | "inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-sidebar-border px-2.5 py-1 text-[11px] font-medium text-sidebar-muted-foreground transition-colors hover:bg-sidebar-row-hover hover:text-sidebar-foreground", | ||
| 5315 | compact && "justify-center px-1.5", | ||
| 5316 | )} | ||
| 5317 | > | 4853 | > |
| 5318 | <PlusIcon className="-mx-0.5 size-3" /> | 4854 | <PlusIcon className="-mx-0.5 size-3" /> |
| 5319 | <span className={compact ? "sr-only" : undefined}>Add project</span> | 4855 | Add project |
| 5320 | </button> | 4856 | </button> |
| 5321 | </> | 4857 | </> |
| 5322 | ) : compact ? null : scopedProjectGroup ? ( | 4858 | ) : scopedProjectGroup ? ( |
| 5323 | `No threads in ${scopedProjectGroup.displayName} yet` | 4859 | `No threads in ${scopedProjectGroup.displayName} yet` |
| 5324 | ) : ( | 4860 | ) : ( |
| 5325 | "No threads yet" | 4861 | "No threads yet" |
apps/web/src/components/ThreadStatusIndicators.tsx+4-12| ... | @@ -144,17 +144,14 @@ export function ThreadPullRequestBadgeControl({ | ... | @@ -144,17 +144,14 @@ export function ThreadPullRequestBadgeControl({ |
| 144 | number, | 144 | number, |
| 145 | url, | 145 | url, |
| 146 | status, | 146 | status, |
| 147 | iconOnly = false, | ||
| 148 | onOpenStack, | 147 | onOpenStack, |
| 149 | onOpenPullRequest, | 148 | onOpenPullRequest, |
| 150 | }: { | 149 | }: { |
| 151 | variant: "underline" | "ghost" | "badge"; | 150 | variant: "underline" | "ghost"; |
| 152 | badge: ThreadPullRequestBadge | null; | 151 | badge: ThreadPullRequestBadge | null; |
| 153 | number?: number | undefined; | 152 | number?: number | undefined; |
| 154 | url?: string | undefined; | 153 | url?: string | undefined; |
| 155 | status: PrStatusIndicator | null; | 154 | status: PrStatusIndicator | null; |
| 156 | /** Dense rows drop the number/layer count and keep only the state glyph. */ | ||
| 157 | iconOnly?: boolean; | ||
| 158 | onOpenStack: () => void; | 155 | onOpenStack: () => void; |
| 159 | onOpenPullRequest: (event: MouseEvent<HTMLAnchorElement>) => void; | 156 | onOpenPullRequest: (event: MouseEvent<HTMLAnchorElement>) => void; |
| 160 | }) { | 157 | }) { |
| ... | @@ -171,9 +168,7 @@ export function ThreadPullRequestBadgeControl({ | ... | @@ -171,9 +168,7 @@ export function ThreadPullRequestBadgeControl({ |
| 171 | const className = cn( | 168 | const className = cn( |
| 172 | variant === "ghost" | 169 | variant === "ghost" |
| 173 | ? buttonVariants({ variant: "ghost", size: "xs" }) | 170 | ? buttonVariants({ variant: "ghost", size: "xs" }) |
| 174 | : variant === "badge" | 171 | : "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap border-b border-transparent hover:border-current focus-visible:outline-2 focus-visible:outline-ring", |
| 175 | ? "inline-flex size-3 shrink-0 cursor-pointer items-center justify-center rounded-full bg-sidebar ring-1 ring-sidebar outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||
| 176 | : "inline-flex shrink-0 cursor-pointer items-center gap-0.5 whitespace-nowrap border-b border-transparent hover:border-current focus-visible:outline-2 focus-visible:outline-ring", | ||
| 177 | "text-xs tabular-nums", | 172 | "text-xs tabular-nums", |
| 178 | variant === "ghost" && | 173 | variant === "ghost" && |
| 179 | "font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]", | 174 | "font-normal text-xs! active:scale-100 [--control-icon-color:currentColor]", |
| ... | @@ -183,11 +178,8 @@ export function ThreadPullRequestBadgeControl({ | ... | @@ -183,11 +178,8 @@ export function ThreadPullRequestBadgeControl({ |
| 183 | ); | 178 | ); |
| 184 | const content = ( | 179 | const content = ( |
| 185 | <> | 180 | <> |
| 186 | <ThreadPullRequestBadgeIcon | 181 | <ThreadPullRequestBadgeIcon icon={badge?.kind ?? "pull-request"} /> |
| 187 | icon={badge?.kind ?? "pull-request"} | 182 | {isStack ? badge.layers : linkedCount !== null ? `+${linkedCount}` : number} |
| 188 | className={variant === "badge" ? "size-2.5" : undefined} | ||
| 189 | /> | ||
| 190 | {iconOnly ? null : isStack ? badge.layers : linkedCount !== null ? `+${linkedCount}` : number} | ||
| 191 | </> | 183 | </> |
| 192 | ); | 184 | ); |
| 193 | return ( | 185 | return ( |
apps/web/src/components/settings/CompactSidebarPreview.tsx deleted-82| ... | @@ -1,82 +0,0 @@ | ||
| 1 | import { useEffect, useRef, useState } from "react"; | ||
| 2 | |||
| 3 | import { cn } from "~/lib/utils"; | ||
| 4 | |||
| 5 | export function CompactSidebarPreview({ | ||
| 6 | railEnabled, | ||
| 7 | compactRows, | ||
| 8 | }: { | ||
| 9 | railEnabled: boolean; | ||
| 10 | compactRows: boolean; | ||
| 11 | }) { | ||
| 12 | const [collapsed, setCollapsed] = useState(false); | ||
| 13 | const sidebarRef = useRef<HTMLSpanElement>(null); | ||
| 14 | |||
| 15 | useEffect(() => { | ||
| 16 | if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; | ||
| 17 | const animation = sidebarRef.current?.animate( | ||
| 18 | [ | ||
| 19 | { width: "36px", offset: 0 }, | ||
| 20 | { width: railEnabled ? "12px" : "0px", offset: 0.45 }, | ||
| 21 | { width: railEnabled ? "12px" : "0px", offset: 0.6 }, | ||
| 22 | { width: "36px", offset: 1 }, | ||
| 23 | ], | ||
| 24 | { duration: 800, easing: "ease-in-out" }, | ||
| 25 | ); | ||
| 26 | return () => animation?.cancel(); | ||
| 27 | }, [railEnabled, compactRows]); | ||
| 28 | |||
| 29 | return ( | ||
| 30 | <button | ||
| 31 | type="button" | ||
| 32 | aria-label="Toggle compact sidebar preview" | ||
| 33 | aria-pressed={collapsed} | ||
| 34 | className="group/compact-preview relative flex h-10 w-full cursor-pointer overflow-hidden rounded-lg border border-border bg-background p-1 shadow-xs/5 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background" | ||
| 35 | onClick={() => { | ||
| 36 | sidebarRef.current?.getAnimations().forEach((animation) => animation.cancel()); | ||
| 37 | setCollapsed((value) => !value); | ||
| 38 | }} | ||
| 39 | > | ||
| 40 | <span | ||
| 41 | ref={sidebarRef} | ||
| 42 | aria-hidden | ||
| 43 | className={cn( | ||
| 44 | "flex h-full shrink-0 flex-col justify-center gap-1 overflow-hidden rounded-md bg-sidebar transition-[width] duration-250 ease-out motion-reduce:transition-none", | ||
| 45 | collapsed ? (railEnabled ? "w-3" : "w-0") : "w-9", | ||
| 46 | )} | ||
| 47 | > | ||
| 48 | {(compactRows ? [0, 1, 2, 3] : [0, 1, 2]).map((row) => ( | ||
| 49 | <span key={row} className="flex shrink-0 items-start gap-1 px-1"> | ||
| 50 | <span className="size-1 shrink-0 rounded-[1px] bg-muted-foreground/50" /> | ||
| 51 | <span | ||
| 52 | className={cn( | ||
| 53 | "flex shrink-0 flex-col gap-0.5 transition-opacity duration-250 ease-out motion-reduce:transition-none", | ||
| 54 | collapsed && "opacity-0", | ||
| 55 | )} | ||
| 56 | > | ||
| 57 | <span className="h-0.5 w-4 rounded-full bg-muted-foreground/25" /> | ||
| 58 | {!compactRows && <span className="h-0.5 w-2 rounded-full bg-muted-foreground/15" />} | ||
| 59 | </span> | ||
| 60 | </span> | ||
| 61 | ))} | ||
| 62 | </span> | ||
| 63 | <span aria-hidden className="flex min-w-0 flex-1 flex-col gap-1 px-1 pt-1"> | ||
| 64 | <span className="h-0.5 w-full rounded-full bg-muted-foreground/25" /> | ||
| 65 | <span className="h-0.5 w-4/5 rounded-full bg-muted-foreground/20" /> | ||
| 66 | <span className="h-0.5 w-3/5 rounded-full bg-muted-foreground/15" /> | ||
| 67 | </span> | ||
| 68 | <span | ||
| 69 | aria-hidden | ||
| 70 | className={cn( | ||
| 71 | "pointer-events-none absolute top-3 left-5 flex w-9 flex-col gap-1 rounded-sm border border-border bg-popover p-1 opacity-0 shadow-xs transition-opacity duration-150 ease-out motion-reduce:transition-none", | ||
| 72 | collapsed && | ||
| 73 | railEnabled && | ||
| 74 | "group-hover/compact-preview:opacity-100 group-focus-visible/compact-preview:opacity-100", | ||
| 75 | )} | ||
| 76 | > | ||
| 77 | <span className="h-0.5 w-full rounded-full bg-popover-foreground/45" /> | ||
| 78 | <span className="h-0.5 w-2/3 rounded-full bg-popover-foreground/25" /> | ||
| 79 | </span> | ||
| 80 | </button> | ||
| 81 | ); | ||
| 82 | } | ||
apps/web/src/components/settings/SettingsPanels.tsx-80| ... | @@ -170,7 +170,6 @@ import { | ... | @@ -170,7 +170,6 @@ import { |
| 170 | import { searchableSetting } from "./settingsSearch"; | 170 | import { searchableSetting } from "./settingsSearch"; |
| 171 | import { ProjectFavicon } from "../ProjectFavicon"; | 171 | import { ProjectFavicon } from "../ProjectFavicon"; |
| 172 | import { PanelAnimationsPreview } from "./PanelAnimationsPreview"; | 172 | import { PanelAnimationsPreview } from "./PanelAnimationsPreview"; |
| 173 | import { CompactSidebarPreview } from "./CompactSidebarPreview"; | ||
| 174 | 173 | ||
| 175 | const ENVIRONMENT_IDENTIFICATION_LABELS: Record<EnvironmentIdentificationMode, string> = { | 174 | const ENVIRONMENT_IDENTIFICATION_LABELS: Record<EnvironmentIdentificationMode, string> = { |
| 176 | artwork: "Artwork", | 175 | artwork: "Artwork", |
| ... | @@ -524,10 +523,6 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -524,10 +523,6 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 524 | ...(theme !== "system" ? ["Theme"] : []), | 523 | ...(theme !== "system" ? ["Theme"] : []), |
| 525 | ...(!followSystem ? ["Follow system"] : []), | 524 | ...(!followSystem ? ["Follow system"] : []), |
| 526 | ...(themeHalves !== null ? ["Theme mix"] : []), | 525 | ...(themeHalves !== null ? ["Theme mix"] : []), |
| 527 | ...(settings.compactSidebarEnabled !== DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled || | ||
| 528 | settings.sidebarCompactThreadRows !== DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows | ||
| 529 | ? ["Compact sidebar"] | ||
| 530 | : []), | ||
| 531 | ...(settings.appearanceContrast !== DEFAULT_UNIFIED_SETTINGS.appearanceContrast | 526 | ...(settings.appearanceContrast !== DEFAULT_UNIFIED_SETTINGS.appearanceContrast |
| 532 | ? ["Contrast"] | 527 | ? ["Contrast"] |
| 533 | : []), | 528 | : []), |
| ... | @@ -634,7 +629,6 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -634,7 +629,6 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 634 | settings.browserLinkTarget, | 629 | settings.browserLinkTarget, |
| 635 | settings.browserAutoShowFloatingPreview, | 630 | settings.browserAutoShowFloatingPreview, |
| 636 | settings.appearanceContrast, | 631 | settings.appearanceContrast, |
| 637 | settings.compactSidebarEnabled, | ||
| 638 | settings.diffColorScheme, | 632 | settings.diffColorScheme, |
| 639 | settings.enableAgentBrowserAccess, | 633 | settings.enableAgentBrowserAccess, |
| 640 | settings.confirmQuit, | 634 | settings.confirmQuit, |
| ... | @@ -666,7 +660,6 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -666,7 +660,6 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 666 | settings.continueThreadsAfterServerUpdate, | 660 | settings.continueThreadsAfterServerUpdate, |
| 667 | settings.sidebarAutoSettleAfterDays, | 661 | settings.sidebarAutoSettleAfterDays, |
| 668 | settings.sidebarAutoSettleOnMerge, | 662 | settings.sidebarAutoSettleOnMerge, |
| 669 | settings.sidebarCompactThreadRows, | ||
| 670 | settings.sidebarProjectGroupingMode, | 663 | settings.sidebarProjectGroupingMode, |
| 671 | settings.sidebarThreadPreviewCount, | 664 | settings.sidebarThreadPreviewCount, |
| 672 | settings.showSkillsInSlashMenu, | 665 | settings.showSkillsInSlashMenu, |
| ... | @@ -744,7 +737,6 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -744,7 +737,6 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 744 | } | 737 | } |
| 745 | updateSettings({ | 738 | updateSettings({ |
| 746 | appearanceContrast: DEFAULT_UNIFIED_SETTINGS.appearanceContrast, | 739 | appearanceContrast: DEFAULT_UNIFIED_SETTINGS.appearanceContrast, |
| 747 | compactSidebarEnabled: DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled, | ||
| 748 | diffColorScheme: DEFAULT_UNIFIED_SETTINGS.diffColorScheme, | 740 | diffColorScheme: DEFAULT_UNIFIED_SETTINGS.diffColorScheme, |
| 749 | timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat, | 741 | timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat, |
| 750 | notificationMode: DEFAULT_UNIFIED_SETTINGS.notificationMode, | 742 | notificationMode: DEFAULT_UNIFIED_SETTINGS.notificationMode, |
| ... | @@ -762,7 +754,6 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -762,7 +754,6 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 762 | panelAnimationDurationMs: DEFAULT_UNIFIED_SETTINGS.panelAnimationDurationMs, | 754 | panelAnimationDurationMs: DEFAULT_UNIFIED_SETTINGS.panelAnimationDurationMs, |
| 763 | sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount, | 755 | sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount, |
| 764 | sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode, | 756 | sidebarProjectGroupingMode: DEFAULT_UNIFIED_SETTINGS.sidebarProjectGroupingMode, |
| 765 | sidebarCompactThreadRows: DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows, | ||
| 766 | sidebarAutoSettleAfterDays: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays, | 757 | sidebarAutoSettleAfterDays: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays, |
| 767 | sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge, | 758 | sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge, |
| 768 | responseStreamingMode: DEFAULT_UNIFIED_SETTINGS.responseStreamingMode, | 759 | responseStreamingMode: DEFAULT_UNIFIED_SETTINGS.responseStreamingMode, |
| ... | @@ -1143,19 +1134,6 @@ export function AppearanceSettingsPanel() { | ... | @@ -1143,19 +1134,6 @@ export function AppearanceSettingsPanel() { |
| 1143 | const [isImportThemeOpen, setIsImportThemeOpen] = useState(false); | 1134 | const [isImportThemeOpen, setIsImportThemeOpen] = useState(false); |
| 1144 | const settings = useScopedSettings(); | 1135 | const settings = useScopedSettings(); |
| 1145 | const updateSettings = useUpdateScopedSettings(); | 1136 | const updateSettings = useUpdateScopedSettings(); |
| 1146 | const compactSidebarMode = settings.compactSidebarEnabled | ||
| 1147 | ? settings.sidebarCompactThreadRows | ||
| 1148 | ? "both" | ||
| 1149 | : "rail" | ||
| 1150 | : settings.sidebarCompactThreadRows | ||
| 1151 | ? "threads" | ||
| 1152 | : "off"; | ||
| 1153 | const compactSidebarModes = { | ||
| 1154 | off: "Off", | ||
| 1155 | rail: "Rail only", | ||
| 1156 | threads: "Threads only", | ||
| 1157 | both: "Both", | ||
| 1158 | }; | ||
| 1159 | const environmentStageLabel = useEnvironmentStageLabel(); | 1137 | const environmentStageLabel = useEnvironmentStageLabel(); |
| 1160 | const showEnvironmentIdentification = | 1138 | const showEnvironmentIdentification = |
| 1161 | resolveEnvironmentIdentificationPillLabel(environmentStageLabel) !== null; | 1139 | resolveEnvironmentIdentificationPillLabel(environmentStageLabel) !== null; |
| ... | @@ -1442,64 +1420,6 @@ export function AppearanceSettingsPanel() { | ... | @@ -1442,64 +1420,6 @@ export function AppearanceSettingsPanel() { |
| 1442 | /> | 1420 | /> |
| 1443 | </SettingsSection> | 1421 | </SettingsSection> |
| 1444 | 1422 | ||
| 1445 | <SettingsSection id="appearance-sidebar" title="Sidebar"> | ||
| 1446 | <SettingsRow | ||
| 1447 | {...searchableSetting("compact-sidebar")} | ||
| 1448 | description="Choose a collapsed icon rail, denser thread rows, or both. Click the preview to collapse or expand." | ||
| 1449 | resetAction={ | ||
| 1450 | settings.compactSidebarEnabled !== DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled || | ||
| 1451 | settings.sidebarCompactThreadRows !== | ||
| 1452 | DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows ? ( | ||
| 1453 | <SettingResetButton | ||
| 1454 | label="compact sidebar" | ||
| 1455 | onClick={() => | ||
| 1456 | updateSettings({ | ||
| 1457 | compactSidebarEnabled: DEFAULT_UNIFIED_SETTINGS.compactSidebarEnabled, | ||
| 1458 | sidebarCompactThreadRows: DEFAULT_UNIFIED_SETTINGS.sidebarCompactThreadRows, | ||
| 1459 | }) | ||
| 1460 | } | ||
| 1461 | /> | ||
| 1462 | ) : null | ||
| 1463 | } | ||
| 1464 | control={ | ||
| 1465 | <div className="grid w-full grid-cols-[5rem_1fr] items-center gap-3 sm:w-auto sm:grid-cols-[7rem_10rem] sm:gap-4"> | ||
| 1466 | <CompactSidebarPreview | ||
| 1467 | key={compactSidebarMode} | ||
| 1468 | railEnabled={settings.compactSidebarEnabled} | ||
| 1469 | compactRows={settings.sidebarCompactThreadRows} | ||
| 1470 | /> | ||
| 1471 | <Select | ||
| 1472 | value={compactSidebarMode} | ||
| 1473 | onValueChange={(value) => { | ||
| 1474 | if ( | ||
| 1475 | value !== "off" && | ||
| 1476 | value !== "rail" && | ||
| 1477 | value !== "threads" && | ||
| 1478 | value !== "both" | ||
| 1479 | ) | ||
| 1480 | return; | ||
| 1481 | updateSettings({ | ||
| 1482 | compactSidebarEnabled: value === "rail" || value === "both", | ||
| 1483 | sidebarCompactThreadRows: value === "threads" || value === "both", | ||
| 1484 | }); | ||
| 1485 | }} | ||
| 1486 | > | ||
| 1487 | <SelectTrigger size="sm" className="w-full" aria-label="Compact sidebar"> | ||
| 1488 | <SelectValue>{compactSidebarModes[compactSidebarMode]}</SelectValue> | ||
| 1489 | </SelectTrigger> | ||
| 1490 | <SelectPopup align="end" alignItemWithTrigger={false}> | ||
| 1491 | {Object.entries(compactSidebarModes).map(([value, label]) => ( | ||
| 1492 | <SelectItem key={value} hideIndicator value={value}> | ||
| 1493 | {label} | ||
| 1494 | </SelectItem> | ||
| 1495 | ))} | ||
| 1496 | </SelectPopup> | ||
| 1497 | </Select> | ||
| 1498 | </div> | ||
| 1499 | } | ||
| 1500 | /> | ||
| 1501 | </SettingsSection> | ||
| 1502 | |||
| 1503 | <TypographySection /> | 1423 | <TypographySection /> |
| 1504 | </SettingsPageContainer> | 1424 | </SettingsPageContainer> |
| 1505 | ); | 1425 | ); |
apps/web/src/components/settings/SettingsSidebarNav.tsx+7-26| ... | @@ -24,7 +24,6 @@ import { | ... | @@ -24,7 +24,6 @@ import { |
| 24 | XIcon, | 24 | XIcon, |
| 25 | } from "lucide-react"; | 25 | } from "lucide-react"; |
| 26 | import { useLocation, useNavigate } from "@tanstack/react-router"; | 26 | import { useLocation, useNavigate } from "@tanstack/react-router"; |
| 27 | import { useCompactSidebarEnabled } from "../../hooks/useSettings"; | ||
| 28 | 27 | ||
| 29 | import { Button } from "../ui/button"; | 28 | import { Button } from "../ui/button"; |
| 30 | import { Input } from "../ui/input"; | 29 | import { Input } from "../ui/input"; |
| ... | @@ -112,13 +111,12 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -112,13 +111,12 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 112 | (item) => item.to !== "/settings/projects" || isSettingsOverviewVisible(scopeSearch), | 111 | (item) => item.to !== "/settings/projects" || isSettingsOverviewVisible(scopeSearch), |
| 113 | ); | 112 | ); |
| 114 | const { isMobile, setOpenMobile, open, setOpen } = useSidebar(); | 113 | const { isMobile, setOpenMobile, open, setOpen } = useSidebar(); |
| 115 | const compactSidebarEnabled = useCompactSidebarEnabled(); | ||
| 116 | const searchInputRef = useRef<HTMLInputElement>(null); | 114 | const searchInputRef = useRef<HTMLInputElement>(null); |
| 117 | const [query, setQuery] = useState(""); | 115 | const [query, setQuery] = useState(""); |
| 118 | const [activeResultIndex, setActiveResultIndex] = useState(0); | 116 | const [activeResultIndex, setActiveResultIndex] = useState(0); |
| 119 | const searchableItems = useAvailableSettingsSearchItems(); | 117 | const searchableItems = useAvailableSettingsSearchItems(); |
| 120 | const results = useMemo(() => searchSettings(query, searchableItems), [query, searchableItems]); | 118 | const results = useMemo(() => searchSettings(query, searchableItems), [query, searchableItems]); |
| 121 | const isSearching = query.trim().length > 0 && !(compactSidebarEnabled && !isMobile && !open); | 119 | const isSearching = query.trim().length > 0; |
| 122 | const hasResults = results.length > 0; | 120 | const hasResults = results.length > 0; |
| 123 | 121 | ||
| 124 | useEffect(() => { | 122 | useEffect(() => { |
| ... | @@ -235,18 +233,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -235,18 +233,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 235 | <> | 233 | <> |
| 236 | <SidebarContent className="overflow-x-hidden"> | 234 | <SidebarContent className="overflow-x-hidden"> |
| 237 | <SidebarGroup className="gap-2 p-[var(--sidebar-content-inset)]"> | 235 | <SidebarGroup className="gap-2 p-[var(--sidebar-content-inset)]"> |
| 238 | <SidebarMenuButton | 236 | <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 | className="hidden group-data-[collapsible=icon]:flex" | ||
| 240 | aria-label="Search settings" | ||
| 241 | tooltip="Search settings" | ||
| 242 | onClick={() => { | ||
| 243 | setOpen(true); | ||
| 244 | requestAnimationFrame(() => searchInputRef.current?.focus()); | ||
| 245 | }} | ||
| 246 | > | ||
| 247 | <SearchIcon /> | ||
| 248 | </SidebarMenuButton> | ||
| 249 | <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 group-data-[collapsible=icon]:hidden"> | ||
| 250 | <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> | 237 | <SearchIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> |
| 251 | <Input | 238 | <Input |
| 252 | ref={searchInputRef} | 239 | ref={searchInputRef} |
| ... | @@ -343,14 +330,10 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -343,14 +330,10 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 343 | <SidebarMenuItem key={item.to}> | 330 | <SidebarMenuItem key={item.to}> |
| 344 | <SidebarMenuButton | 331 | <SidebarMenuButton |
| 345 | isActive={isActive} | 332 | isActive={isActive} |
| 346 | aria-label={item.label} | ||
| 347 | tooltip={item.label} | ||
| 348 | onClick={() => handleSectionClick(item.to)} | 333 | onClick={() => handleSectionClick(item.to)} |
| 349 | > | 334 | > |
| 350 | <Icon /> | 335 | <Icon /> |
| 351 | <span className="truncate group-data-[collapsible=icon]:hidden"> | 336 | <span className="truncate">{item.label}</span> |
| 352 | {item.label} | ||
| 353 | </span> | ||
| 354 | </SidebarMenuButton> | 337 | </SidebarMenuButton> |
| 355 | </SidebarMenuItem> | 338 | </SidebarMenuItem> |
| 356 | ); | 339 | ); |
| ... | @@ -360,12 +343,10 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -360,12 +343,10 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 360 | </SidebarGroup> | 343 | </SidebarGroup> |
| 361 | </SidebarContent> | 344 | </SidebarContent> |
| 362 | <SidebarFooter className="px-[var(--sidebar-content-inset)] py-1"> | 345 | <SidebarFooter className="px-[var(--sidebar-content-inset)] py-1"> |
| 363 | <div className="contents group-data-[collapsible=icon]:hidden"> | 346 | <Suspense fallback={null}> |
| 364 | <Suspense fallback={null}> | 347 | <T3ConnectSidebarSignIn /> |
| 365 | <T3ConnectSidebarSignIn /> | 348 | </Suspense> |
| 366 | </Suspense> | 349 | <div className="flex items-center gap-1"> |
| 367 | </div> | ||
| 368 | <div className="flex items-center gap-1 group-data-[collapsible=icon]:flex-col"> | ||
| 369 | <div className="min-w-0 flex-1"> | 350 | <div className="min-w-0 flex-1"> |
| 370 | <SidebarUtilityMenu /> | 351 | <SidebarUtilityMenu /> |
| 371 | </div> | 352 | </div> |
apps/web/src/components/settings/settingsSearch.ts-8| ... | @@ -159,14 +159,6 @@ export const SETTINGS_SEARCH_ITEMS = [ | ... | @@ -159,14 +159,6 @@ export const SETTINGS_SEARCH_ITEMS = [ |
| 159 | title: "Panel animations", | 159 | title: "Panel animations", |
| 160 | to: "/settings/appearance", | 160 | to: "/settings/appearance", |
| 161 | }, | 161 | }, |
| 162 | { | ||
| 163 | id: "compact-sidebar", | ||
| 164 | title: "Compact sidebar", | ||
| 165 | to: "/settings/appearance", | ||
| 166 | searchTerms: [ | ||
| 167 | "collapsed icons rail hover navigation preview expanded dense density one line rows chats threads compact thread list", | ||
| 168 | ], | ||
| 169 | }, | ||
| 170 | { | 162 | { |
| 171 | id: "environment-identification", | 163 | id: "environment-identification", |
| 172 | title: "Environment identification", | 164 | title: "Environment identification", |
apps/web/src/components/sidebar/SidebarChrome.tsx+6-8| ... | @@ -86,7 +86,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) { | ... | @@ -86,7 +86,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) { |
| 86 | <Link | 86 | <Link |
| 87 | aria-label="Go to threads" | 87 | aria-label="Go to threads" |
| 88 | className={cn( | 88 | className={cn( |
| 89 | "relative z-10 ml-[var(--workspace-titlebar-content-left)] hidden h-7 w-fit min-w-0 shrink-0 items-center overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2 md:flex group-data-[collapsible=icon]:hidden", | 89 | "relative z-10 ml-[var(--workspace-titlebar-content-left)] hidden h-7 w-fit min-w-0 shrink-0 items-center overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2 md:flex", |
| 90 | onBackdrop ? "text-white" : "text-foreground", | 90 | onBackdrop ? "text-white" : "text-foreground", |
| 91 | )} | 91 | )} |
| 92 | to="/" | 92 | to="/" |
| ... | @@ -187,12 +187,12 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { | ... | @@ -187,12 +187,12 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { |
| 187 | }, [canGoBack, closeMobileSidebar, navigate]); | 187 | }, [canGoBack, closeMobileSidebar, navigate]); |
| 188 | 188 | ||
| 189 | return ( | 189 | return ( |
| 190 | <SidebarMenu className="flex-row items-center group-data-[collapsible=icon]:flex-col"> | 190 | <SidebarMenu className="flex-row items-center"> |
| 191 | {currentFooterPage ? ( | 191 | {currentFooterPage ? ( |
| 192 | <SidebarMenuItem className="min-w-0 flex-1"> | 192 | <SidebarMenuItem className="min-w-0 flex-1"> |
| 193 | <SidebarMenuButton onClick={handleBackClick} aria-label="Back" tooltip="Back"> | 193 | <SidebarMenuButton onClick={handleBackClick}> |
| 194 | <ArrowLeftIcon /> | 194 | <ArrowLeftIcon /> |
| 195 | <span className="group-data-[collapsible=icon]:hidden">Back</span> | 195 | <span>Back</span> |
| 196 | </SidebarMenuButton> | 196 | </SidebarMenuButton> |
| 197 | </SidebarMenuItem> | 197 | </SidebarMenuItem> |
| 198 | ) : ( | 198 | ) : ( |
| ... | @@ -224,10 +224,8 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { | ... | @@ -224,10 +224,8 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { |
| 224 | export const SidebarChromeFooter = memo(function SidebarChromeFooter() { | 224 | export const SidebarChromeFooter = memo(function SidebarChromeFooter() { |
| 225 | return ( | 225 | return ( |
| 226 | <SidebarFooter className="px-[var(--sidebar-content-inset)] py-1"> | 226 | <SidebarFooter className="px-[var(--sidebar-content-inset)] py-1"> |
| 227 | <div className="contents group-data-[collapsible=icon]:hidden"> | 227 | <SidebarProviderUpdatePill /> |
| 228 | <SidebarProviderUpdatePill /> | 228 | <SidebarUpdateArchitectureWarning /> |
| 229 | <SidebarUpdateArchitectureWarning /> | ||
| 230 | </div> | ||
| 231 | <SidebarUtilityMenu /> | 229 | <SidebarUtilityMenu /> |
| 232 | </SidebarFooter> | 230 | </SidebarFooter> |
| 233 | ); | 231 | ); |
apps/web/src/components/sidebar/SidebarCompletedTime.test.tsx deleted-56| ... | @@ -1,56 +0,0 @@ | ||
| 1 | import { act, memo } from "react"; | ||
| 2 | import { create, type ReactTestRenderer } from "react-test-renderer"; | ||
| 3 | import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test"; | ||
| 4 | |||
| 5 | import { SidebarCompletedTime } from "./SidebarCompletedTime"; | ||
| 6 | |||
| 7 | let renderer: ReactTestRenderer | undefined; | ||
| 8 | |||
| 9 | beforeEach(() => { | ||
| 10 | vi.useFakeTimers(); | ||
| 11 | vi.setSystemTime(new Date("2026-09-07T01:01:00Z")); | ||
| 12 | vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); | ||
| 13 | vi.stubGlobal("window", { | ||
| 14 | setTimeout, | ||
| 15 | clearTimeout, | ||
| 16 | setInterval, | ||
| 17 | clearInterval, | ||
| 18 | }); | ||
| 19 | }); | ||
| 20 | |||
| 21 | afterEach(async () => { | ||
| 22 | await act(() => renderer?.unmount()); | ||
| 23 | renderer = undefined; | ||
| 24 | vi.unstubAllGlobals(); | ||
| 25 | vi.useRealTimers(); | ||
| 26 | }); | ||
| 27 | |||
| 28 | it("advances visible and accessible completion times without rerendering its memoized row", async () => { | ||
| 29 | const rowRender = vi.fn(); | ||
| 30 | const Row = memo(function Row() { | ||
| 31 | rowRender(); | ||
| 32 | return <SidebarCompletedTime completedAt="2026-09-07T01:00:00Z" />; | ||
| 33 | }); | ||
| 34 | await act(() => { | ||
| 35 | renderer = create(<Row />); | ||
| 36 | }); | ||
| 37 | expect(renderer!.root.findByType("time").props.dateTime).toBe("2026-09-07T01:00:00Z"); | ||
| 38 | expect(renderer!.root.findByProps({ className: "sr-only" }).children).toEqual(["Completed "]); | ||
| 39 | expect( | ||
| 40 | renderer!.root.findAll((node) => node.props.role === "status" || node.props["aria-live"]), | ||
| 41 | ).toHaveLength(0); | ||
| 42 | expect(renderer!.root.findByProps({ className: "text-secondary-label" }).children).toEqual([ | ||
| 43 | "1m", | ||
| 44 | ]); | ||
| 45 | |||
| 46 | await act(() => vi.advanceTimersByTime(60_000)); | ||
| 47 | |||
| 48 | expect(renderer!.root.findByProps({ className: "sr-only" }).children).toEqual(["Completed "]); | ||
| 49 | expect(renderer!.root.findByProps({ className: "text-secondary-label" }).children).toEqual([ | ||
| 50 | "2m", | ||
| 51 | ]); | ||
| 52 | expect(rowRender).toHaveBeenCalledTimes(1); | ||
| 53 | await act(() => renderer!.unmount()); | ||
| 54 | renderer = undefined; | ||
| 55 | expect(vi.getTimerCount()).toBe(0); | ||
| 56 | }); | ||
apps/web/src/components/sidebar/SidebarCompletedTime.tsx deleted-16| ... | @@ -1,16 +0,0 @@ | ||
| 1 | import { useNowMinute } from "../../hooks/useNowMinute"; | ||
| 2 | import { formatRelativeTimeLabel } from "../../timestampFormat"; | ||
| 3 | |||
| 4 | export function SidebarCompletedTime({ completedAt }: { completedAt: string }) { | ||
| 5 | // Subscribe inside the label so time advances even when the row is memoized. | ||
| 6 | const nowMinute = useNowMinute(); | ||
| 7 | const relativeTime = formatRelativeTimeLabel(completedAt, Date.parse(`${nowMinute}:00Z`)); | ||
| 8 | const label = relativeTime === "just now" ? "now" : relativeTime.replace(/ ago$/, ""); | ||
| 9 | |||
| 10 | return ( | ||
| 11 | <time dateTime={completedAt} className="inline-flex items-center"> | ||
| 12 | <span className="sr-only">Completed </span> | ||
| 13 | <span className="text-secondary-label">{label}</span> | ||
| 14 | </time> | ||
| 15 | ); | ||
| 16 | } | ||
apps/web/src/components/sidebar/SidebarThreadHeader.tsx+4-27| ... | @@ -20,10 +20,9 @@ import { | ... | @@ -20,10 +20,9 @@ import { |
| 20 | } from "react"; | 20 | } from "react"; |
| 21 | 21 | ||
| 22 | import { cn } from "~/lib/utils"; | 22 | import { cn } from "~/lib/utils"; |
| 23 | import { useCompactSidebarEnabled } from "../../hooks/useSettings"; | ||
| 24 | import { Button } from "../ui/button"; | 23 | import { Button } from "../ui/button"; |
| 25 | import { Input } from "../ui/input"; | 24 | import { Input } from "../ui/input"; |
| 26 | import { SidebarMenuButton, useSidebar } from "../ui/sidebar"; | 25 | import { SidebarMenuButton } from "../ui/sidebar"; |
| 27 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 26 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 28 | 27 | ||
| 29 | export interface SidebarThreadHeaderProps { | 28 | export interface SidebarThreadHeaderProps { |
| ... | @@ -70,9 +69,6 @@ export function SidebarThreadHeader({ | ... | @@ -70,9 +69,6 @@ export function SidebarThreadHeader({ |
| 70 | activeSearchResultIndex, | 69 | activeSearchResultIndex, |
| 71 | onClearSearch, | 70 | onClearSearch, |
| 72 | }: SidebarThreadHeaderProps) { | 71 | }: SidebarThreadHeaderProps) { |
| 73 | const compactEnabled = useCompactSidebarEnabled(); | ||
| 74 | const { state, isMobile, setOpen } = useSidebar(); | ||
| 75 | const compact = compactEnabled && state === "collapsed" && !isMobile; | ||
| 76 | const resultsVisible = isSearching && searchResultCount > 0; | 72 | const resultsVisible = isSearching && searchResultCount > 0; |
| 77 | // Results shrink as the query narrows, so the active index can outrun the | 73 | // Results shrink as the query narrows, so the active index can outrun the |
| 78 | // list; pointing aria-activedescendant at a removed option strands the | 74 | // list; pointing aria-activedescendant at a removed option strands the |
| ... | @@ -83,24 +79,10 @@ export function SidebarThreadHeader({ | ... | @@ -83,24 +79,10 @@ export function SidebarThreadHeader({ |
| 83 | : "New thread"; | 79 | : "New thread"; |
| 84 | 80 | ||
| 85 | return ( | 81 | return ( |
| 86 | <div className={cn("flex items-center gap-1", compact && "flex-col")}> | 82 | <div className="flex items-center gap-1"> |
| 87 | {compact ? ( | ||
| 88 | <SidebarHeaderIconButton | ||
| 89 | label="Search threads" | ||
| 90 | onClick={() => { | ||
| 91 | setOpen(true); | ||
| 92 | requestAnimationFrame(() => searchInputRef.current?.focus()); | ||
| 93 | }} | ||
| 94 | > | ||
| 95 | <SearchIcon /> | ||
| 96 | </SidebarHeaderIconButton> | ||
| 97 | ) : null} | ||
| 98 | <div | 83 | <div |
| 99 | ref={searchFieldRef} | 84 | ref={searchFieldRef} |
| 100 | className={cn( | 85 | className="flex h-8 min-w-0 flex-1 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" |
| 101 | "flex h-8 min-w-0 flex-1 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", | ||
| 102 | compact && "hidden", | ||
| 103 | )} | ||
| 104 | > | 86 | > |
| 105 | <SearchIcon className="size-4 shrink-0 text-[var(--sidebar-icon-color)]" /> | 87 | <SearchIcon className="size-4 shrink-0 text-[var(--sidebar-icon-color)]" /> |
| 106 | <Input | 88 | <Input |
| ... | @@ -142,12 +124,7 @@ export function SidebarThreadHeader({ | ... | @@ -142,12 +124,7 @@ export function SidebarThreadHeader({ |
| 142 | </div> | 124 | </div> |
| 143 | {/* Segmented well: the icons read as one control instead of three loose | 125 | {/* Segmented well: the icons read as one control instead of three loose |
| 144 | buttons competing with the search field beside them. */} | 126 | buttons competing with the search field beside them. */} |
| 145 | <div | 127 | <div className="flex shrink-0 items-center rounded-md bg-sidebar-control-surface/60 p-px"> |
| 146 | className={cn( | ||
| 147 | "flex shrink-0 items-center rounded-md bg-sidebar-control-surface/60 p-px", | ||
| 148 | compact && "flex-col", | ||
| 149 | )} | ||
| 150 | > | ||
| 151 | {hasProjects ? ( | 128 | {hasProjects ? ( |
| 152 | <> | 129 | <> |
| 153 | {projectScope} | 130 | {projectScope} |
apps/web/src/components/sidebar/SidebarUpdatePill.tsx+1-1| ... | @@ -348,7 +348,7 @@ function SidebarUpdateControl() { | ... | @@ -348,7 +348,7 @@ function SidebarUpdateControl() { |
| 348 | ); | 348 | ); |
| 349 | 349 | ||
| 350 | return ( | 350 | return ( |
| 351 | <SidebarMenuItem className="ml-auto shrink-0 group-data-[collapsible=icon]:hidden"> | 351 | <SidebarMenuItem className="ml-auto shrink-0"> |
| 352 | <Popover | 352 | <Popover |
| 353 | handle={releaseNotesPopoverHandle} | 353 | handle={releaseNotesPopoverHandle} |
| 354 | onOpenChange={(open, details) => { | 354 | onOpenChange={(open, details) => { |
apps/web/src/components/ui/sidebar.tsx-3| ... | @@ -591,11 +591,9 @@ function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof S | ... | @@ -591,11 +591,9 @@ function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof S |
| 591 | function SidebarContent({ | 591 | function SidebarContent({ |
| 592 | className, | 592 | className, |
| 593 | fixedHeader, | 593 | fixedHeader, |
| 594 | fixedFooter, | ||
| 595 | ...props | 594 | ...props |
| 596 | }: React.ComponentProps<"div"> & { | 595 | }: React.ComponentProps<"div"> & { |
| 597 | fixedHeader?: React.ReactNode; | 596 | fixedHeader?: React.ReactNode; |
| 598 | fixedFooter?: React.ReactNode; | ||
| 599 | }) { | 597 | }) { |
| 600 | return ( | 598 | return ( |
| 601 | <> | 599 | <> |
| ... | @@ -619,7 +617,6 @@ function SidebarContent({ | ... | @@ -619,7 +617,6 @@ function SidebarContent({ |
| 619 | {...props} | 617 | {...props} |
| 620 | /> | 618 | /> |
| 621 | </ScrollArea> | 619 | </ScrollArea> |
| 622 | {fixedFooter ? <div className="w-full shrink-0">{fixedFooter}</div> : null} | ||
| 623 | </> | 620 | </> |
| 624 | ); | 621 | ); |
| 625 | } | 622 | } |
apps/web/src/hooks/useSettings.ts-7| ... | @@ -379,13 +379,6 @@ export function useLegacySidebarEnabled(): boolean { | ... | @@ -379,13 +379,6 @@ export function useLegacySidebarEnabled(): boolean { |
| 379 | return settingsHydrated && legacySidebarEnabled; | 379 | return settingsHydrated && legacySidebarEnabled; |
| 380 | } | 380 | } |
| 381 | 381 | ||
| 382 | /** Keep the default collapsed sidebar until persisted client settings hydrate. */ | ||
| 383 | export function useCompactSidebarEnabled(): boolean { | ||
| 384 | const settingsHydrated = useClientSettingsHydrated(); | ||
| 385 | const compactSidebarEnabled = useClientSettingsValue().compactSidebarEnabled; | ||
| 386 | return settingsHydrated && compactSidebarEnabled; | ||
| 387 | } | ||
| 388 | |||
| 389 | /** Read current settings for one environment, merged with client-local preferences. */ | 382 | /** Read current settings for one environment, merged with client-local preferences. */ |
| 390 | export function useEnvironmentSettings<T = UnifiedSettings>( | 383 | export function useEnvironmentSettings<T = UnifiedSettings>( |
| 391 | environmentId: EnvironmentId, | 384 | environmentId: EnvironmentId, |
apps/web/src/timestampFormat.test.ts-12| ... | @@ -228,15 +228,3 @@ describe("formatElapsedDurationLabel", () => { | ... | @@ -228,15 +228,3 @@ describe("formatElapsedDurationLabel", () => { |
| 228 | expect(formatElapsedDurationLabel("2026-04-03T12:00:00.000Z")).toBe("4d"); | 228 | expect(formatElapsedDurationLabel("2026-04-03T12:00:00.000Z")).toBe("4d"); |
| 229 | }); | 229 | }); |
| 230 | }); | 230 | }); |
| 231 | |||
| 232 | describe("explicit relative-time clock", () => { | ||
| 233 | it("uses the supplied minute instead of the wall clock", () => { | ||
| 234 | const completedAt = "2026-09-07T01:00:00Z"; | ||
| 235 | expect(formatRelativeTimeLabel(completedAt, Date.parse("2026-09-07T01:01:00Z"))).toBe("1m ago"); | ||
| 236 | expect(formatRelativeTimeLabel(completedAt, Date.parse("2026-09-07T01:02:00Z"))).toBe("2m ago"); | ||
| 237 | expect(formatRelativeTime(completedAt, Date.parse("2026-09-07T01:02:00Z"))).toEqual({ | ||
| 238 | value: "2m", | ||
| 239 | suffix: "ago", | ||
| 240 | }); | ||
| 241 | }); | ||
| 242 | }); |
apps/web/src/timestampFormat.ts+4-4| ... | @@ -196,10 +196,10 @@ export type RelativeTimeState = | ... | @@ -196,10 +196,10 @@ export type RelativeTimeState = |
| 196 | | { status: "invalid" } | 196 | | { status: "invalid" } |
| 197 | | { status: "relative"; value: string; suffix: string | null }; | 197 | | { status: "relative"; value: string; suffix: string | null }; |
| 198 | 198 | ||
| 199 | export function formatRelativeTime(isoDate: string, nowMs = Date.now()): RelativeTimeParts | null { | 199 | export function formatRelativeTime(isoDate: string): RelativeTimeParts | null { |
| 200 | const date = parseTimestampDate(isoDate); | 200 | const date = parseTimestampDate(isoDate); |
| 201 | if (!date) return null; | 201 | if (!date) return null; |
| 202 | const diffMs = nowMs - date.getTime(); | 202 | const diffMs = Date.now() - date.getTime(); |
| 203 | if (diffMs < 0) return { value: "just now", suffix: null }; | 203 | if (diffMs < 0) return { value: "just now", suffix: null }; |
| 204 | const seconds = Math.floor(diffMs / 1000); | 204 | const seconds = Math.floor(diffMs / 1000); |
| 205 | if (seconds < 60) return { value: "just now", suffix: null }; | 205 | if (seconds < 60) return { value: "just now", suffix: null }; |
| ... | @@ -211,8 +211,8 @@ export function formatRelativeTime(isoDate: string, nowMs = Date.now()): Relativ | ... | @@ -211,8 +211,8 @@ export function formatRelativeTime(isoDate: string, nowMs = Date.now()): Relativ |
| 211 | return { value: `${days}d`, suffix: "ago" }; | 211 | return { value: `${days}d`, suffix: "ago" }; |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | export function formatRelativeTimeLabel(isoDate: string, nowMs = Date.now()) { | 214 | export function formatRelativeTimeLabel(isoDate: string) { |
| 215 | const relative = formatRelativeTime(isoDate, nowMs); | 215 | const relative = formatRelativeTime(isoDate); |
| 216 | if (!relative) return ""; | 216 | if (!relative) return ""; |
| 217 | return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value; | 217 | return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value; |
| 218 | } | 218 | } |
apps/web/src/workspaceTitlebar.ts+1-1| ... | @@ -1,2 +1,2 @@ | ... | @@ -1,2 +1,2 @@ |
| 1 | export const COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS = | 1 | export const COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS = |
| 2 | "[[data-sidebar-state=collapsed]_&]:pl-[var(--workspace-titlebar-content-left)] [[data-sidebar-state=collapsed]:has([data-side=left][data-collapsible=icon])_&]:pl-[max(calc(env(safe-area-inset-left)+1.25rem),calc(var(--workspace-titlebar-content-left)-var(--sidebar-width-icon)))]"; | 2 | "[[data-sidebar-state=collapsed]_&]:pl-[var(--workspace-titlebar-content-left)]"; |
packages/contracts/src/settings.test.ts+9-12| ... | @@ -482,18 +482,7 @@ describe("ClientSettings environment identification", () => { | ... | @@ -482,18 +482,7 @@ describe("ClientSettings environment identification", () => { |
| 482 | 482 | ||
| 483 | describe("ClientSettings sidebar", () => { | 483 | describe("ClientSettings sidebar", () => { |
| 484 | it("defaults to the current sidebar", () => { | 484 | it("defaults to the current sidebar", () => { |
| 485 | const settings = decodeClientSettings({}); | 485 | expect(decodeClientSettings({}).legacySidebarEnabled).toBe(false); |
| 486 | expect(settings.legacySidebarEnabled).toBe(false); | ||
| 487 | expect(settings.sidebarCompactThreadRows).toBe(false); | ||
| 488 | }); | ||
| 489 | |||
| 490 | it("preserves an explicit compact thread row preference", () => { | ||
| 491 | expect(decodeClientSettings({ sidebarCompactThreadRows: true }).sidebarCompactThreadRows).toBe( | ||
| 492 | true, | ||
| 493 | ); | ||
| 494 | expect( | ||
| 495 | decodeClientSettingsPatch({ sidebarCompactThreadRows: true }).sidebarCompactThreadRows, | ||
| 496 | ).toBe(true); | ||
| 497 | }); | 486 | }); |
| 498 | 487 | ||
| 499 | it("drops the retired sidebar v2 beta keys, resetting everyone to the default", () => { | 488 | it("drops the retired sidebar v2 beta keys, resetting everyone to the default", () => { |
| ... | @@ -506,6 +495,14 @@ describe("ClientSettings sidebar", () => { | ... | @@ -506,6 +495,14 @@ describe("ClientSettings sidebar", () => { |
| 506 | expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser"); | 495 | expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser"); |
| 507 | }); | 496 | }); |
| 508 | 497 | ||
| 498 | it("drops the retired compact sidebar keys for users who opted in", () => { | ||
| 499 | const stored = { compactSidebarEnabled: true, sidebarCompactThreadRows: true }; | ||
| 500 | const decoded = decodeClientSettings(stored); | ||
| 501 | expect(decoded).not.toHaveProperty("compactSidebarEnabled"); | ||
| 502 | expect(decoded).not.toHaveProperty("sidebarCompactThreadRows"); | ||
| 503 | expect(decodeClientSettingsPatch(stored)).toEqual({}); | ||
| 504 | }); | ||
| 505 | |||
| 509 | it("preserves an explicit legacy sidebar opt-in", () => { | 506 | it("preserves an explicit legacy sidebar opt-in", () => { |
| 510 | expect(decodeClientSettings({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(true); | 507 | expect(decodeClientSettings({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe(true); |
| 511 | expect(decodeClientSettingsPatch({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe( | 508 | expect(decodeClientSettingsPatch({ legacySidebarEnabled: true }).legacySidebarEnabled).toBe( |
packages/contracts/src/settings.ts-4| ... | @@ -437,7 +437,6 @@ export const ClientSettingsSchema = Schema.Struct({ | ... | @@ -437,7 +437,6 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 437 | // old keys, so everyone, including prior beta opt-outs, resets to the new | 437 | // old keys, so everyone, including prior beta opt-outs, resets to the new |
| 438 | // default sidebar. | 438 | // default sidebar. |
| 439 | legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 439 | legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 440 | compactSidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | ||
| 441 | sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe( | 440 | sidebarProjectGroupingMode: SidebarProjectGroupingMode.pipe( |
| 442 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)), | 441 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE)), |
| 443 | ), | 442 | ), |
| ... | @@ -451,7 +450,6 @@ export const ClientSettingsSchema = Schema.Struct({ | ... | @@ -451,7 +450,6 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 451 | sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( | 450 | sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( |
| 452 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)), | 451 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)), |
| 453 | ), | 452 | ), |
| 454 | sidebarCompactThreadRows: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | ||
| 455 | sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe( | 453 | sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe( |
| 456 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT)), | 454 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT)), |
| 457 | ), | 455 | ), |
| ... | @@ -1504,14 +1502,12 @@ export const ClientSettingsPatch = Schema.Struct({ | ... | @@ -1504,14 +1502,12 @@ export const ClientSettingsPatch = Schema.Struct({ |
| 1504 | proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean), | 1502 | proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean), |
| 1505 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), | 1503 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), |
| 1506 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), | 1504 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), |
| 1507 | compactSidebarEnabled: Schema.optionalKey(Schema.Boolean), | ||
| 1508 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), | 1505 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), |
| 1509 | sidebarProjectGroupingOverrides: Schema.optionalKey( | 1506 | sidebarProjectGroupingOverrides: Schema.optionalKey( |
| 1510 | Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode), | 1507 | Schema.Record(TrimmedNonEmptyString, SidebarProjectGroupingMode), |
| 1511 | ), | 1508 | ), |
| 1512 | sidebarProjectSortOrder: Schema.optionalKey(SidebarProjectSortOrder), | 1509 | sidebarProjectSortOrder: Schema.optionalKey(SidebarProjectSortOrder), |
| 1513 | sidebarThreadSortOrder: Schema.optionalKey(SidebarThreadSortOrder), | 1510 | sidebarThreadSortOrder: Schema.optionalKey(SidebarThreadSortOrder), |
| 1514 | sidebarCompactThreadRows: Schema.optionalKey(Schema.Boolean), | ||
| 1515 | sidebarThreadPreviewCount: Schema.optionalKey(SidebarThreadPreviewCount), | 1511 | sidebarThreadPreviewCount: Schema.optionalKey(SidebarThreadPreviewCount), |
| 1516 | timestampFormat: Schema.optionalKey(TimestampFormat), | 1512 | timestampFormat: Schema.optionalKey(TimestampFormat), |
| 1517 | snapShotEnabled: Schema.optionalKey(Schema.Boolean), | 1513 | snapShotEnabled: Schema.optionalKey(Schema.Boolean), |