From d68de69fe663d55e7757a51b38386d074cee98a8 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 19 Jun 2026 21:03:35 +0200 Subject: [PATCH] STATE-WP-0062 T3: Services nav section + First/Self Hosted pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single "Services (TPSC)" nav entry with a Services section: Third Party (existing /tpsc cloud-third-party view), First Party (/services/first-party — Service Maturity Level + dev-repo columns, development_type=first_party), and Self Hosted (/services/self-hosted — self_hosted third-party OSS with upstream/host/runbook). New pages are filtered views over /services/catalog and degrade to an empty-state if the API is offline. Co-Authored-By: Claude Opus 4.8 --- dashboard/observablehq.config.js | 11 +++++- dashboard/src/services/first-party.md | 49 +++++++++++++++++++++++++++ dashboard/src/services/self-hosted.md | 48 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 dashboard/src/services/first-party.md create mode 100644 dashboard/src/services/self-hosted.md diff --git a/dashboard/observablehq.config.js b/dashboard/observablehq.config.js index c467f7b..f10d757 100644 --- a/dashboard/observablehq.config.js +++ b/dashboard/observablehq.config.js @@ -34,7 +34,16 @@ export default { { name: "Inbox", path: "/inbox" }, { name: "Progress", path: "/progress" }, { name: "Token Cost", path: "/token-cost" }, - { name: "Services (TPSC)", path: "/tpsc" }, + { + name: "Services", + collapsible: true, + open: false, + pages: [ + { name: "Third Party", path: "/tpsc" }, + { name: "First Party", path: "/services/first-party" }, + { name: "Self Hosted", path: "/services/self-hosted" }, + ], + }, { name: "Todo", path: "/todo" }, { name: "Tools & Apps", path: "/tools" }, // ── Sections (alphabetical) ─────────────────────────────────────────────── diff --git a/dashboard/src/services/first-party.md b/dashboard/src/services/first-party.md new file mode 100644 index 0000000..dbc7403 --- /dev/null +++ b/dashboard/src/services/first-party.md @@ -0,0 +1,49 @@ +--- +title: First Party Services +--- + +# First Party Services Catalog + +Services **coulomb is development-responsible for** (`development_type = first_party`), +whether deployed to a cloud or self-hosted on coulomb infrastructure. The +**Service Maturity Level** column tracks each service against the +[Service DoM](/policy/service-dom) (1 · Core → 2 · Standard → 3 · Mature). + +```js +import {API} from "../components/config.js"; +``` + +```js +const services = await fetch(`${API}/services/catalog?development_type=first_party`) + .then(r => r.ok ? r.json() : []) + .catch(() => []); +const repos = await fetch(`${API}/repos/`) + .then(r => r.ok ? r.json() : []) + .catch(() => []); +const repoById = new Map(repos.map(r => [r.id, r.slug])); +``` + +```js +const LEVEL = {1: "1 · Core", 2: "2 · Standard", 3: "3 · Mature"}; + +const rows = services.map(s => ({ + Service: s.name, + Slug: s.slug, + Hosting: s.hosting_type === "self_hosted" ? "self-hosted" : "cloud-hosted", + "Maturity Level": s.maturity_level ? LEVEL[s.maturity_level] : "—", + "Dev Repo": s.first_party?.repo_id ? (repoById.get(s.first_party.repo_id) ?? "(unlinked)") : "—", + Domain: s.first_party?.owning_domain ?? "—", + Status: s.status, +})); +``` + +```js +display(services.length === 0 + ? html`
No first-party services registered yet. Add one with + POST /services/catalog (development_type: "first_party").
` + : Inputs.table(rows, { + columns: ["Service", "Hosting", "Maturity Level", "Dev Repo", "Domain", "Status"], + sort: "Service", + rows: 30, + })); +``` diff --git a/dashboard/src/services/self-hosted.md b/dashboard/src/services/self-hosted.md new file mode 100644 index 0000000..c7dc6eb --- /dev/null +++ b/dashboard/src/services/self-hosted.md @@ -0,0 +1,48 @@ +--- +title: Self Hosted Services +--- + +# Self Hosted Services Catalog + +Services and webapps built on **third-party / open-source software** that coulomb +**hosts and operates** as part of the three-helix infrastructure +(`hosting_type = self_hosted`, `development_type = third_party`). coulomb runs +these but is not development-responsible for them. + +> First-party services that coulomb also self-hosts (e.g. the State Hub itself) +> are listed under [First Party](/services/first-party), classified by who develops +> them. + +```js +import {API} from "../components/config.js"; +``` + +```js +const services = await fetch(`${API}/services/catalog?hosting_type=self_hosted&development_type=third_party`) + .then(r => r.ok ? r.json() : []) + .catch(() => []); +``` + +```js +const rows = services.map(s => ({ + Service: s.name, + Slug: s.slug, + "Upstream OSS": s.self_hosted?.upstream_oss_project ?? s.owner_or_provider ?? "—", + "Helix Instance": s.self_hosted?.helix_instance ?? "—", + Host: s.self_hosted?.host_node ?? "—", + Runbook: s.self_hosted?.runbook_ref ?? "—", + Status: s.status, +})); +``` + +```js +display(services.length === 0 + ? html`
No self-hosted third-party services registered yet. Add one with + POST /services/catalog (hosting_type: "self_hosted", + development_type: "third_party").
` + : Inputs.table(rows, { + columns: ["Service", "Upstream OSS", "Helix Instance", "Host", "Runbook", "Status"], + sort: "Service", + rows: 30, + })); +```