Commit Graph

51 Commits

Author SHA1 Message Date
Paul Payne
38c33224e7 refactor: remove dead provides/consumes capability model
The program-level Capability model (provides/consumes) was a vestige of an
earlier design superseded by the deployment-level requires/Requirement graph.
It was read only into a diagnostic node and surfaced as an always-empty
'Capabilities' panel; nothing acted on it. Removed from manifest, relations,
the CLI re-export, and the System Map.
2026-07-07 04:54:50 -07:00
Paul Payne
b28645f2f4 mesh: carry gateway_domain so peers can launch remote apps
The mesh payload didn't include a node's acme domain, so a peer had no way
to build launch URLs for another machine's exposed apps. Publish
gateway_domain in the MQTT registry payload and surface it per-deployment on
/mesh/deployments as `domain`. The dashboard palette and System Map now build
`https://<subdomain>.<domain>` for remote http-exposed apps (references use
their base_url), making primer's apps launchable from civil.
2026-07-06 21:07:10 -07:00
Paul Payne
25bc8a8ab1 feat(app): collapsible map legend (persisted) 2026-07-06 20:39:47 -07:00
Paul Payne
004df3ba4f fix: remote endpoint reach-gating + drag-to-connect resync race
- Mesh endpoints now gate the http port on `subdomain` (the registry's "exposed"
  signal), matching the local reach-gated derivation — so a remote reach:off
  service (e.g. primer's castle-gateway) no longer shows a phantom :9000.
- The focus/resync effect no longer rebuilds the node set while a connection is
  being dragged; a background refetch mid-drag was silently cancelling the
  connection, so drag-to-expose/-require intermittently "did nothing".
2026-07-06 20:36:52 -07:00
Paul Payne
c1ec216ea4 feat(app): ⌘K command palette — app-wide launcher
The keyboard twin of the map's inspect panel: ⌘K (or the "Launch…" nav button)
from any page. Empty query = the "Start Menu" (browsable launchable apps, local +
remote, grouped by machine badge); typing searches every deployment. Enter
launches the app in a new tab; per-result actions jump to it on the map
(/map?focus=<id>, which the map now honors by selecting + centering) or open its
Castle details. Navigation/launch only — no mutation — for v1.
2026-07-06 20:07:50 -07:00
Paul Payne
778772d863 feat(app): launch affordance + unify menu into the inspect panel
- "Start Menu": frontends and http-exposed services get a launch ↗ that opens
  the running app (https://<name>.<domain>) in a new tab — on the node (hover)
  and in the panel header.
- The right-click / kebab NodeMenu is gone; its actions (Restart, expose, Delete)
  now live in the inspect panel as an Actions row, so one gesture (click a node)
  is the whole control surface. Panel header separates Launch (the app) from
  Castle details (the management page). Remote nodes stay view-only.
2026-07-06 19:55:50 -07:00
Paul Payne
7ffc1995ae feat(app): local node on top, machine divider, remote-node parity
- Local node's deployments render on top (with a "⬡ <host> · this node" label and
  the lane headers), then a labeled "other machines" divider, then each remote
  machine as a band below — same kind-columns, so cross-node edges align vertically.
- Remote nodes get full UX parity: draggable (persisted), selectable, and a unified
  focus adjacency (consumes/consumed-by over local + remote + reference ids) drives
  identical highlight + inspect-panel behavior whether a node is local or remote.
  The panel shows a machine badge on cross-node deps and disables edit on remotes.
2026-07-06 19:47:07 -07:00
Paul Payne
79efb006f5 feat(app): mesh bands — kind-columns, draggable, shared headers
Remote machines' deployments now sit in the same kind-columns as local (so a
cross-node edge runs straight down its column), are draggable with persisted
positions, and the lane headers lift above the topmost band so they head every
machine's columns.
2026-07-06 17:30:22 -07:00
Paul Payne
ebd23e42c1 feat: cross-node consumption edges over the mesh
Carry each deployment's `requires` (deployment refs, no secrets) from config →
registry → the mesh MQTT payload, and expose it on /mesh/deployments. The System
Map resolves a remote deployment's ref against the provider set across nodes
(same machine → local provider → other machine) and draws a dashed cross-node
edge. This is the multi-node payoff: e.g. primer's castle-api → civil's mqtt.
2026-07-06 17:16:09 -07:00
Paul Payne
4f5f569da4 feat(app): map polish — loading spinner, cleaner cards, reveal-on-select
- Loading spinner while the graph query is in flight.
- Deployment cards drop the "consumed by N" line and external-ref chips — both
  now live in the inspect panel, so cards are just name/port/reach.
