From f24de3da132e1c15f8a32e3eacccd304f5f3bbe6 Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 12 Dec 2025 19:20:45 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20fix:=20bump=20newly=20added?= =?UTF-8?q?=20projects=20to=20top?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/browser/components/ProjectSidebar.tsx | 48 ++++++++++++++++------- src/common/utils/projectOrdering.test.ts | 8 ++-- src/common/utils/projectOrdering.ts | 8 +++- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/browser/components/ProjectSidebar.tsx b/src/browser/components/ProjectSidebar.tsx index 2fcbd84acb..8e523af520 100644 --- a/src/browser/components/ProjectSidebar.tsx +++ b/src/browser/components/ProjectSidebar.tsx @@ -36,6 +36,23 @@ export type { WorkspaceSelection } from "./WorkspaceListItem"; // Draggable project item moved to module scope to avoid remounting on every parent render. // Defining components inside another component causes a new function identity each render, // which forces React to unmount/remount the subtree. That led to hover flicker and high CPU. + +const PROJECT_ITEM_BASE_CLASS = + "py-2 px-3 flex items-center border-l-[3px] border-l-transparent bg-sidebar transition-colors duration-150"; + +function getProjectItemClassName(opts: { + isDragging: boolean; + isOver: boolean; + selected: boolean; +}): string { + return cn( + PROJECT_ITEM_BASE_CLASS, + opts.isDragging ? "cursor-grabbing opacity-35 [&_*]:!cursor-grabbing" : "cursor-grab", + opts.isOver && "bg-accent/[0.08]", + opts.selected && "bg-hover border-l-accent", + "hover:[&_button]:opacity-100 hover:[&_[data-drag-handle]]:opacity-100" + ); +} type DraggableProjectItemProps = React.PropsWithChildren<{ projectPath: string; onReorder: (draggedPath: string, targetPath: string) => void; @@ -87,13 +104,11 @@ const DraggableProjectItemBase: React.FC = ({ return (
drag(drop(node))} - className={cn( - "py-2 px-3 flex items-center border-l-transparent transition-all duration-150 bg-sidebar", - isDragging ? "cursor-grabbing opacity-40 [&_*]:!cursor-grabbing" : "cursor-grab", - isOver && "bg-accent/[0.08]", - selected && "bg-hover border-l-accent", - "hover:[&_button]:opacity-100 hover:[&_[data-drag-handle]]:opacity-100" - )} + className={getProjectItemClassName({ + isDragging, + isOver, + selected: !!selected, + })} {...rest} > {children} @@ -141,17 +156,22 @@ const ProjectDragLayer: React.FC = () => { if (!isDragging || !currentOffset || !item?.projectPath) return null; const abbrevPath = PlatformPaths.abbreviate(item.projectPath); - const { dirPath, basename } = PlatformPaths.splitAbbreviated(abbrevPath); + const { basename } = PlatformPaths.splitAbbreviated(abbrevPath); return (
-
- -
-
- {dirPath} - {basename} +
+ +
+
+ {basename}
diff --git a/src/common/utils/projectOrdering.test.ts b/src/common/utils/projectOrdering.test.ts index e6a2a0c568..2e6dddd406 100644 --- a/src/common/utils/projectOrdering.test.ts +++ b/src/common/utils/projectOrdering.test.ts @@ -69,11 +69,11 @@ describe("projectOrdering", () => { expect(result).toEqual(["/a", "/b"]); }); - it("appends new projects to the end", () => { + it("prepends new projects to the front", () => { const projects = createProjects(["/a", "/b", "/c", "/d"]); const order = ["/b", "/a"]; const result = normalizeOrder(order, projects); - expect(result).toEqual(["/b", "/a", "/c", "/d"]); + expect(result).toEqual(["/c", "/d", "/b", "/a"]); }); it("preserves order of existing projects", () => { @@ -130,13 +130,13 @@ describe("projectOrdering", () => { // After projects load, normalization should work normally: // 1. projectOrder is still ["/a", "/b", "/c"] from localStorage // 2. Projects are now loaded with an additional project ["/d"] - // 3. Normalization should append the new project + // 3. Normalization should treat the new project as "most recent" and put it first const projectOrder = ["/a", "/b", "/c"]; const loadedProjects = createProjects(["/a", "/b", "/c", "/d"]); const result = normalizeOrder(projectOrder, loadedProjects); - expect(result).toEqual(["/a", "/b", "/c", "/d"]); + expect(result).toEqual(["/d", "/a", "/b", "/c"]); }); }); }); diff --git a/src/common/utils/projectOrdering.ts b/src/common/utils/projectOrdering.ts index 487f97a101..4b4bad763d 100644 --- a/src/common/utils/projectOrdering.ts +++ b/src/common/utils/projectOrdering.ts @@ -56,13 +56,17 @@ export function reorderProjects( /** * Normalize an order array against the current set of projects. * - Removes paths that no longer exist - * - Appends new paths to the end (preserving their natural order) + * - Prepends new paths to the front (preserving their natural order) + * + * UX rationale: when a user adds a project, they almost always want to use it next. Putting newly + * added projects at the top makes that action feel "recent" even if the user already has a custom + * project ordering. */ export function normalizeOrder(order: string[], projects: Map): string[] { const present = new Set(projects.keys()); const filtered = order.filter((p) => present.has(p)); const missing = Array.from(projects.keys()).filter((p) => !filtered.includes(p)); - return [...filtered, ...missing]; + return [...missing, ...filtered]; } /** From 2a574a087157c633e68be42d7b4ea669aabe1003 Mon Sep 17 00:00:00 2001 From: Ammar Date: Sat, 13 Dec 2025 20:00:52 -0600 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A4=96=20fix:=20simplify=20drag=20pre?= =?UTF-8?q?view=20to=20match=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/browser/components/ProjectSidebar.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/browser/components/ProjectSidebar.tsx b/src/browser/components/ProjectSidebar.tsx index 8e523af520..4a1172b3a8 100644 --- a/src/browser/components/ProjectSidebar.tsx +++ b/src/browser/components/ProjectSidebar.tsx @@ -164,15 +164,14 @@ const ProjectDragLayer: React.FC = () => {
- + + +
-
- {basename} -
+ {basename}
From 5e7b1142e0ec1d8152c7f4bd05d6f94c9540e9ed Mon Sep 17 00:00:00 2001 From: Ammar Date: Sat, 13 Dec 2025 20:08:20 -0600 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A4=96=20fix:=20remove=20unintended?= =?UTF-8?q?=20border=20change=20to=20idle=20rows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/browser/components/ProjectSidebar.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/browser/components/ProjectSidebar.tsx b/src/browser/components/ProjectSidebar.tsx index 4a1172b3a8..bcf30f576c 100644 --- a/src/browser/components/ProjectSidebar.tsx +++ b/src/browser/components/ProjectSidebar.tsx @@ -38,7 +38,7 @@ export type { WorkspaceSelection } from "./WorkspaceListItem"; // which forces React to unmount/remount the subtree. That led to hover flicker and high CPU. const PROJECT_ITEM_BASE_CLASS = - "py-2 px-3 flex items-center border-l-[3px] border-l-transparent bg-sidebar transition-colors duration-150"; + "py-2 px-3 flex items-center border-l-transparent bg-sidebar transition-colors duration-150"; function getProjectItemClassName(opts: { isDragging: boolean; @@ -161,12 +161,7 @@ const ProjectDragLayer: React.FC = () => { return (
-
+