📄 sprint-brief-2026-04-24.md 9,778 bytes Apr 24, 2026 📋 Raw

HoffDesk Architecture — Sprint Planning Brief

Date: 2026-04-24
Owner: Matt (Director)
Architects: Daedalus 🎨 (Frontend/UX), Socrates 🧠 (Backend/Infra)
Model: OpenClaw agents on Beelink (titanium-butler), Gaming PC (Tailscale) for local LLM


1. Service Topology

A single Beelink machine running everything behind Cloudflare Tunnel.

Internet
   │
   ▼
Cloudflare (hoffdesk.com)
   │
   ▼
Cloudflare Tunnel (cloudflared)
   │
   ├── :5232  → Radicale (CalDAV server)
   │
   └── :8000  → hoffdesk-api (FastAPI — single app serving everything)
                   │
                   ├── Public blog pages (HTML)
                   ├── Blog admin panel (HTML + HTMX)
                   ├── Content generation API (JSON)
                   ├── Family dashboard API (JSON)
                   ├── Family admin (HTML)
                   ├── Auth endpoints (login/logout/session)
                   ├── Webhook receivers
                   └── Telegram bot callback

Key constraint: One FastAPI process on port 8000 handles all routes. Subdomain separation is cosmetic — the app routes internally.


2. Subdomain Map

Subdomain Status Backend Port What It Serves Owner
hoffdesk.com ❌ Not configured Public landing page (future)
notes.hoffdesk.com ✅ Live :8000 Blog public pages + blog admin + content pipeline Socrates
blog.hoffdesk.com ❌ Removed Dead entry removed from config Done ✅
api.hoffdesk.com ✅ Live :8000 JSON API (blog, content, family, health) + blog HTML Socrates
family.hoffdesk.com ✅ Live :8000 Family dashboard Daedalus + Socrates
cal.hoffdesk.com ✅ Live :5232 CalDAV (Radicale) Socrates
hook.hoffdesk.com ✅ Live :8000 Webhook receiver (Cloudflare Worker → Beelink) Socrates
proto.hoffdesk.com ⚠️ Dev only :8765 Dashboard prototype (mock data, manual start) Retire now (family.hoffdesk.com live)

🔧 Housekeeping Items

  • blog.hoffdesk.com — either add DNS CNAME pointing to the tunnel or remove from ~/.cloudflared/config.yml. Currently a dead config entry.
  • proto.hoffdesk.com — retire once family.hoffdesk.com is wired. The dev server needs manual startup and returns mock data; the real API has session auth and actual data now.
  • api.hoffdesk.com — currently serves blog HTML at / alongside JSON. If we want a clean JSON-only gateway, route by subdomain header. If not, update the plan to match reality.

3. Route Hierarchy

hoffdesk-api
├── [PUBLIC]  /
   └── Blog index, article pages, categories, tags

├── [PUBLIC]  /api/blog/
   └── JSON API: posts, categories, tags, RSS, sitemap

├── [AUTH]    /admin/blog/
   ├── Dashboard, post list, editor, images
   └── Content pipeline (Magic Wand + Struggle v2)

├── [AUTH]    /api/v1/content/
   └── Content pipeline v2 API (briefs CRUD, submit, approve, reject, output, score)

├── [AUTH]    /admin/family/
   └── Family pipeline status, email webhook config

├── [AUTH]    /family/events/removed
   └── Recently removed events (machine-to-machine via Bearer token)

├── [AUTH]    /auth/login, /auth/logout, /auth/me
   └── Session auth (POST login sets cookie, GET me validates)

├── [INTERNAL] /health
   └── Health check

├── [INTERNAL] /telegram/callback
   └── Telegram bot webhook

└── [INTERNAL] /webhook
    └── Generic webhook receiver

4. Auth Model (Finalized 2026-04-24)

Session cookies for humans, Bearer tokens for machines. No exceptions.