- A node's external (reference) edges are drawn only while it's selected, then
  removed — no permanent web of long cross-map lines.
- LAN exposure edges animate like the Internet ones (animation = live traffic).
2026-07-06 16:44:10 -07:00
Paul Payne
46778b7f2e feat(app): system map — consumption model, inspect mode, externals, mesh
Reframes the map around consumes / consumed_by and adds:
- Inspect on select: dim the rest, light a node's consumes/consumed-by, and a
  side panel listing both (removable/navigable) with protocol chips.
- Protocol-typed dependency edges (http/pg/bolt/mqtt) + reach badges; TCP infra
  now shows its port.
- External resources: a registry lane of reference nodes with an "add external"
  form; consumption of one renders as a chip on the consumer (no cross-map line).
- Suggested (undeclared) consumption as dashed amber edges — click to accept.
- Capability/candidates in the panel (alternative providers of the same protocol).
- A "Machines" band rendering other mesh nodes' deployments (read-only).

Adds useSuggestions / useMeshDeployments / useSaveReference hooks and the
GraphEndpoint / GraphSuggestion / MeshDeployment types.
2026-07-06 16:09:12 -07:00
Paul Payne
8086a09bf7 feat(app): interactive system map (/map)
A spatial control surface for the whole node. Tools, jobs, services, and
frontends sit in lanes with program-source grips; edges show requires
(dependency), same-program siblings, and exposure to LAN/Internet targets.

Direct manipulation, each backed by the existing config PUT + apply:
- drag a node to LAN/Internet to set reach (internal/public)
- delete an exposure line or a node (with confirm) to unexpose/remove
- right-click or the per-node ⋯ menu: open, restart, expose, delete
- click the source grip to open the backing program

Nodes are draggable with Shift-/toggle box-select; layout persists to
localStorage with a reset control. Adds useSetReach/useDeleteDeployment
hooks, the @xyflow/react dep, and the nav entry + route.
2026-07-06 13:12:15 -07:00
Paul Payne
20bf78caf1 api+ui: kind-scoped save/delete endpoints and links
API: /config/{services,jobs,tools,static}/{name} now pin the twin they
target — _save_deployment/_delete_deployment take an explicit kind so a
patch to a 'backup' service can't bleed into a 'backup' job/tool. Add
/tools and /static endpoints; keep /deployments/{name} kind-agnostic.
New test_kind_twins proves per-kind save/delete isolation on disk.

UI: ConfigPanel and CreateDeploymentForm write to the kind-scoped
resource; GatewayPanel/NodeDetail/DeploymentsSection link via a shared
detailPath(name, kind) helper instead of the ambiguous /deployment/:name.

Includes incidental ruff-format reflow of untouched api files.
2026-07-06 02:51:08 -07:00
Paul Payne
add356dcf2 feat: relationship model (requires/repos/predicates) + git sync
A derived, mostly-computed model of how programs, deployments, and repos relate,
plus the git-sync surfaces that motivated it. See docs/relationships.md.

Core:
- `requires: [{kind, ref, version?, bind?}]` on programs + deployments — one
  precondition relation; `system_dependencies` is its `{kind: system}` alias.
  kind fixes meaning + check (system=installed, deployment=exists).
- relations.py: derives repos (git toplevel / monorepo), fan-in, and the
  predicates functional?/fresh?/deployed? — nothing stored.
- env is generated FROM a `{kind: deployment, bind}` requirement (target URL →
  consumer env), never scraped back into one; explicit defaults.env still wins.
- git.py: working-copy status/pull, repo toplevel + remote url.

