From 203f5212e68f0ef89d7da5ad81070edcd968c322 Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Sun, 14 Jun 2026 12:26:29 -0700 Subject: [PATCH] fix(app): drive program activate toggle off active, not installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Programs table's install/uninstall icon keyed off `installed` (shutil.which(name)) — a PATH lookup that only makes sense for python CLI tools. Static frontends have no PATH binary so they were always `installed=false` → a green 'Install' icon even while actively served; container daemons were `installed=null` → same. And for tools the value reflected the castle-api service's PATH, which isn't guaranteed to include ~/.local/bin across reboots, so they could all flip to 'not installed'. Switch ProgramActions/ProgramTable/ProgramDetail to the uniform `active` state (Phase 2's lifecycle.is_active: PATH-independent ~/.local/bin check for tools, systemctl for services/jobs, served-assets check for static frontends). Now every program reflects real state: served frontends show Uninstall, container daemons (no install verb) show no icon. App builds clean. --- app/src/components/ProgramActions.tsx | 24 +++++++++++++----------- app/src/components/ProgramTable.tsx | 2 +- app/src/pages/ProgramDetail.tsx | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/components/ProgramActions.tsx b/app/src/components/ProgramActions.tsx index 4d68618..6464543 100644 --- a/app/src/components/ProgramActions.tsx +++ b/app/src/components/ProgramActions.tsx @@ -41,32 +41,34 @@ export interface ActionOutput { interface ProgramActionsProps { name: string actions: string[] - installed?: boolean | null + active?: boolean | null compact?: boolean onOutput?: (output: ActionOutput) => void } function visibleActions( actions: string[], - installed: boolean | null | undefined, + active: boolean | null | undefined, compact: boolean, ): string[] { + // `active` is the uniform lifecycle state (on PATH / running / served), not a + // PATH lookup — so it's correct for tools, services, jobs, and static frontends. if (compact) { - // Table: only install/uninstall based on state - if (installed === true) return actions.includes("uninstall") ? ["uninstall"] : [] - if (installed === false) return actions.includes("install") ? ["install"] : [] - // null — show install if available (frontends, etc.) + // Table: only the activate/deactivate toggle based on state + if (active === true) return actions.includes("uninstall") ? ["uninstall"] : [] + if (active === false) return actions.includes("install") ? ["install"] : [] + // null — show install if available return actions.includes("install") ? ["install"] : [] } - // Detail page: dev actions always, install/uninstall based on state + // Detail page: dev actions always, activate/deactivate based on state const visible: string[] = [] for (const a of DEV_ACTIONS) { if (actions.includes(a)) visible.push(a) } - if (installed === true) { + if (active === true) { if (actions.includes("uninstall")) visible.push("uninstall") - } else if (installed === false) { + } else if (active === false) { if (actions.includes("install")) visible.push("install") } else { // null — show both if available @@ -76,11 +78,11 @@ function visibleActions( return visible } -export function ProgramActions({ name, actions, installed, compact, onOutput }: ProgramActionsProps) { +export function ProgramActions({ name, actions, active, compact, onOutput }: ProgramActionsProps) { const { mutate, isPending } = useProgramAction() const [runningAction, setRunningAction] = useState(null) - const visible = visibleActions(actions, installed, !!compact) + const visible = visibleActions(actions, active, !!compact) const handleAction = (action: string) => { setRunningAction(action) diff --git a/app/src/components/ProgramTable.tsx b/app/src/components/ProgramTable.tsx index aea4838..d0ae9b1 100644 --- a/app/src/components/ProgramTable.tsx +++ b/app/src/components/ProgramTable.tsx @@ -143,7 +143,7 @@ function ProgramRow({ program }: { program: ProgramSummary }) { - + ) diff --git a/app/src/pages/ProgramDetail.tsx b/app/src/pages/ProgramDetail.tsx index 83eca73..21b1308 100644 --- a/app/src/pages/ProgramDetail.tsx +++ b/app/src/pages/ProgramDetail.tsx @@ -37,7 +37,7 @@ export function ProgramDetailPage() { stack={deployment.stack} source={deployment.source} > - + {actionOutput && actionOutput.action && (