Route Auth Method Who Notes
/ and /api/blog/* (public) None Anyone SEO + RSS readers
/admin/blog/* Session cookie Matt (admin, editor) Redirects to login if unauthenticated
/admin/family/* Session cookie Matt, Aundrea (family) Role check required
/api/v1/content/* Session cookie (browser) or Bearer (API) Matt Dual-mode for admin panel + scripts
/family/events/removed Bearer token (X-Hoffdesk-Secret) Dashboard proxy Machine-to-machine only
/telegram/callback Internal secret Telegram bot Unchanged
/webhook Internal secret Cloudflare Worker Unchanged

Credentials

User Password Role Access
matt hoffdesk-matt-2026 admin, editor Everything
aundrea hoffdesk-aundrea-2026 family Dashboard only

Auth Flow (Aundrea)

family.hoffdesk.com
  ↓ (no session cookie)
/auth/login?redirect=/family/dashboard/
  ↓ (enters credentials)
Session cookie set (HttpOnly, SameSite=Lax, 30 days)
  ↓
Redirect to /family/dashboard/
  ↓
HTMX polls /api/today (cookie automatic)

5. What Each Person Owns

Wadsworth 📋 — Chief of Staff & Coordination

Component Status Next Action
family.hoffdesk.com tunnel entry ✅ Done DNS CNAME added, tunnel routing confirmed live
blog.hoffdesk.com cleanup ✅ Done Was already removed from config, no DNS entry
proto.hoffdesk.com retirement 🔜 Ready DNS route can be removed (config already absent)
Sprint coordination 🔜 Ongoing Track blockers, handoffs, auth log monitoring

Socrates 🧠 — Backend & Infrastructure

Component Status Next Action
FastAPI app (port 8000) ✅ Live
Blog API (13 routes + 36 tests) ✅ Live
Content pipeline v2 API (8 endpoints) ✅ Live Wire Phase 2 frontend templates
Session auth middleware + login/logout ✅ Live
/auth/login + /auth/me + /auth/logout ✅ Live
Role-based access (admin vs family) ✅ Live (Verify family role scoping)
/api/today endpoint ✅ Live Real data from Radicale + weather + health
/family/events/removed ✅ Live Tested and returning data
family.hoffdesk.com tunnel entry ✅ Configured Added to ~/.cloudflared/config.yml
/ root route for family subdomain ✅ Live Redirects unauth → /family/login/, auth → dashboard
/family/login/ page ✅ Live Dedicated family login page (not admin login)
Dashboard static files (/static/style.css) ✅ Live Mounted at /static/
Pipeline admin UI routes ❌ Not wired Serve Daedalus' Phase 2 templates

Daedalus 🎨 — Frontend & UX

Component Status Next Action
Blog templates (7 Jinja2 files + CSS) ✅ Live
Content pipeline v2 frontend (4 templates + CSS) ✅ Delivered Blocked on route wiring
Auth migration (removed all tokens from templates) ✅ Done
Family dashboard HTML + CSS ✅ Delivered Blocked on /api/today + tunnel
Recently removed events widget (dashboard card) ✅ Delivered Hitting live endpoint
Dashboard login flow ❌ Not built Need to add guest-role experience
Dashboard Jinja2 conversion (for shared layout) 🔜 Next After Socrates wires routes

6. Sprint Priorities

Now (unblocked)

  1. Wire family.hoffdesk.com — add tunnel entry + DNS CNAME. Dashboard goes live.
  2. Wire pipeline Phase 2 templates to backend routes. Content generation pipeline goes live.
  3. Clean up blog.hoffdesk.com + proto.hoffdesk.com.

This Sprint

  1. Build /api/today — aggregate endpoint returning real calendar + weather + system health. Dashboard becomes real.
  2. Convert dashboard to Jinja2 — shared base layout, auth-aware nav, user greeting.
  3. Build family dashboard login page — guest-friendly version of the admin login (Aundrea's first impression).

Next Sprint

  1. Dashboard Week View — Family Assistant v1.5 UI mocks (conflict panel, undo toast, digest card).
  2. Public landing page at hoffdesk.com.
  3. API gateway pattern — if we want api.hoffdesk.com JSON-only.

7. Architecture Decisions Log

Date Decision Made By
2026-04-19 Sovereign stack: FastAPI + HTMX + Tailwind, no SPA frameworks Matt
2026-04-19 Mobile-first, dark mode default, no toggle Matt
2026-04-20 Blog at notes.hoffdesk.com, admin at /admin/blog/ Matt
2026-04-21 Dashboard at family.hoffdesk.com, bare domain reserved for landing Matt
2026-04-24 Session cookies for browsers, Bearer tokens for API/webhooks Matt
2026-04-24 Roles: admin (full), family (dashboard only) Matt

8. Pain Points

  1. Monolith pressure. One FastAPI app handles blog, admin, content pipeline, family dashboard, webhooks, and bot callbacks. It works now but won't scale to multi-service gracefully. Not urgent, but worth noting.

  2. Dashboard / blog / family admin all live in different template directories. My workspace templates, shared template symlinks, production template paths — it's easy to update the wrong copy. Need a single source of truth.

  3. Three auth patterns existed for a week. We killed the tokens-in-URLs pattern, but the session middleware is brand new. Need a sprint of just watching auth logs to catch edge cases.

  4. Dashboard is HTML+JS, not Jinja2. This means it doesn't inherit shared layouts or benefit from server-side rendering. Converting it matters most when Aundrea starts using it daily.