Surfaces:
- `castle graph` + GET /graph — the relationship diagnostic.
- GET /repos, /repos/{key}/git|sync — repo-scoped sync (a repo is the sync unit;
  a monorepo backs several programs). GET /programs/{name}/git|sync + repo context.
- Dashboard: Graph screen, program-page git status + repo-aware Sync, and a
  monorepo banner on Programs.

Governing principle: predicates are derived; encode only the non-derivable, as a
node or edge property. Pull-only sync — converge stays a separate step.
2026-07-05 10:55:42 -07:00
Paul Payne
f422c1879d ui: Apply everywhere — power toggles, plan preview, no start/stop/install
Rework the dashboard around convergence:
- ServiceControls / ServiceCard / JobCard: replace start/restart/stop
  with a Power toggle (set enabled → apply, i.e. activate/deactivate)
  plus Restart (the one imperative bounce).
- ToolDetail: install/uninstall becomes an Enable/Disable toggle;
  `installed` stays the live state, `enabled` the desired one.
- ConfigPanel Apply and CreateDeploymentForm now POST /apply (one
  converge that renders + restarts only what changed) instead of
  /deploy + a separate start/restart.
- New ConvergePanel on Overview: a terraform-style Preview (POST /apply
  plan=true) showing would-activate/restart/deactivate, then Apply.
- useApply + useSetEnabled hooks; `enabled` added to Service/Job/
  Deployment types; both handle the self-restart connection drop.

Backend: PUT /config/deployments/{name}/enabled sets desired on/off
(edit config; caller runs apply) — the declarative toggle the UI uses.

Dashboard builds clean (tsc + vite); 59 API tests pass; live /apply and
/services?enabled verified after an api restart.
2026-07-02 11:49:54 -07:00
Paul Payne
6929bd89d3 Detail pages: rename the 'Runs' field to 'Launch' (matches the launcher concept)
The field showing a deployment's launcher + target is now labelled 'Launch'
(consistent with launcher/manager vocabulary) on ServiceDetail and the Service/
Job/Create field editors.
2026-07-01 13:35:02 -07:00
Paul Payne
60cbb8a844 Programs page: deployment-kind filter chips (opt-in)
ProgramList gains a filterable prop; the Programs page enables it. Chips filter by
whether a program has a deployment of that kind (membership over program.deployments),
so a tool-and-job program shows under both Tool and Job. Counts are programs-per-kind.
Tools page stays unfiltered (already scoped).
2026-07-01 13:10:13 -07:00
Paul Payne
05031a1395 Static service detail: drop the duplicate Subdomain field
A static showed both 'Served at' (from the static block) and 'Subdomain' (the
general proxied-service field) — same URL twice. Gate 'Subdomain' to non-static;
a static's gateway URL is already 'Served at'.
2026-07-01 13:08:46 -07:00
Paul Payne
0463cd91f1 Fix tool install-state, inherit program description, declutter Tools cards
- Tool detail showed installed tools as 'Not installed': /deployments/{name}
  populated 'installed' for path deployments but left 'active' null, and
  ToolDetail keyed off 'active'. Now the endpoint sets active=installed for path,
  and ToolDetail reads 'installed' directly (verified: on-PATH ⇒ True, else False).
- A deployment now inherits its program's description by default: copied at
  creation (CLI create + API _save_deployment on new deployments) + a one-time
  patchup of existing ones (29 deployments).
- Tools page cards no longer list deployments (ProgramCard showDeployments=false) —
  the lens already scopes to the tool; the deployment list is a Programs-catalog thing.
2026-07-01 13:07:00 -07:00
Paul Payne
183c589b63 Tools page cards link to the tool detail page, not the program
ProgramCard/ProgramList take an optional linkBase (default /programs); the Tools
page passes /tools so a tool tile opens /tools/<name> (its deployment detail),
not /programs/<name>.
2026-07-01 12:59:31 -07:00
Paul Payne
86d4552fdc Dashboard: Deployments nav group, tool detail page, deployment links, static-aware detail
- Nav: Services/Scheduled/Tools nested under a 'Deployments' group (expandable;
  flattened to icons when the sidebar is collapsed). Programs stays top-level.
