From 3f7e915c4d0e5f8777e42c230264b2f62bedc72b Mon Sep 17 00:00:00 2001 From: Gagan Date: Tue, 7 Jul 2026 15:22:27 +0530 Subject: [PATCH] feat(apps): show installed state on catalog cards Cards for apps already installed from the catalog show a green Installed badge (matched via the install record's registry name) that opens the app, instead of a second Install button. --- .../renderer/src/components/apps/catalog.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/x/apps/renderer/src/components/apps/catalog.tsx b/apps/x/apps/renderer/src/components/apps/catalog.tsx index a84169ff..99bdda2c 100644 --- a/apps/x/apps/renderer/src/components/apps/catalog.tsx +++ b/apps/x/apps/renderer/src/components/apps/catalog.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { Bot, Download, Link2, RefreshCw, Search, ShieldAlert } from 'lucide-react' +import { BadgeCheck, Bot, Download, Link2, RefreshCw, Search, ShieldAlert } from 'lucide-react' import type { rowboatApp } from '@x/shared' // Catalog tab (spec §14): search the registry, install with the D18 capability @@ -160,6 +160,18 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v const [url, setUrl] = useState('') const [agentPrompt, setAgentPrompt] = useState<{ folder: string; appName: string; slugs: string[]; names: string[]; defaultModel?: ModelChoice } | null>(null) const [enabling, setEnabling] = useState(false) + // Registry name → local folder, for apps already installed from the catalog. + const [installedByName, setInstalledByName] = useState>(new Map()) + + const loadInstalled = async () => { + try { + const r = await window.ipc.invoke('apps:list', {}) + setInstalledByName(new Map( + r.apps.filter((a) => a.kind === 'installed' && a.install).map((a) => [a.install!.name, a.folder]), + )) + } catch { /* cards just show Install */ } + } + useEffect(() => { void loadInstalled() }, []) const load = async (force = false) => { setError(null) @@ -233,6 +245,7 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v ? await window.ipc.invoke('apps:installFromUrl', { url: preview.url, confirmed: true }) : await window.ipc.invoke('apps:install', { name: preview.name ?? '', confirmed: true }) setPreview(null) + void loadInstalled() if (r.status === 'installed' && r.app) { if (r.app.agentSlugs.length > 0) { // Offer to switch the bundled agents on (they install disabled). @@ -300,10 +313,18 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v

{r.description || 'No description.'}

- + {installedByName.has(r.name) ? ( + + ) : ( + + )} ))}