From 04639adace67c7765eb14a32c78ae4fb62da7fe6 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 29 Jun 2026 23:29:20 -0500 Subject: [PATCH] feat(desktop): flag already-installed themes in the install pickers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Cmd-K "Install theme…" palette listed Marketplace themes with no hint that you already had them, and clicking one re-downloaded + re-installed a theme you owned. The Appearance settings grid already detected this, but by parsing theme descriptions inline on every render — plumbing that never made it to the palette. Lift it into one reactive source and reuse it everywhere: - $marketplaceInstalls (computed over $userThemes): extensionId -> installed theme, derived once via marketplaceIdOf and memoized, instead of rebuilding a Set per render. - Both install surfaces now mark owned rows installed and, on click, re-activate the installed theme rather than re-fetching it. - Drops the duplicated description-parsing in settings and the per-session "installed here" state in both surfaces (the store is the source of truth, so previously-installed themes show correctly too). --- .../marketplace-theme-page.tsx | 23 ++++++-- .../src/app/settings/appearance-settings.tsx | 48 ++++++++-------- apps/desktop/src/themes/user-themes.test.ts | 56 ++++++++++++++++--- apps/desktop/src/themes/user-themes.ts | 33 ++++++++++- 4 files changed, 122 insertions(+), 38 deletions(-) diff --git a/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx b/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx index eb175fdcb72..6766b2dae30 100644 --- a/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +++ b/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx @@ -8,6 +8,7 @@ * user can grab several. */ +import { useStore } from '@nanostores/react' import { useQuery } from '@tanstack/react-query' import { useEffect, useState } from 'react' @@ -18,6 +19,7 @@ import { triggerHaptic } from '@/lib/haptics' import { Check, Download, Loader2, Palette } from '@/lib/icons' import { cn } from '@/lib/utils' import { installVscodeThemeFromMarketplace } from '@/themes/install' +import { $marketplaceInstalls } from '@/themes/user-themes' const compactNumber = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 }) @@ -43,8 +45,8 @@ export function MarketplaceThemePage({ search, onPickTheme }: MarketplaceThemePa const { t } = useI18n() const copy = t.commandCenter.installTheme const debouncedSearch = useDebounced(search.trim(), 300) + const installs = useStore($marketplaceInstalls) const [installingId, setInstallingId] = useState(null) - const [installed, setInstalled] = useState>({}) const [installError, setInstallError] = useState(null) const query = useQuery({ @@ -53,6 +55,20 @@ export function MarketplaceThemePage({ search, onPickTheme }: MarketplaceThemePa staleTime: 5 * 60 * 1000 }) + // Already installed → just re-activate it; never re-download what we have. + const select = (item: DesktopMarketplaceSearchItem) => { + const owned = installs.get(item.extensionId) + + if (owned) { + triggerHaptic('crisp') + onPickTheme(owned.name) + + return + } + + void install(item) + } + const install = async (item: DesktopMarketplaceSearchItem) => { if (installingId) { return @@ -65,7 +81,6 @@ export function MarketplaceThemePage({ search, onPickTheme }: MarketplaceThemePa const theme = await installVscodeThemeFromMarketplace(item.extensionId) triggerHaptic('crisp') - setInstalled(prev => ({ ...prev, [item.extensionId]: true })) onPickTheme(theme.name) } catch (error) { setInstallError(error instanceof Error ? error.message : copy.error) @@ -93,7 +108,7 @@ export function MarketplaceThemePage({ search, onPickTheme }: MarketplaceThemePa {installError &&

{installError}

} {results.map(item => { const busy = installingId === item.extensionId - const done = installed[item.extensionId] + const done = installs.has(item.extensionId) return (