- Overview: Tools tile (Wrench, count of tool programs).
- New /tools/:name tool detail page (useDeployment): Install/Uninstall lifecycle,
  a link up to its program, and an editable ToolFields (description + env).
- ServiceDetail is static-aware: a caddy deployment shows served-by-gateway +
  URL + root, no start/stop, and edits via a new StaticFields (root + public + env)
  so a launcher is never injected into a caddy deployment.
- Program detail's Deployment section is now a clean list of links — every
  deployment (tool→/tools, service/static→/services, job→/jobs) links to its
  detail page; lifecycle controls moved onto those pages.

clean pnpm build + tsc.
2026-07-01 12:57:25 -07:00
Paul Payne
01505328ad Dashboard: programs list deployments; Tools page; statics on Services; editable launcher
- Programs page: each card lists the program's deployments (name · kind) instead
  of a single kind badge; kind-filter chips removed (nav is the kind lens now).
  protonmail honestly shows tool + job.
- New Tools nav page (Wrench) — programs with a tool deployment (/programs?kind=tool).
- Services page now shows statics too: /services returns service + static, and a
  static renders as a caddy-served card (KindBadge, served URL, no start/stop).
- Program detail: DeploymentsSection + deleteConfirm rebuilt over program.deployments;
  header drops the (now nonexistent) program kind.
- ServiceFields / JobFields: launcher is now an editable select (python|command|
  container|compose|node), persisted into run.launcher via the normal save.

clean pnpm build + tsc --noEmit.
2026-07-01 12:32:09 -07:00
Paul Payne
6ee6d4c850 Dashboard: lifecycle is a deployment concern, not a program action
Install/Uninstall no longer live in the program's action cluster (they were a
tool's PATH-deployment lifecycle masquerading as a program action). ProgramActions
is now dev-verbs-only (build/test/lint/type-check/check). The program detail's
Deployment section owns lifecycle, keyed on kind: a tool shows Installed-on-PATH
with an Install/Uninstall toggle, a static shows served-by-gateway + its URL,
services/jobs link to their own pages. Program list cards drop the quick toggle
for a plain active dot. 'Remove program' stays as catalog deletion — no longer a
dead-end now that the blocking deployment is visible in the Deployment section.
2026-07-01 11:32:25 -07:00
Paul Payne
8120ec9f9e Drop dead ProgramSummary.runner from dashboard types
The API's ProgramSummary no longer emits runner (a program has no launcher —
that lives on its deployment). Remove the stale UI type field and the
never-rendered Launcher row on the program detail page; a program's launcher
already shows on its deployment in the Deployments section.
2026-07-01 11:00:54 -07:00
Paul Payne
317232ca6a Manager-first deployment model: split runner, merge service/job, frontend→static
Replace the conflated `runner` axis with two orthogonal ones: `manager`
(systemd|caddy|path|none) — who supervises/realizes a deployment — and, for
systemd only, a nested `launcher` (python|command|container|compose|node) — how
the process starts. ServiceSpec and JobSpec collapse into one manager-
discriminated DeploymentSpec union (Systemd/Caddy/Path/Remote); the services/
and jobs/ config dirs collapse into one deployments/ dir. The human "kind"
(service|job|tool|static|reference) is fully derived (kind_for), never stored —
the frontend kind is renamed static. behavior is gone.

- core: DeploymentSpec union + LaunchSpec + kind_for; legacy-aware loader
  normalizes old runner shapes; CastleConfig.deployments with derived
  services/jobs/tools views; registry.Deployment carries manager/launcher/kind.
- cli: service/job/tool as filtered views + a deployment group; --behavior→--kind,
  create --runner→--launcher; lifecycle dispatches over config.deployments.
- castle-api: /deployments primary with /services,/jobs as views; summaries
  derive kind; PUT/DELETE /config/deployments/{name} (services/jobs aliased).
- app: KindBadge, frontend→static everywhere, pick-a-kind creation wizard,
  per-kind config editors.
- docs: single deployments/ layout, manager/launcher, static kind throughout.

