Phase 8 Artifacts — Agent Dashboard + Search + Community

Status: Draft Last Updated: August 29, 2026 Tags: agentic, dashboard, search, bookmarks, comments, rss


Summary

Three artifacts from Phase 8:

  1. Agent Dashboard — real-time KPI cards for infrastructure status
  2. Search / Related Content — tag chips + related post links
  3. Comments / Bookmarks / RSS — brief design for community features

1. Agent Dashboard — Real-Time Status Design

Route

/admin/dashboard (admin/owner only; renders inside the admin layout)

KPI Cards (top row)

Card Metric Source Color
Infrastructure 3/3 healthy k8s API: kubectl get nodes Green
Deploys This Week N (count of deploy workflow runs) GitHub/Forgejo API Blue
Active Tickets N open Forgejo Issues API Amber
KB Sync Status Synced / Behind git log --oneline -1 Green/Red
Uptime (30d) 99.X% Uptime Kuma API Green/Amber/Red
Mailgun Delivery Rate XX% (delivered / sent) Mailgun stats API Green/Amber

Card component sketch

┌──────────────────────────┐
│  Infrastructure     ●    │  ← green dot = healthy
│  3 / 3 nodes healthy     │
│  Updated 14s ago         │
└──────────────────────────┘
┌──────────────────────────┐
│  Deploys This Week   📦  │
│  4  deploys             │
│  Last: 2h ago           │
└──────────────────────────┘

Implementation notes

Prisma model additions needed

model Deployment {
  id          String   @id @default(cuid())
  sha         String
  branch      String
  status      String   // "pending" | "running" | "success" | "failed"
  triggeredAt DateTime @default(now())
  finishedAt  DateTime?
  environment String   // "staging" | "production"
}

2. Search / Related Content — Tag + Related Links

Tag chips (below post title / on blog listing cards)

Tags: [Infrastructure] [Self-Hosting] [Privacy]

Related posts section (bottom of post template)

Related:
  → Building a Self-Hosted Email Server (same tags: 3)
  → Why I Migrated Away from the Cloud (same author: 1)
  → The Philosophy of Self-Sufficiency (same category: 1)

Algorithm (simple, no ML):

-- Related by shared tag count, then recency
SELECT p.id, p.title, p.slug,
       COUNT(pt_shared.tag_id) AS shared_tags
FROM Post p
JOIN PostTag pt_shared ON pt_shared.post_id = p.id
WHERE pt_shared.post_id != :currentPostId
  AND pt_shared.tag_id IN (
    SELECT tag_id FROM PostTag WHERE post_id = :currentPostId
  )
  AND p.status = 'PUBLISHED'
GROUP BY p.id
ORDER BY shared_tags DESC, p.publishedAt DESC
LIMIT 5;

Frontend component:

function RelatedPosts({ currentPostId, tags }: { currentPostId: string; tags: Tag[] }) {
  // Fetches /api/posts/related?postId=...&tags=...
  // Renders <RelatedPostCard> list
}

3. Comments / Bookmarks / RSS — Design Brief

Comments

Decision: Implement threaded comments (post author can moderate).

model Comment {
  id        String    @id @default(cuid())
  postId    String
  post      Post      @relation(fields: [postId], references: [id])
  authorId  String?   // null = guest comment
  author    User?     @relation(fields: [authorId], references: [id])
  authorName String   // guest display name
  authorEmail String  // guest email (not shown publicly)
  body      String
  status    String    @default("PENDING") // PENDING | APPROVED | SPAM | DELETED
  parentId  String?  // null = top-level
  parent    Comment?  @relation("CommentReplies", fields: [parentId], references: [id])
  replies   Comment[] @relation("CommentReplies")
  createdAt DateTime  @default(now())
}

Endpoints:

UI states: Loading skeleton, empty ("Be the first to comment"), thread list with reply button, comment form (name/email/body for guests), success toast.

Bookmarks

model Bookmark {
  id      String @id @default(cuid())
  userId  String
  user    User   @relation(fields: [userId], references: [id])
  postId  String
  post    Post   @relation(fields: [postId], references: [id])
  @@unique([userId, postId])
  createdAt DateTime @default(now())
}

Endpoints:

UI: Bookmark icon on post cards and post template. Filled = bookmarked. Requires auth; prompts login if not authenticated.

RSS Feed

Route: /feed.xml

Generated at build time (static) or server-side on request (dynamic for up-to-date counts):

// src/routes/feed.xml.tsx
// Content-Type: application/rss+xml
// Returns <rss><channel> with last 20 published posts
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>fogserv.cloud</title>
    <link>https://fogserv.cloud</link>
    <description>Self-hosted infrastructure, agentic automation, and democratic technology.</description>
    <language>en-us</language>
    <lastBuildDate>Fri, 29 Aug 2026 12:00:00 +0000</lastBuildDate>
    <atom:link href="https://fogserv.cloud/feed.xml" rel="self" type="application/rss+xml"/>
    <!-- <item> repeated per post -->
  </channel>
</rss>

Implementation: TanStack Router supports file-based routes; create src/routes/feed.xml.tsx that renders XML. Add <link rel="alternate" type="application/rss+xml"> to the HTML <head>.


Next Steps / Ops Actions

  1. Dashboard: Add Deployment model to prisma/schema.prisma. Wire Forgejo API or GitHub Actions API for deploy counts. Build <KPICard> component and <DashboardPage> in admin.
  2. Related content: Add GET /api/posts/related endpoint. Render <RelatedPosts> on PostPage.
  3. Comments: Add Comment model + migration. Build CommentSection component with CommentForm and CommentThread. Implement moderation UI in admin.
  4. Bookmarks: Add Bookmark model + migration. Wire bookmark toggle on post cards and post template.
  5. RSS: Create src/routes/feed.xml.tsx with XML rendering. Add to <head>.

Sources & Related

Change Log

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse