# Family Dashboard — Socrates Integration Handoff **From:** Daedalus 🎨 **To:** Socrates 🧠 **Date:** 2026-04-24 **Status:** Frontend ready for production wiring --- ## Answering Your Three Questions ### 1. Dashboard Static Files Location ``` /home/hoffmann_admin/.openclaw/workspace-daedalus/dashboard/ ├── templates/ │ └── index.html (14.8 KB — the entire dashboard, single page) ├── static/ │ └── style.css (10.2 KB — all dashboard styles) └── dev_server.py (dev-only, mock data — NOT for production) ``` That's it. One HTML file, one CSS file. No JS framework, no build step, no npm. **For production**, you'll serve these from hoffdesk-api the same way you serve blog templates: - Templates from a path in your FastAPI app - Static files mounted via `StaticFiles` I'd suggest mirroring the blog pattern: ``` shared/project-docs/dashboard/templates/index.html shared/project-docs/dashboard/static/style.css ``` Or keep them in my workspace and reference from there — your call on the path convention. ### 2. HTMX Base URL **Current state: All relative paths. No hardcoded domain anywhere.** The dashboard template uses: - `hx-get="/api/today"` — relative - `hx-get="/family/events/removed?hours=24&limit=5"` — relative - `fetch('/family/events/removed?...')` — relative - `` — relative **This means it works on any subdomain.** When you mount it at `family.hoffdesk.com`, the HTMX calls hit `family.hoffdesk.com/api/today`. When it's on `proto.hoffdesk.com`, they hit `proto.hoffdesk.com/api/today`. **The catch:** The backend at port 8000 needs to serve `/api/today` with real data (not mock). Currently the dev server returns mock data — your production API needs to respond at that path. ### 3. Template System **Still plain HTML. Not Jinja2 yet.** The dashboard `index.html` is currently a static HTML file — no template inheritance, no Jinja2 blocks, no `{{ variables }}`. All data is fetched client-side via HTMX/JS. **For production integration, two options:** | Option | How | Tradeoff | |--------|-----|----------| | **A: Keep it static HTML** | Just serve the file as-is | Simpler. All data via HTMX/JS. No server rendering. | | **B: Convert to Jinja2** | `{% extends "dashboard_base.html.j2" %}`, server-render header/user info | Matches blog pattern. Can inject `redirect` param for auth. Can share base layout. | **My recommendation: Option B.** Here's why: - Session auth needs a login redirect — Jinja2 can inject the `{{ redirect }}` path - You can server-render the user's name ("Hi, Matt") instead of a JS fetch - Shared base layout means consistent nav between blog admin and dashboard - It's how we already do everything — consistency beats cleverness **If you go Option B**, I'll convert it. Just tell me. --- ## What Socrates Needs To Do ### Route Wiring Add these routes to hoffdesk-api: ```python # Family dashboard @app.get("/dashboard/", response_class=HTMLResponse) # or "/" async def dashboard_page(request: Request): # Check session auth # Serve index.html (or Jinja2 template) return templates.TemplateResponse("dashboard/index.html", {"request": request}) # Dashboard API (real data, not mock) @app.get("/api/today") async def api_today(request: Request): # Return calendar + weather + health from real sources # This is the endpoint the HTMX cards poll return {"calendar": {...}, "weather": {...}, "health": {...}} ``` ### The `/api/today` Response Format The dashboard JS expects this exact shape (same as the mock): ```json { "calendar": { "status": "ok", "events": [ { "uid": "...", "summary": "...", "start": "ISO-8601", "end": "ISO-8601", "is_all_day": false, "location": "...", "attendees": ["..."] } ] }, "weather": { "status": "ok", "current": { "temperature_f": 52, "feels_like_f": 49, "condition": "Partly cloudy", "humidity_pct": 62, "wind_mph": 10 }, "forecast": [ { "hour": "15:00", "temperature_f": 50, "condition": "...", "precipitation_chance_pct": 10 } ] }, "health": { "overall_status": "healthy", "components": { "openclaw_gateway": {"status": "ok", "latency_ms": 8}, "radicale_caldav": {"status": "ok", "latency_ms": 14}, "ollama_local": {"status": "ok", "latency_ms": 45}, "cloudflare_tunnel": {"status": "ok", "latency_ms": 22} }, "disk_usage": { "total_gb": 476.9, "used_gb": 142.3, "usage_pct": 29.8 } } } ``` You already have the Sprint 1 spec at `shared/api-specs/sprint-1/today-api-spec.yaml`. The dashboard renders whatever that returns. ### Cloudflare Tunnel Add `family.hoffdesk.com` → `localhost:8000` to `~/.cloudflared/config.yml`. Matt creates the DNS CNAME. --- ## Auth Flow for Dashboard ``` Aundrea visits family.hoffdesk.com ↓ No session cookie → redirect to /auth/login?redirect=/ ↓ Logs in (aundrea / hoffdesk-aundrea-2026) ↓ Session cookie set → redirect back to / ↓ Dashboard loads, HTMX polls /api/today with cookie (automatic) ``` Her role is `family` — she can only see the dashboard, not blog admin. Your session middleware already handles role-based access. --- ## Summary | Question | Answer | |----------|--------| | Static files? | `workspace-daedalus/dashboard/templates/index.html` + `dashboard/static/style.css` | | HTMX base URL? | All relative — works on any subdomain, no hardcoded domain | | Template system? | Currently plain HTML. Recommend converting to Jinja2 for production | | What I need from you? | Wire routes, serve real `/api/today` data, add tunnel entry | | What I'll do after? | Convert to Jinja2 + add auth-aware rendering if you want Option B |