Stage 3: App — program/deployment model & controls

- ProgramActions: install/uninstall (activate) shown only for tools and
  no-service frontends. Daemons activate via a service/job, so the program
  page no longer offers a misleading Install/Uninstall (which secretly
  enabled/disabled the service).
- Program page: new Deployments section listing the services/jobs that run
  the program, as links (a program → 0-N services/jobs).
- Service page: 'Runs' row from the run_target summary field (the old code
  dug into manifest.run, which was empty pre-Stage-1), a Host row for
  proxy_host, and a convenience link to the referenced program.
- lifecycle.activate on a daemon with no service now errors toward
  'castle expose' instead of silently installing its binary to PATH.

core 94 green; ruff + app build clean.
This commit is contained in:
2026-06-14 15:37:20 -07:00
parent 73698fafe7
commit dea585b15b
6 changed files with 132 additions and 13 deletions

View File

@@ -4,6 +4,7 @@ import { useProgram, useEventStream } from "@/services/api/hooks"
import { runnerLabel } from "@/lib/labels"
import { DetailHeader } from "@/components/detail/DetailHeader"
import { ConfigPanel } from "@/components/detail/ConfigPanel"
import { DeploymentsSection } from "@/components/detail/DeploymentsSection"
import { ProgramActions, ActionOutputPanel, type ActionOutput } from "@/components/ProgramActions"
export function ProgramDetailPage() {
@@ -37,7 +38,14 @@ export function ProgramDetailPage() {
stack={deployment.stack}
source={deployment.source}
>
<ProgramActions name={deployment.id} actions={deployment.actions} active={deployment.active} onOutput={setActionOutput} />
<ProgramActions
name={deployment.id}
actions={deployment.actions}
active={deployment.active}
behavior={deployment.behavior}
deployedAs={[...deployment.services, ...deployment.jobs]}
onOutput={setActionOutput}
/>
</DetailHeader>
{actionOutput && actionOutput.action && (
@@ -128,6 +136,8 @@ export function ProgramDetailPage() {
)}
</div>
<DeploymentsSection program={deployment} />
<ConfigPanel deployment={deployment} configSection="programs" onRefetch={refetch} />
</div>
)