Live migration verified byte-identical: regenerated Caddyfile and every unit
ExecStart line unchanged, so nothing restarted. Suites: core 124, cli 25,
castle-api 55; dashboard build + type-check clean.
2026-07-01 10:23:03 -07:00
Paul Payne
ab10ca74fe Move Gateway up in nav; render jobs and programs as cards
Reorder the sidebar so Gateway sits right under Overview. Replace the
Scheduled and Program tables with card grids matching Services: add JobCard
and ProgramCard, convert ScheduledSection to a JobCard grid, and turn the
former ProgramTable into a filterable ProgramCard grid (renamed ProgramList;
kept the search box, dropped the sortable table). Remove the now-orphaned
SortHeader component.
2026-06-30 23:02:33 -07:00
Paul Payne
bc915d23fb Add app shell with left nav; split dashboard into top-level pages
Introduce a Layout shell with a collapsible left sidebar (state persisted
to localStorage) that becomes a hamburger drawer under md. Break the single
Dashboard into routed pages — Overview (summary tiles + node bar), Services,
Scheduled, Programs, Gateway, Mesh — nested under the shared Layout via an
Outlet. Centralize useEventStream in Layout (drop the per-page duplicates),
repoint detail-page back links at their section pages, and remove the now-dead
SectionHeader component / SECTION_HEADERS map (each page owns its header).
2026-06-30 22:52:28 -07:00
Paul Payne
4ff6af8f4d Rename the dashboard program castle-app -> castle
The dashboard is now served at castle.<domain> (was castle-app.<domain>). Since
the subdomain is the program name, this is just a program rename plus the one
special-case constant.

- Generator: _DASHBOARD = "castle" (the :9000 redirect target and off-mode root).
- Program file renamed (runtime): programs/castle-app.yaml -> programs/castle.yaml
  (source unchanged: repo:app).
- Dashboard UI: ProgramDetail shows a frontend's subdomain (via subdomainUrl),
  dropping the old castle-app-at-root special-case; comment refs updated.
- Docs: registry.md/design.md/react-vite.md updated to the subdomain model and the
  `castle` name (react-vite.md also corrected — frontends serve at their subdomain
  root with VITE_BASE=/, and builds are manual via `castle program build`, not
  deploy).
2026-06-30 21:04:18 -07:00
Paul Payne
15dc8ef6f7 Dashboard UI: subdomain checkbox, drop proxy path/host
Follow the backend to the subdomain-only model:
- Service editor + create form: replace the Proxy path / Proxy host text fields
  with a single "Expose" checkbox (writes proxy: { caddy: {} } or removes proxy).
