From 099217062a4cb3a3caacce7513354bb388e8d76c Mon Sep 17 00:00:00 2001 From: rootphantomer Date: Thu, 9 Jul 2026 16:46:39 +0800 Subject: [PATCH] fix: group duplicates by color and sort A-Z within groups --- webapp/src/components/VaultPage.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/webapp/src/components/VaultPage.tsx b/webapp/src/components/VaultPage.tsx index cba0ce3..e950311 100644 --- a/webapp/src/components/VaultPage.tsx +++ b/webapp/src/components/VaultPage.tsx @@ -419,7 +419,36 @@ export default function VaultPage(props: VaultPageProps) { return !!meta?.searchText.includes(searchQuery); }); + // Pre-compute group min name for duplicates group ordering + const groupMinName = new Map(); + if (sidebarFilter.kind === 'duplicates' && duplicateSignatureInfo) { + for (const cipher of next) { + const gk = (duplicateSignatureInfo.byId.get(cipher.id) || []) + .filter(s => (duplicateSignatureInfo.counts.get(s) || 0) >= 2) + .sort()[0] || ''; + if (!gk) continue; + const name = cipherMetaById.get(cipher.id)?.name || ''; + const cur = groupMinName.get(gk); + if (!cur || nameCollator.compare(name, cur) < 0) groupMinName.set(gk, name); + } + } + next.sort((a, b) => { + // Duplicates view: group by color, sort A-Z within each group + if (sidebarFilter.kind === 'duplicates' && duplicateSignatureInfo) { + const gk = (id: string) => (duplicateSignatureInfo.byId.get(id) || []) + .filter(s => (duplicateSignatureInfo.counts.get(s) || 0) >= 2) + .sort()[0] || ''; + const gA = gk(a.id), gB = gk(b.id); + if (gA !== gB) return !gA ? 1 : !gB ? -1 : nameCollator.compare( + groupMinName.get(gA) || '', groupMinName.get(gB) || '' + ) || (gA < gB ? -1 : 1); + return nameCollator.compare( + cipherMetaById.get(a.id)?.name || '', + cipherMetaById.get(b.id)?.name || '' + ) || String(a.id || '').localeCompare(String(b.id || '')); + } + const metaA = cipherMetaById.get(a.id); const metaB = cipherMetaById.get(b.id); if (sortMode === 'edited') {