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 oncefamily.hoffdesk.comis 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)
- Wire family.hoffdesk.com — add tunnel entry + DNS CNAME. Dashboard goes live.
- Wire pipeline Phase 2 templates to backend routes. Content generation pipeline goes live.
- Clean up
blog.hoffdesk.com+proto.hoffdesk.com.
This Sprint
- Build
/api/today— aggregate endpoint returning real calendar + weather + system health. Dashboard becomes real. - Convert dashboard to Jinja2 — shared base layout, auth-aware nav, user greeting.
- Build family dashboard login page — guest-friendly version of the admin login (Aundrea's first impression).
Next Sprint
- Dashboard Week View — Family Assistant v1.5 UI mocks (conflict panel, undo toast, digest card).
- Public landing page at
hoffdesk.com. - API gateway pattern — if we want
api.hoffdesk.comJSON-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
-
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.
-
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.
-
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.
-
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.