- ServiceSummary/DeploymentSummary type: proxy_path/proxy_host → subdomain.
- ServiceCard / ServiceDetail: show the subdomain, linking to
  <subdomain>.<domain> via a new subdomainUrl() helper (domain derived from the
  dashboard's own host; null on a bare host / off mode).
2026-06-30 20:55:38 -07:00
Paul Payne
a3a862b63f feat: Add type-check script and enhance UI for better text handling 2026-06-27 12:46:15 -07:00
Paul Payne
2370b62362 ui: maximize button on the log viewer
The LogViewer header now has a maximize toggle (and a clear button) — maximize
expands it to a full-screen overlay (Esc to restore), so you can read service/
job logs without the 24rem cap. Consolidated the per-page 'Logs' Clear button
into the viewer header (both service and job detail pages).
2026-06-17 09:19:29 -07:00
Paul Payne
c40f84104d feat: unified gateway route table (static · proxy · remote)
The gateway does two things — reverse-proxy services and serve static
frontends — but the dashboard/API route table only ever showed the proxy
routes, so 'serving a frontend' and 'proxying a service' looked like unrelated
features and castle-app/power-graph-app were invisible in the route view.

Now there's one concept: a route maps an address (path '/foo' or host 'foo.lan')
to a target of one kind — static (a built dist served by file_server), proxy
(a local service port), or remote (a service on another node).

- core: caddyfile.py gains compute_routes() — the single source of truth for
  the route list; generate_caddyfile_from_registry renders it (output byte-for-
  byte identical, verified against the live Caddyfile). Adds a GatewayRoute
  dataclass.
- api: GatewayRoute model → {address, kind, target, name, node}; GET /gateway
  builds from compute_routes (incl. config for static frontends + mesh for
  remote), so the table matches what Caddy actually does.
- app: Gateway panel shows Address · Kind · Target for every route (static
  frontends + host routes now appear); program detail shows 'Reachable at
  /foo/ · served (static)' for static frontends.
- cli: 'castle gateway status' prints the full route table.
- docs: registry.md/design.md/CLAUDE.md describe routes as one concept with
  three target kinds.

core 94 / cli 24 / api 52 green; ruff + app build clean; Caddyfile unchanged.
2026-06-14 17:48:35 -07:00
Paul Payne
2a073fc0b4 Stage 4: App — create service/job forms (UI twin of castle expose)
CreateDeploymentForm builds a service or job in castle.yaml (PUT /config/...),
then deploys it (POST /deploy {name} → unit + Caddyfile + reload) and, for a
service, starts it (POST /services/{name}/start), then navigates to the new
detail page.

Reachable two ways:
- Program page Deployments section: 'Create service' / 'Create job' buttons,
  prefilled from the program (name, program ref, run target, runner).
- Dashboard: standalone '+ Add service' / '+ Add job' (deployments can run
  anything, not just castle programs).

Service form covers port / port_env / health / proxy path / proxy host; job
form covers schedule. Name validated against existing services/jobs.

Verified: the configs the form builds validate (PUT 200) and round-trip clean.
App builds; api 52 / core 94 green from prior stages.
2026-06-14 15:41:13 -07:00
Paul Payne
dea585b15b 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.
2026-06-14 15:37:20 -07:00
Paul Payne
203f5212e6 fix(app): drive program activate toggle off active, not installed
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.
2026-06-14 12:26:29 -07:00
Paul Payne
432bf2c57f chore(app): remove orphaned ConfigEditor page
Imported nowhere and absent from the router — an unreachable bulk-editor
superseded by the typed detail pages (ServiceDetail/ProgramDetail/
ScheduledDetail), which edit via ConfigPanel. It also called endpoints that
no longer exist (GET /components, PUT/DELETE /config/components/{name}; the
API uses /deployments and typed /config/{programs,services,jobs}/{name}).

App builds clean.
2026-06-14 12:17:41 -07:00
Paul Payne
0515faadfd refactor(app): rename Component* UI widgets to accurate domain names
The dashboard widgets were named after the old 'component' concept. Renamed
each to what it actually renders:

- ComponentCard  → ServiceCard      (ServiceSummary, /services/, service actions)
- ComponentTable → ProgramTable     (ProgramSummary[], the program catalog)
- ComponentEditor→ DeploymentEditor (DeploymentDetail, the unified type)
- ComponentFields→ DeploymentFields (AnyDetail)
- AddComponent   → AddDeployment

Also purged the domain term 'component' from the rest of the app: the
useComponent hook → useDeployment, ConfigPanel + the three detail pages'
`component` prop/var → `deployment`, and the gateway/node table headers
(Component → Program / Deployment). Props renamed to match (component →
service / program / deployment). The React UI directory app/src/components/
keeps its conventional name.

App type-checks and builds clean.
2026-06-14 12:13:41 -07:00
Paul Payne
3f3b88f17b refactor: Align vocabulary — component → program / deployment
Canonical terms (see docs/registry.md glossary):
- program: any catalog entry (tool/daemon/frontend) — the software
- service / job: how a program is deployed (systemd .service / .timer)
- deployment: umbrella for the unified service+job+program view

Changes:
- core: ServiceSpec/JobSpec component: → program: (component accepted as
  back-compat validation_alias); DeployedComponent → Deployment.
- api: ComponentSummary/Detail → DeploymentSummary/Detail; GET /components
  → /deployments; GatewayRoute.component → program; GatewayInfo
  .component_count → deployment_count; ServiceActionResponse/action keys
  component → program.
- app: matching type renames, /components → /deployments hook, route
  /component/:name → /deployment/:name, GatewayRoute.program.
- docs: component-registry.md → registry.md (+ canonical glossary);
  CLAUDE.md endpoint list refreshed (drop removed /tools, add typed
  program/service/job + config routes).

Tests: core 92, cli 24, api 52 green; app build clean; ruff clean.
2026-06-14 11:05:22 -07:00
Paul Payne
2953aea548 Runtime half: convergent deploy + unified active lifecycle
Convergent deploy (prefix-based prune): castle deploy now removes orphaned
castle-* systemd units/timers no longer in the registry. Full deploy only;
partial (--name) deploys preserve siblings. Fixes the orphaned-timer / stale-
path drift class.

Container runner: derive --name from the service name (castle-<name>) instead
of the image name; skip the <PREFIX>_DATA_DIR env injection for containers
(avoids colliding with image env namespaces like NEO4J_*).

Unified active lifecycle (core/lifecycle.py): install/uninstall keep their
names but become activate/deactivate, dispatching by behavior — tool→PATH,
daemon/self-serving-frontend/job→systemd, static frontend→served. is_active
reports a uniform state (PATH-independent for tools). The CLI install/uninstall
and service enable/disable route through the extracted core.

API + dashboard: surface a uniform `active` state on programs alongside
`installed`; ProgramDetail shows active/inactive.

Tests: test_deploy_prune (6), test_lifecycle (5). 168 tests pass; lint clean.
2026-06-13 18:08:54 -07:00
Paul Payne
400e0b253b Less stack-centric and location-centric model. 2026-06-13 17:26:49 -07:00
payneio
9fd2221e05 refactor: Rename component pages to program pages and remove tools concept
The 'tools' concept was just a filtered view of programs and added unnecessary
complexity to the frontend. This commit:

- Removes ToolCard.tsx and Tools.tsx (pages/components never used in router)
- Renames ComponentDetail.tsx → ProgramDetail.tsx (exports ProgramDetailPage)
- Renames ComponentRedirect.tsx → ProgramRedirect.tsx (exports ProgramRedirect)
- Updates imports in routes.tsx to match new file names
- Fixes stale comment in hooks.ts

Frontend naming now aligns with the unified programs model.
2026-04-27 21:10:31 -07:00
payneio
2069e30353 refactor: Remove dedicated /tools endpoints, use /programs?behavior=tool instead
Eliminates inconsistency where tools had their own filtered view but daemons and frontends didn't.

Changes:
- API: Removed tools router and ToolSummary/ToolDetail models. Added optional behavior query parameter to GET /programs for filtering by program type (tool, daemon, frontend).
- Frontend: Updated hooks to pass behavior param to usePrograms instead of separate useTools. Updated components to use ProgramSummary type. Removed useToolDetail hook.
- Docs: Updated API documentation to reflect program behavior filtering.
2026-04-27 21:06:57 -07:00
Paul Payne
efab2a7893 refactor: Update component specifications to use 'program' and 'behavior' attributes, enhancing clarity and consistency across the application 2026-02-23 22:56:18 -08:00
Paul Payne
0d36e4f72a Refactor component terminology to programs in config and manifest
- Updated the terminology from "components" to "programs" across the codebase, including in config loading, saving, and manifest specifications.
- Introduced a new `stacks.py` file to handle lifecycle actions for development stacks, implementing handlers for Python and React Vite stacks.
- Adjusted tests to reflect the new program structure and ensure proper functionality.
- Revised documentation to align with the new terminology and structure, ensuring clarity on the purpose and configuration of programs, services, and jobs.
2026-02-23 22:09:41 -08:00
Paul Payne
f559fba143 feat: Introduce job and service categorization for components, enhancing routing and display logic across the application 2026-02-23 17:02:55 -08:00
Paul Payne
56b9c8ddf1 refactor: Update component structure to use behavior and stack attributes, enhancing clarity and functionality across services, tools, and frontends 2026-02-23 16:32:55 -08:00
Paul Payne
3343e955fd feat: Implement multi-node support with MQTT and mDNS for service discovery and coordination 2026-02-23 02:30:12 -08:00
Paul Payne
eeaa5045d0 refactor: Decouple roles. 2026-02-23 01:49:24 -08:00
Paul Payne
eab6f8b535 refactor: update tools handling and improve CLI documentation 2026-02-22 21:34:00 -08:00
Paul Payne
bb367ab632 fix: update gateway name to 'castle-gateway' for correct identification 2026-02-21 01:26:58 -08:00