- new Secrets page (/secrets, nav entry): lists vault secrets, reveal/copy,
add/edit/delete, shows active backend + addr + role; read-only on followers
- GET /secrets/info (backend/addr/role/writable); fix /secrets CRUD to use the
castle.yaml-selected backend (was defaulting to the now-empty file backend)
- OpenBaoBackend.list_names drops folder entries (node-prefix groups)
- tests: conftest forces file backend so host castle.yaml can't leak into tests
app builds clean (tsc 0); endpoints verified live against the vault.
The System Map subsumes the read-only Graph diagnostic, so remove the page,
its route, and nav entry (the /graph API + useGraph stay — the map and palette
consume them).
Add a host switcher under the sidebar brand: it shows which castle host you're
driving (the is_local node) and, when peers are present, jumps to a peer's
dashboard at castle.<domain>, preserving the current view. Node summaries now
carry gateway_domain so the switcher can build each host's URL.
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.
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.
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.
- 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.
- 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.
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.
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).