📄 BUG-JSON-RENDER-BRIEF.md 7,194 bytes Apr 29, 2026 📋 Raw

Bug Brief: JSON Rendering in Dashboard Cards

Status: 🔍 Identified — Ready for Daedalus fix
Severity: Medium — Cards show raw JSON instead of rendered HTML
Assignee: Daedalus 🎨


The Problem

Dashboard cards intermittently display raw JSON instead of rendered HTML content. This happens when:

  1. HTMX receives JSON but the target element's innerHTML gets the raw string instead of parsed/rendered content
  2. JavaScript renderer fails silently and falls back to showing the response text
  3. API response structure mismatches what the frontend expects

Architecture Analysis

Current Data Flow

┌─────────────┐     hx-get      ┌─────────────┐
│  Dashboard  │ ───────────────→│  /api/today │
│   (cards)   │    (JSON)       │   (FastAPI)  │
└─────────────┘                 └─────────────┘
         │                             │
         │    JSON.parse()            │
         │    + renderCalendar()      │
         │    + renderWeather()         │
         │    + renderHealth()          │
         │    + renderEvents()          │
         ↓                             │
   ┌─────────────┐                      │
   │  card-body  │ ←─── innerHTML ─────┘
   │  (content)  │
   └─────────────┘

The Issue: Mixed Patterns

The dashboard has two competing approaches:

Card Pattern API Status
Calendar HTMX + JS renderer /api/today JSON ✅ Working
Weather HTMX + JS renderer /api/today JSON ✅ Working
Health HTMX + JS renderer /api/today JSON ✅ Working
Event Graph HTMX + JS renderer /api/today JSON ⚠️ Buggy
IMAP Proxy HTMX direct HTML /api/public/imap/dashboard HTML ✅ Working

Root Cause: Event Graph Card

Problem 1: Double Targeting

The events-card and health-card both target /api/today:

<!-- Event Graph Card -->
<section hx-get="/api/today" hx-target="#events-content" ...>

<!-- Health Card -->
<section hx-get="/api/today" hx-target="#health-content" ...>

Both fire on the same endpoint, but the JavaScript only handles health-content target for events:

// In htmx:afterRequest handler:
if (targetId === 'health-content') {
  document.getElementById('health-content').innerHTML = renderHealth(data.health);
  // Also render events from the same /api/today response
  if (data.events) {
    document.getElementById('events-content').innerHTML = renderEvents(data);
  }
}

Bug: When the events-card fires its own HTMX request, the targetId is events-content, but the handler only renders events when targetId === 'health-content'. The events-content request falls through unhandled — potentially showing raw JSON.

Problem 2: Missing events Key in API Response

The /api/today endpoint returns:

{
  "calendar": {...},
  "weather": {...},
  "health": {...},
  "generated_at": "..."
}

No events key! The frontend checks if (data.events) but the backend doesn't include it. The Event Graph card tries to render from /api/today but the data isn't there.

Problem 3: Events API is Separate

The Event Graph data comes from a different endpoint:
- /api/today — calendar, weather, health
- /api/events-dashboard — Event Graph data (separate!)

But the Event Graph card is wired to /api/today:

<section hx-get="/api/today" hx-target="#events-content" ...>

It should be wired to /api/events-dashboard.


Fix Required

Backend change — Add events to /api/today:

@router.get("/api/today")
async def api_today(request: Request):
    # ... existing code ...
    events_data = await _get_event_graph()

    return {
        "timestamp": datetime.now().isoformat(),
        "calendar": calendar_data,
        "weather": weather_data,
        "health": health_data,
        "events": events_data,  # ← ADD THIS
        "meta": {...}
    }

Frontend change — Fix the HTMX handler:

document.body.addEventListener('htmx:afterRequest', function(event) {
  if (event.detail.failed) return;
  try {
    const data = JSON.parse(event.detail.xhr.responseText);
    const targetId = event.detail.target.id;

    // Handle each card independently
    if (targetId === 'calendar-content' || targetId === 'events-content' || 
        targetId === 'weather-content' || targetId === 'health-content') {
      // All cards use /api/today, parse once and render all
      if (data.calendar) {
        document.getElementById('calendar-content').innerHTML = renderCalendar(data.calendar);
      }
      if (data.weather) {
        document.getElementById('weather-content').innerHTML = renderWeather(data.weather);
      }
      if (data.health) {
        document.getElementById('health-content').innerHTML = renderHealth(data.health);
      }
      if (data.events) {
        document.getElementById('events-content').innerHTML = renderEvents(data);
      }
    }

    // Update footer
    const refreshEl = document.getElementById('footer-refresh');
    if (refreshEl) refreshEl.textContent = 'Updated ' + new Date().toLocaleTimeString(...);
  } catch(e) { console.error('Parse error:', e); }
});

Option B: Separate Endpoints (Current Design, Fix Wiring)

Change Event Graph card to use its correct endpoint:

<!-- BEFORE -->
<section hx-get="/api/today" hx-target="#events-content" ...>

<!-- AFTER -->
<section hx-get="/api/events-dashboard" hx-target="#events-content" ...>

Add a separate handler for events:

document.body.addEventListener('htmx:afterRequest', function(event) {
  // ... existing handlers ...

  if (targetId === 'events-content') {
    document.getElementById('events-content').innerHTML = renderEvents(data);
  }
});

Files to Modify

File Path Change
Dashboard template shared/project-docs/dashboard/templates/index.html Fix HTMX endpoints and JS handlers
Dashboard router workspace-socrates/hoffdesk-api/dashboard/router.py Add events to /api/today response

Verification Steps

  1. Load dashboard at http://localhost:8001/
  2. Check each card renders HTML (not JSON)
  3. Verify Event Graph shows events or "No upcoming events"
  4. Verify Calendar shows "Nothing scheduled today 🎉" or events
  5. Verify Weather shows temperature and forecast
  6. Verify Health shows system status
  7. Verify IMAP shows connection status

  • CSS polishshared/project-docs/dashboard/CSS-POLISH-DELIVERY.md
  • System State handoffshared/project-docs/dashboard/SYSTEM-STATE-HANDOFF.md
  • Jinja2 handoffshared/project-docs/dashboard/JINJA2-HANDOFF.md

Prepared by Wadsworth 📋 | 2026-04-29 01:53 UTC