From 00d270341adc24b4ad58607a7b2c537b9ccf2ba8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Apr 2026 15:21:25 +0000 Subject: [PATCH] fix(editor): key selected packages by type+name to avoid sync collision Packages with the same name but different types (e.g. "ghostty" cask vs "ghostty" npm) collided in the selection map, so toggling one would visually toggle both. Switch the map key to "type:name" so each variant is tracked independently. --- src/lib/components/ConfigEditor.svelte | 63 ++++++++++++++---------- src/lib/components/PackageManager.svelte | 26 ++++++---- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/lib/components/ConfigEditor.svelte b/src/lib/components/ConfigEditor.svelte index b8afff7..7902efa 100644 --- a/src/lib/components/ConfigEditor.svelte +++ b/src/lib/components/ConfigEditor.svelte @@ -52,6 +52,10 @@ let selectedPackages = $state(new Map()); let packageDescs = $state(new Map()); + function pkgKey(name: string, type: string): string { + return `${type}:${name}`; + } + interface MacOSPref { domain: string; key: string; @@ -63,11 +67,10 @@ let expandedPrefCats = $state>(new Set()); const packages = $derived( - Array.from(selectedPackages.entries()).map(([name, type]) => ({ - name, - type, - desc: packageDescs.get(name) || '', - })) + Array.from(selectedPackages.entries()).map(([key, type]) => { + const name = key.slice(type.length + 1); + return { name, type, desc: packageDescs.get(key) || '' }; + }) ); const sections = [ @@ -139,9 +142,9 @@ const p = PRESET_PACKAGES[preset]; if (!p) return; const newMap = new Map(); - for (const pkg of p.cli) newMap.set(pkg, 'formula'); - for (const pkg of p.cask) newMap.set(pkg, 'cask'); - if (p.npm) for (const pkg of p.npm) newMap.set(pkg, 'npm'); + for (const pkg of p.cli) newMap.set(pkgKey(pkg, 'formula'), 'formula'); + for (const pkg of p.cask) newMap.set(pkgKey(pkg, 'cask'), 'cask'); + if (p.npm) for (const pkg of p.npm) newMap.set(pkgKey(pkg, 'npm'), 'npm'); selectedPackages = newMap; } @@ -151,14 +154,15 @@ } function togglePackage(name: string, type: string, desc: string = '') { + const key = pkgKey(name, type); const newMap = new Map(selectedPackages); const newDescs = new Map(packageDescs); - if (newMap.has(name)) { - newMap.delete(name); - newDescs.delete(name); + if (newMap.has(key)) { + newMap.delete(key); + newDescs.delete(key); } else { - newMap.set(name, type); - if (desc) newDescs.set(name, desc); + newMap.set(key, type); + if (desc) newDescs.set(key, desc); } selectedPackages = newMap; packageDescs = newDescs; @@ -200,10 +204,12 @@ const descs = new Map(); for (const pkg of data.packages) { if (typeof pkg === 'string') { - map.set(pkg, 'formula'); + map.set(pkgKey(pkg, 'formula'), 'formula'); } else { - map.set(pkg.name, pkg.type || 'formula'); - if (pkg.desc) descs.set(pkg.name, pkg.desc); + const t = pkg.type || 'formula'; + const k = pkgKey(pkg.name, t); + map.set(k, t); + if (pkg.desc) descs.set(k, pkg.desc); } } selectedPackages = map; @@ -265,10 +271,12 @@ const newDescs = new Map(); for (const pkg of savedPkgs) { if (typeof pkg === 'string') { - newMap.set(pkg, 'formula'); + newMap.set(pkgKey(pkg, 'formula'), 'formula'); } else { - newMap.set(pkg.name, pkg.type || 'formula'); - if (pkg.desc) newDescs.set(pkg.name, pkg.desc); + const t = pkg.type || 'formula'; + const k = pkgKey(pkg.name, t); + newMap.set(k, t); + if (pkg.desc) newDescs.set(k, pkg.desc); } } selectedPackages = newMap; @@ -293,11 +301,10 @@ return { ...formData, alias: formData.alias.trim() || null, - packages: Array.from(selectedPackages.entries()).map(([name, type]) => ({ - name, - type, - desc: packageDescs.get(name) || '', - })), + packages: Array.from(selectedPackages.entries()).map(([key, type]) => { + const name = key.slice(type.length + 1); + return { name, type, desc: packageDescs.get(key) || '' }; + }), snapshot: updatedSnapshot, }; } @@ -316,10 +323,12 @@ const newDescs = new Map(); for (const pkg of parsed.packages || []) { if (typeof pkg === 'string') { - newMap.set(pkg, 'formula'); + newMap.set(pkgKey(pkg, 'formula'), 'formula'); } else { - newMap.set(pkg.name, pkg.type || 'formula'); - if (pkg.desc) newDescs.set(pkg.name, pkg.desc); + const t = pkg.type || 'formula'; + const k = pkgKey(pkg.name, t); + newMap.set(k, t); + if (pkg.desc) newDescs.set(k, pkg.desc); } } selectedPackages = newMap; diff --git a/src/lib/components/PackageManager.svelte b/src/lib/components/PackageManager.svelte index 83a8fc1..84ac252 100644 --- a/src/lib/components/PackageManager.svelte +++ b/src/lib/components/PackageManager.svelte @@ -21,6 +21,10 @@ onPresetChange: (preset: string) => void; } = $props(); + function pkgKey(name: string, type: string): string { + return `${type}:${name}`; + } + let searchQuery = $state(''); let searchResults = $state([]); let searchLoading = $state(false); @@ -89,11 +93,12 @@ const cli: string[] = []; const npm: string[] = []; const taps: string[] = []; - for (const [pkg, type] of selectedPackages) { - if (type === 'cask') apps.push(pkg); - else if (type === 'npm') npm.push(pkg); - else if (type === 'tap') taps.push(pkg); - else cli.push(pkg); + for (const [key, type] of selectedPackages) { + const name = key.slice(type.length + 1); + if (type === 'cask') apps.push(name); + else if (type === 'npm') npm.push(name); + else if (type === 'tap') taps.push(name); + else cli.push(name); } return { apps, cli, npm, taps }; }); @@ -128,8 +133,9 @@ } const caskSet = new Set(data.casks || []); for (const pkg of data.packages) { - if (!selectedPackages.has(pkg)) { - togglePackage(pkg, caskSet.has(pkg) ? 'cask' : 'formula'); + const type = caskSet.has(pkg) ? 'cask' : 'formula'; + if (!selectedPackages.has(pkgKey(pkg, type))) { + togglePackage(pkg, type); } } showImport = false; @@ -185,14 +191,14 @@