From 426895d9e8478cf4e27b50bcbad9bb4800c8402f Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Sat, 22 Aug 2026 19:32:06 -0700 Subject: [PATCH] fix: deduplicate project folders in sidebar across profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The all-profiles project tree merge used raw path strings as keys. On Windows (case-insensitive fs), the same folder with different casings across profiles (e.g. C:\Users\me\Project vs C:\Users\me\project) produced duplicate top-level entries in the sidebar Projects section. Backend: normalize the merge key with os.path.normcase() (no-op on POSIX, lowercases on Windows). Frontend: deduplicate projectModel by id before rendering — a safety net that catches any merge edge cases the backend fix doesn't cover. Closes #92588 --- apps/desktop/src/app/chat/sidebar/index.tsx | 10 +++++++++- hermes_cli/web_routers/profiles.py | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index d3dd287ba5..fcc84966c8 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -1008,7 +1008,15 @@ export function ChatSidebar({ // Layer the user's manual drag-order on top of the deterministic sort. Empty // (default) returns `sorted` untouched; projects the user hasn't ordered yet // keep their sorted position rather than jumping the hand-picked list. - return orderProjectsByIds(sorted, projectOrderIds) + // Deduplicate by id — safety net for backend merge edge cases + // (e.g. Windows path-casing differences across profiles). + const seen = new Set() + const deduped = sorted.filter(project => { + if (seen.has(project.id)) return false + seen.add(project.id) + return true + }) + return orderProjectsByIds(deduped, projectOrderIds) }, [ projectTree, dismissedAutoProjects, diff --git a/hermes_cli/web_routers/profiles.py b/hermes_cli/web_routers/profiles.py index aa1c3487a4..9672eb29ec 100644 --- a/hermes_cli/web_routers/profiles.py +++ b/hermes_cli/web_routers/profiles.py @@ -16,6 +16,7 @@ from hermes_cli.web_read_coalescing import coalesced_read import inspect import json import logging +import os import re import subprocess import sys @@ -687,7 +688,7 @@ def _merge_profile_tree( session["profile"] = profile session["is_default_profile"] = profile == "default" - key = project.get("path") or project["id"] + key = os.path.normcase(project.get("path") or project["id"]) existing = merged.get(key) if existing is None: merged[key] = project