From 18242b22d2b0202d6dc2339b5f91ebed2730ca53 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Jul 2026 00:16:06 +0200 Subject: [PATCH 1/7] group projects in new-thread pickers Co-authored-by: codex --- apps/web/src/components/CommandPalette.tsx | 143 +++++++++++++++++++-- apps/web/src/routes/_chat.tsx | 23 +++- 2 files changed, 150 insertions(+), 16 deletions(-) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 4f330baf665..ee7a79a1fc5 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -59,6 +59,7 @@ import { useAtomQueryRunner } from "../state/use-atom-query-runner"; import { useEnvironments, usePrimaryEnvironment } from "../state/environments"; import { useProjects, useThreadShells } from "../state/entities"; import { + resolveThreadActionProjectRef, startNewThreadInProjectFromContext, startNewThreadFromContext, } from "../lib/chatThreadActions"; @@ -79,7 +80,7 @@ import { } from "../lib/projectPaths"; import { onOpenCommandPalette } from "../commandPaletteBus"; import { isTerminalFocused } from "../lib/terminalFocus"; -import { getLatestThreadForProject } from "../lib/threadSort"; +import { getLatestThreadForProject, sortThreads } from "../lib/threadSort"; import { cn, isMacPlatform, isWindowsPlatform, newProjectId } from "../lib/utils"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; import { buildThreadRouteParams, resolveThreadRouteTarget } from "../threadRoutes"; @@ -106,6 +107,7 @@ import { ITEM_ICON_CLASS, RECENT_THREAD_LIMIT, } from "./CommandPalette.logic"; +import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic"; import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; import { CommandPaletteResults } from "./CommandPaletteResults"; import { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitLabIcon } from "./Icons"; @@ -128,6 +130,12 @@ import { stackedThreadToast, toastManager } from "./ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; +import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; +import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; +import { + buildSidebarProjectPickerEntries, + buildSidebarProjectSnapshots, +} from "../sidebarProjectGrouping"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; @@ -491,6 +499,7 @@ function OpenCommandPaletteDialog(props: { const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } = useHandleNewThread(); const projects = useProjects(); + const projectOrder = useUiStateStore((store) => store.projectOrder); const threads = useThreadShells(); const keybindings = useAtomValue(primaryServerKeybindingsAtom); const providers = useAtomValue(primaryServerProvidersAtom); @@ -505,6 +514,93 @@ function OpenCommandPaletteDialog(props: { const [isRemoteProjectLookingUp, setIsRemoteProjectLookingUp] = useState(false); const [isRemoteProjectCloning, setIsRemoteProjectCloning] = useState(false); const primaryEnvironmentId = primaryEnvironment?.environmentId ?? null; + const projectGroupingSettings = useMemo( + () => selectProjectGroupingSettings(clientSettings), + [clientSettings], + ); + + const environmentLabelById = useMemo( + () => + new Map( + environments.map((environment) => [environment.environmentId, environment.label] as const), + ), + [environments], + ); + const orderedProjects = useMemo( + () => + orderItemsByPreferredIds({ + items: projects, + preferredIds: projectOrder, + getId: getProjectOrderKey, + getPreferenceIds: (project) => [ + getProjectOrderKey(project), + legacyProjectCwdPreferenceKey(project.workspaceRoot), + ], + }), + [projectOrder, projects], + ); + const unsortedProjectGroups = useMemo( + () => + buildSidebarProjectSnapshots({ + projects: clientSettings.sidebarProjectSortOrder === "manual" ? orderedProjects : projects, + settings: projectGroupingSettings, + primaryEnvironmentId, + resolveEnvironmentLabel: (environmentId) => environmentLabelById.get(environmentId) ?? null, + }), + [ + clientSettings.sidebarProjectSortOrder, + environmentLabelById, + orderedProjects, + primaryEnvironmentId, + projectGroupingSettings, + projects, + ], + ); + const projectGroups = useMemo( + () => + sortLogicalProjectsForSidebar( + unsortedProjectGroups, + threads, + clientSettings.sidebarProjectSortOrder, + ), + [clientSettings.sidebarProjectSortOrder, threads, unsortedProjectGroups], + ); + const contextualProjectRef = useMemo( + () => + resolveThreadActionProjectRef({ + activeDraftThread, + activeThread: activeThread ?? undefined, + defaultProjectRef, + handleNewThread, + }), + [activeDraftThread, activeThread, defaultProjectRef, handleNewThread], + ); + const projectPickerEntries = useMemo( + () => + buildSidebarProjectPickerEntries({ + groups: projectGroups, + preferredProjectRef: contextualProjectRef, + }), + [contextualProjectRef, projectGroups], + ); + const pickerProjects = useMemo( + () => + projectPickerEntries.map(({ group, targetProject }) => ({ + ...targetProject, + title: group.displayName, + })), + [projectPickerEntries], + ); + const projectGroupByTargetKey = useMemo( + () => + new Map( + projectPickerEntries.map(({ group, targetProject }) => [ + `${targetProject.environmentId}:${targetProject.id}`, + group, + ]), + ), + [projectPickerEntries], + ); const addProjectEnvironmentOptions = useMemo(() => { const options = environments.map((environment): AddProjectEnvironmentOption => { @@ -648,11 +744,28 @@ function OpenCommandPaletteDialog(props: { const openProjectFromSearch = useMemo( () => async (project: (typeof projects)[number]) => { - const latestThread = getLatestThreadForProject( - threads.filter((thread) => thread.environmentId === project.environmentId), - project.id, - clientSettings.sidebarThreadSortOrder, - ); + const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); + const groupedProjectKeys = group + ? new Set( + group.memberProjectRefs.map( + (projectRef) => `${projectRef.environmentId}:${projectRef.projectId}`, + ), + ) + : null; + const latestThread = groupedProjectKeys + ? (sortThreads( + threads.filter( + (thread) => + thread.archivedAt === null && + groupedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`), + ), + clientSettings.sidebarThreadSortOrder, + )[0] ?? null) + : getLatestThreadForProject( + threads.filter((thread) => thread.environmentId === project.environmentId), + project.id, + clientSettings.sidebarThreadSortOrder, + ); if (latestThread) { await navigate({ to: "/$environmentId/$threadId", @@ -665,13 +778,19 @@ function OpenCommandPaletteDialog(props: { await handleNewThread(scopeProjectRef(project.environmentId, project.id)); }, - [handleNewThread, navigate, clientSettings.sidebarThreadSortOrder, threads], + [ + clientSettings.sidebarThreadSortOrder, + handleNewThread, + navigate, + projectGroupByTargetKey, + threads, + ], ); const projectSearchItems = useMemo( () => buildProjectActionItems({ - projects, + projects: pickerProjects, valuePrefix: "project", icon: (project) => ( = []; if (projects.length > 0) { - const activeProjectTitle = currentProjectId - ? (projectTitleById.get(currentProjectId) ?? null) - : null; + const activeProjectTitle = + projectPickerEntries.find((entry) => entry.isPreferred)?.group.displayName ?? + (currentProjectId ? (projectTitleById.get(currentProjectId) ?? null) : null); if (activeProjectTitle) { actionItems.push({ diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx index 57824fe5188..2e29d222b5b 100644 --- a/apps/web/src/routes/_chat.tsx +++ b/apps/web/src/routes/_chat.tsx @@ -1,11 +1,14 @@ import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"; import { useAtomValue } from "@effect/atom-react"; -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { isCommandPaletteOpen } from "../commandPaletteBus"; import { useClientSettings } from "../hooks/useSettings"; import { openCommandPalette } from "../commandPaletteBus"; import { useProjects } from "../state/entities"; +import { usePrimaryEnvironmentId } from "../state/environments"; +import { selectProjectGroupingSettings } from "../logicalProject"; +import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping"; import { dispatchPreviewAction } from "../components/preview/previewActionBus"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { @@ -29,7 +32,19 @@ function ChatRouteGlobalShortcuts() { useHandleNewThread(); const keybindings = useAtomValue(primaryServerKeybindingsAtom); const sidebarV2Enabled = useClientSettings((settings) => settings.sidebarV2Enabled); - const projectCount = useProjects().length; + const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); + const projects = useProjects(); + const primaryEnvironmentId = usePrimaryEnvironmentId(); + const projectGroupCount = useMemo( + () => + buildSidebarProjectSnapshots({ + projects, + settings: projectGroupingSettings, + primaryEnvironmentId, + resolveEnvironmentLabel: () => null, + }).length, + [primaryEnvironmentId, projectGroupingSettings, projects], + ); const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen @@ -83,7 +98,7 @@ function ChatRouteGlobalShortcuts() { // Sidebar v2 routes creation through the command palette whenever // there is a real choice to make; v1 (and single-project setups) // keep the immediate contextual create. - if (sidebarV2Enabled && projectCount > 1) { + if (sidebarV2Enabled && projectGroupCount > 1) { openCommandPalette({ open: "new-thread-in" }); return; } @@ -152,7 +167,7 @@ function ChatRouteGlobalShortcuts() { keybindings, defaultProjectRef, previewOpen, - projectCount, + projectGroupCount, routeThreadRef, selectedThreadKeysSize, sidebarV2Enabled, From af8d73c9215175b0efabd6bf4636e1c2795befc1 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Jul 2026 01:14:31 +0200 Subject: [PATCH 2/7] fix grouped new-thread picker inputs Co-authored-by: codex --- apps/web/src/components/CommandPalette.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index ee7a79a1fc5..fa319b99552 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -56,7 +56,7 @@ import { useEnvironmentQuery } from "../state/query"; import { sourceControlEnvironment } from "../state/sourceControl"; import { useAtomCommand } from "../state/use-atom-command"; import { useAtomQueryRunner } from "../state/use-atom-query-runner"; -import { useEnvironments, usePrimaryEnvironment } from "../state/environments"; +import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; import { useProjects, useThreadShells } from "../state/entities"; import { resolveThreadActionProjectRef, @@ -495,7 +495,7 @@ function OpenCommandPaletteDialog(props: { }); const { environments } = useEnvironments(); const desktopLocalBootstraps = useDesktopLocalBootstraps(); - const primaryEnvironment = usePrimaryEnvironment(); + const primaryEnvironmentId = usePrimaryEnvironmentId(); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } = useHandleNewThread(); const projects = useProjects(); @@ -513,7 +513,6 @@ function OpenCommandPaletteDialog(props: { const [addProjectCloneFlow, setAddProjectCloneFlow] = useState(null); const [isRemoteProjectLookingUp, setIsRemoteProjectLookingUp] = useState(false); const [isRemoteProjectCloning, setIsRemoteProjectCloning] = useState(false); - const primaryEnvironmentId = primaryEnvironment?.environmentId ?? null; const projectGroupingSettings = useMemo( () => selectProjectGroupingSettings(clientSettings), [clientSettings], @@ -808,7 +807,7 @@ function OpenCommandPaletteDialog(props: { () => enumerateCommandPaletteItems( buildProjectActionItems({ - projects, + projects: pickerProjects, valuePrefix: "new-thread-in", icon: (project) => ( Date: Thu, 23 Jul 2026 01:22:21 +0200 Subject: [PATCH 3/7] preserve grouped picker search and context Co-authored-by: codex --- .../src/components/CommandPalette.logic.ts | 3 +- apps/web/src/components/CommandPalette.tsx | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index a217d53b5b7..f69c38e1a0f 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -111,12 +111,13 @@ export function buildProjectActionItems(input: { valuePrefix: string; icon: (project: Project) => ReactNode; runProject: (project: Project) => Promise; + searchTerms?: (project: Project) => ReadonlyArray; shortcutCommand?: KeybindingCommand; }): CommandPaletteActionItem[] { return input.projects.map((project) => ({ kind: "action", value: `${input.valuePrefix}:${project.environmentId}:${project.id}`, - searchTerms: [project.title, project.workspaceRoot], + searchTerms: [project.title, project.workspaceRoot, ...(input.searchTerms?.(project) ?? [])], title: project.title, description: project.workspaceRoot, icon: input.icon(project), diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index fa319b99552..ffe3264fbf8 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -791,6 +791,12 @@ function OpenCommandPaletteDialog(props: { buildProjectActionItems({ projects: pickerProjects, valuePrefix: "project", + searchTerms: (project) => { + const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); + return ( + group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? [] + ); + }, icon: (project) => ( { + const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); + return ( + group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? [] + ); + }, icon: (project) => ( ), runProject: async (project) => { + const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); + const contextualRefBelongsToGroup = + contextualProjectRef !== null && + group?.memberProjectRefs.some( + (projectRef) => + projectRef.environmentId === contextualProjectRef.environmentId && + projectRef.projectId === contextualProjectRef.projectId, + ); await startNewThreadInProjectFromContext( { activeDraftThread, @@ -824,12 +844,22 @@ function OpenCommandPaletteDialog(props: { defaultProjectRef, handleNewThread, }, - scopeProjectRef(project.environmentId, project.id), + contextualRefBelongsToGroup + ? contextualProjectRef + : scopeProjectRef(project.environmentId, project.id), ); }, }), ), - [activeDraftThread, activeThread, defaultProjectRef, handleNewThread, pickerProjects], + [ + activeDraftThread, + activeThread, + contextualProjectRef, + defaultProjectRef, + handleNewThread, + pickerProjects, + projectGroupByTargetKey, + ], ); const allThreadItems = useMemo( From 158b0ad15f33505bd6b85f73bc7abd8655fcf432 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Jul 2026 17:33:27 +0200 Subject: [PATCH 4/7] group draft hero project picker Co-authored-by: codex --- .../src/components/chat/DraftHeroHeadline.tsx | 86 ++++++++++++++----- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/chat/DraftHeroHeadline.tsx b/apps/web/src/components/chat/DraftHeroHeadline.tsx index 3f50d3818b5..6f5650ea3da 100644 --- a/apps/web/src/components/chat/DraftHeroHeadline.tsx +++ b/apps/web/src/components/chat/DraftHeroHeadline.tsx @@ -5,8 +5,15 @@ import { useCallback, useMemo } from "react"; import { openCommandPalette } from "~/commandPaletteBus"; import { useNewThreadHandler } from "~/hooks/useHandleNewThread"; +import { useClientSettings } from "~/hooks/useSettings"; +import { selectProjectGroupingSettings } from "~/logicalProject"; +import { + buildSidebarProjectPickerEntries, + buildSidebarProjectSnapshots, +} from "~/sidebarProjectGrouping"; import { useProjects, useThreadShells } from "~/state/entities"; -import { sortScopedProjectsForSidebar } from "../Sidebar.logic"; +import { useEnvironments, usePrimaryEnvironmentId } from "~/state/environments"; +import { sortLogicalProjectsForSidebar } from "../Sidebar.logic"; import { Menu, MenuItem, @@ -28,29 +35,64 @@ export function DraftHeroHeadline({ }: DraftHeroHeadlineProps) { const projects = useProjects(); const threads = useThreadShells(); + const { environments } = useEnvironments(); + const primaryEnvironmentId = usePrimaryEnvironmentId(); + const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); + const projectSortOrder = useClientSettings((settings) => settings.sidebarProjectSortOrder); const handleNewThread = useNewThreadHandler(); const openAddProject = useCallback(() => openCommandPalette({ open: "add-project" }), []); - const orderedProjects = useMemo( - () => sortScopedProjectsForSidebar(projects, threads, "updated_at"), - [projects, threads], - ); - const projectByKey = useMemo( + const environmentLabelById = useMemo( () => new Map( - orderedProjects.map( - (project) => - [ - scopedProjectKey(scopeProjectRef(project.environmentId, project.id)), - project, - ] as const, - ), + environments.map((environment) => [environment.environmentId, environment.label] as const), + ), + [environments], + ); + const projectGroups = useMemo( + () => + sortLogicalProjectsForSidebar( + buildSidebarProjectSnapshots({ + projects, + settings: projectGroupingSettings, + primaryEnvironmentId, + resolveEnvironmentLabel: (environmentId) => + environmentLabelById.get(environmentId) ?? null, + }), + threads, + projectSortOrder, ), - [orderedProjects], + [ + environmentLabelById, + primaryEnvironmentId, + projectGroupingSettings, + projectSortOrder, + projects, + threads, + ], + ); + const projectPickerEntries = useMemo( + () => + buildSidebarProjectPickerEntries({ + groups: projectGroups, + preferredProjectRef: activeProjectRef, + }), + [activeProjectRef, projectGroups], + ); + const projectEntryByKey = useMemo( + () => new Map(projectPickerEntries.map((entry) => [entry.group.projectKey, entry] as const)), + [projectPickerEntries], ); - const activeProjectKey = activeProjectRef === null ? "" : scopedProjectKey(activeProjectRef); + const activeProjectKey = + activeProjectRef === null + ? "" + : (projectGroups.find((group) => + group.memberProjectRefs.some( + (projectRef) => scopedProjectKey(projectRef) === scopedProjectKey(activeProjectRef), + ), + )?.projectKey ?? ""); const hasResolvedProject = activeProjectTitle !== null; - const canChooseProject = orderedProjects.length > 0; + const canChooseProject = projectPickerEntries.length > 0; const shouldShowProjectMenu = canChooseProject; const projectSelector = shouldShowProjectMenu ? ( @@ -65,20 +107,20 @@ export function DraftHeroHeadline({ { - const project = projectByKey.get(value as string); - if (!project || value === activeProjectKey) { + const entry = projectEntryByKey.get(value as string); + if (!entry || value === activeProjectKey) { return; } + const project = entry.targetProject; void handleNewThread(scopeProjectRef(project.environmentId, project.id), { replace: true, }); }} > - {orderedProjects.map((project) => { - const key = scopedProjectKey(scopeProjectRef(project.environmentId, project.id)); + {projectPickerEntries.map(({ group }) => { return ( - - {project.title} + + {group.displayName} ); })} From b776ee8bd34966c95b18e02c539af93ebd002494 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Jul 2026 17:36:50 +0200 Subject: [PATCH 5/7] align draft hero group label Co-authored-by: codex --- apps/web/src/components/chat/DraftHeroHeadline.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/chat/DraftHeroHeadline.tsx b/apps/web/src/components/chat/DraftHeroHeadline.tsx index 6f5650ea3da..98091f9aab6 100644 --- a/apps/web/src/components/chat/DraftHeroHeadline.tsx +++ b/apps/web/src/components/chat/DraftHeroHeadline.tsx @@ -83,14 +83,16 @@ export function DraftHeroHeadline({ () => new Map(projectPickerEntries.map((entry) => [entry.group.projectKey, entry] as const)), [projectPickerEntries], ); - const activeProjectKey = + const activeProjectGroup = activeProjectRef === null - ? "" + ? null : (projectGroups.find((group) => group.memberProjectRefs.some( (projectRef) => scopedProjectKey(projectRef) === scopedProjectKey(activeProjectRef), ), - )?.projectKey ?? ""); + ) ?? null); + const activeProjectKey = activeProjectGroup?.projectKey ?? ""; + const activeProjectDisplayName = activeProjectGroup?.displayName ?? activeProjectTitle; const hasResolvedProject = activeProjectTitle !== null; const canChooseProject = projectPickerEntries.length > 0; const shouldShowProjectMenu = canChooseProject; @@ -101,7 +103,7 @@ export function DraftHeroHeadline({ aria-label={hasResolvedProject ? "Change project" : "Choose a project"} className="pointer-events-auto inline cursor-pointer border-current border-b border-dotted text-foreground underline-offset-8 transition-opacity hover:opacity-75 focus-visible:rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" > - {activeProjectTitle ?? "Choose a project"} + {activeProjectDisplayName ?? "Choose a project"} Date: Thu, 23 Jul 2026 17:41:13 +0200 Subject: [PATCH 6/7] show logical project names on sidebar threads Co-authored-by: codex --- apps/web/src/components/SidebarV2.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index 2078b0ffc41..a4b5ff57f71 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -823,10 +823,16 @@ export default function SidebarV2() { ), [projects], ); - const projectTitleByKey = useMemo( + const projectDisplayNameByKey = useMemo( () => - new Map(projects.map((project) => [`${project.environmentId}:${project.id}`, project.title])), - [projects], + new Map( + projectGroups.flatMap((group) => + group.memberProjects.map( + (project) => [`${project.environmentId}:${project.id}`, group.displayName] as const, + ), + ), + ), + [projectGroups], ); // now is quantized to the minute so effectiveSettled memoization doesn't @@ -1768,7 +1774,8 @@ export default function SidebarV2() { projectCwdByKey.get(`${thread.environmentId}:${thread.projectId}`) ?? null } projectTitle={ - projectTitleByKey.get(`${thread.environmentId}:${thread.projectId}`) ?? null + projectDisplayNameByKey.get(`${thread.environmentId}:${thread.projectId}`) ?? + null } providerEntryByInstanceId={providerEntryByInstanceId} onThreadClick={handleThreadClick} From af1853b058cc7780f321eb4a57e028533622d00f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Jul 2026 17:45:25 +0200 Subject: [PATCH 7/7] keep sidebar settle actions transparent Co-authored-by: codex --- apps/web/src/components/SidebarV2.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/SidebarV2.tsx b/apps/web/src/components/SidebarV2.tsx index a4b5ff57f71..f962f6cf151 100644 --- a/apps/web/src/components/SidebarV2.tsx +++ b/apps/web/src/components/SidebarV2.tsx @@ -573,7 +573,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { type="button" aria-label="Un-settle thread" onClick={handleUnsettleClick} - className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-row-hover px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100 dark:border-transparent dark:inset-ring-1 dark:inset-ring-white/5" + className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md bg-transparent px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100" > @@ -582,7 +582,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { type="button" aria-label="Settle thread" onClick={handleSettleClick} - className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-row-hover px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100 dark:border-transparent dark:inset-ring-1 dark:inset-ring-white/5" + className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md bg-transparent px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100" > @@ -659,7 +659,7 @@ const SidebarV2Row = memo(function SidebarV2Row(props: { type="button" aria-label="Settle thread" onClick={handleSettleClick} - className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-row-hover px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100 dark:border-transparent dark:inset-ring-1 dark:inset-ring-white/5" + className="absolute inset-y-0 right-0 inline-flex cursor-pointer items-center gap-1 rounded-md bg-transparent px-2 text-xs text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/v2-row:opacity-100" > Settle