sx-pub: live dashboard UI on the plan page

Server-rendered dashboard showing live data from sx-pub API:
- Server status (DB, IPFS, actor, domain)
- Actor identity card with public key
- Collections grid with paths
- Published documents with CIDs and sizes
- Recent outbox activity feed
- Followers list
- API endpoint links for direct access

All phases marked as complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 02:18:45 +00:00
parent d12f38a9d5
commit 68fcdd6cc0

View File

@@ -244,14 +244,131 @@
(~docs/section :title "Implementation Phases" :id "phases"
(div :class "space-y-4"
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
(h4 :class "font-semibold text-emerald-800" "Phase 1: Foundation")
(h4 :class "font-semibold text-emerald-800" "Phase 1: Foundation")
(p :class "text-emerald-700 text-sm" "DB schema, async IPFS client, actor endpoint, webfinger, " (code "/pub/actor") " returns SX actor document."))
(div :class "rounded border border-sky-200 bg-sky-50 p-4"
(h4 :class "font-semibold text-sky-800" "Phase 2: Publishing")
(h4 :class "font-semibold text-sky-800" "Phase 2: Publishing")
(p :class "text-sky-700 text-sm" "Pin to IPFS, path→CID index, collection browsing. Publish the actual SX spec files as the first content."))
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
(h4 :class "font-semibold text-violet-800" "Phase 3: Federation")
(h4 :class "font-semibold text-violet-800" "Phase 3: Federation")
(p :class "text-violet-700 text-sm" "Inbox/outbox, follow/accept, HTTP signature verification, activity delivery, content mirroring."))
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(h4 :class "font-semibold text-amber-800" "Phase 4: Anchoring")
(p :class "text-amber-700 text-sm" "Merkle trees, OpenTimestamps, Bitcoin proof, provenance UI."))))))
(h4 :class "font-semibold text-amber-800" "Phase 4: Anchoring")
(p :class "text-amber-700 text-sm" "Merkle trees, OpenTimestamps, Bitcoin proof, provenance verification."))))
;; -----------------------------------------------------------------------
;; Live Dashboard
;; -----------------------------------------------------------------------
(~docs/section :title "Live Dashboard" :id "dashboard"
(p "Live data from the sx-pub API — server-rendered from the same endpoints.")
;; --- Status ---
(~docs/subsection :title "Server Status"
(let ((status (helper "pub-status-data")))
(div :class "grid grid-cols-2 sm:grid-cols-4 gap-3"
(div :class "rounded border border-stone-200 p-3 text-center"
(p :class "text-xs text-stone-400 uppercase" "DB")
(p :class "font-semibold text-sm" (get status "db")))
(div :class "rounded border border-stone-200 p-3 text-center"
(p :class "text-xs text-stone-400 uppercase" "IPFS")
(p :class "font-semibold text-sm" (get status "ipfs")))
(div :class "rounded border border-stone-200 p-3 text-center"
(p :class "text-xs text-stone-400 uppercase" "Actor")
(p :class "font-semibold text-sm" (get status "actor")))
(div :class "rounded border border-stone-200 p-3 text-center"
(p :class "text-xs text-stone-400 uppercase" "Domain")
(p :class "font-semibold text-sm" (or (get status "domain") "—"))))))
;; --- Actor ---
(~docs/subsection :title "Actor Identity"
(let ((actor (helper "pub-actor-data")))
(div :class "rounded border border-stone-200 bg-stone-50 p-4 space-y-2"
(div :class "flex items-center gap-3"
(div :class "w-10 h-10 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold" "sx")
(div
(p :class "font-semibold" (get actor "display-name"))
(p :class "text-sm text-stone-500" (str "@" (get actor "preferred-username") "@" (get actor "domain")))))
(p :class "text-sm text-stone-600" (get actor "summary"))
(details :class "text-xs"
(summary :class "text-stone-400 cursor-pointer" "Public key")
(pre :class "mt-2 bg-stone-100 rounded p-2 text-xs overflow-x-auto" (get actor "public-key-pem"))))))
;; --- Collections ---
(~docs/subsection :title "Collections"
(let ((collections (helper "pub-collections-data")))
(div :class "grid gap-3"
(map (fn (c)
(div :class "rounded border border-stone-200 p-4 hover:border-violet-300 transition-colors"
(div :class "flex items-center justify-between"
(div
(h4 :class "font-semibold text-stone-800" (get c "name"))
(p :class "text-sm text-stone-500" (get c "description")))
(span :class "text-xs font-mono text-violet-600 bg-violet-50 px-2 py-1 rounded"
(str "/pub/" (get c "slug"))))))
collections))))
;; --- Published Documents ---
(~docs/subsection :title "Published Documents"
(let ((specs (helper "pub-collection-items" "core-specs")))
(when (not (get specs "error"))
(div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-500 uppercase tracking-wide"
(get specs "name"))
(map (fn (d)
(when (!= (get d "slug") "")
(div :class "rounded border border-stone-200 p-3 flex items-center justify-between"
(div
(p :class "font-medium text-stone-800" (get d "title"))
(p :class "text-xs text-stone-400" (get d "summary")))
(div :class "text-right"
(p :class "text-xs font-mono text-emerald-600 truncate max-w-48" (get d "cid"))
(p :class "text-xs text-stone-400" (str (get d "size") " bytes"))))))
(get specs "items"))))))
;; --- Outbox ---
(~docs/subsection :title "Recent Activity"
(let ((outbox (helper "pub-outbox-data" "")))
(if (= (get outbox "total") 0)
(p :class "text-sm text-stone-400 italic" "No activities yet.")
(div :class "space-y-2"
(p :class "text-xs text-stone-400" (str (get outbox "total") " total activities"))
(map (fn (a)
(when (!= (get a "type") "")
(div :class "rounded border border-stone-200 p-3 flex items-center gap-3"
(span :class "text-xs font-semibold text-white bg-violet-500 px-2 py-0.5 rounded"
(get a "type"))
(span :class "text-xs text-stone-500" (get a "published"))
(when (!= (get a "cid") "")
(span :class "text-xs font-mono text-emerald-600 truncate max-w-48"
(get a "cid"))))))
(get outbox "items"))))))
;; --- Followers ---
(~docs/subsection :title "Followers"
(let ((followers (helper "pub-followers-data")))
(if (empty? followers)
(p :class "text-sm text-stone-400 italic" "No followers yet.")
(div :class "space-y-2"
(map (fn (f)
(when (!= (get f "acct") "")
(div :class "rounded border border-stone-200 p-3 flex items-center gap-2"
(div :class "w-8 h-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold" "F")
(p :class "text-sm font-mono text-stone-600 truncate" (get f "acct")))))
followers))))
;; --- API Endpoints ---
(~docs/subsection :title "Try the API"
(p :class "text-sm text-stone-600 mb-2" "All endpoints return " (code "text/sx") ". Try them directly:")
(div :class "grid grid-cols-2 sm:grid-cols-3 gap-2"
(map (fn (endpoint)
(a :href (get endpoint "href")
:class "block rounded border border-stone-200 p-2 text-center hover:border-violet-300 hover:bg-violet-50 transition-colors text-xs font-mono text-stone-600"
(get endpoint "label")))
(list
{"label" "GET /pub/actor" "href" "/pub/actor"}
{"label" "GET /pub/status" "href" "/pub/status"}
{"label" "GET /pub/collections" "href" "/pub/collections"}
{"label" "GET /pub/outbox" "href" "/pub/outbox"}
{"label" "GET /pub/followers" "href" "/pub/followers"}
{"label" "GET /pub/following" "href" "/pub/following"}))))))))