πŸ“„ TRACK1-IMPLEMENTATION.md 18,331 bytes Today 03:53 πŸ“‹ Raw

Track 1: Wire Frontend to Real API β€” Implementation Plan

2026-05-09 | Daedalus subagent (planning only, no execution)


Current State Assessment

Network Architecture

User Browser (HTTPS)
        β”‚
        β–Ό
  hoffdesk.com:443 (Cloudflare)
        β”‚
        β–Ό
  Port 8000 (HoffDesk main_v2.py β€” uvicorn)
        β”‚
        β”œβ”€β”€ /rtsport/* β†’ rtsport_mock.py (serves static HTML/CSS/JS)
        β”œβ”€β”€ /api/v1/dashboard/coach/* β†’ rtsport_coach_api.py (IN-MEMORY MOCK) 🟑
        β”œβ”€β”€ /api/v1/dashboard/parent/* β†’ rtsport_parent_api.py (IN-MEMORY MOCK) 🟑
        β”œβ”€β”€ /api/v1/messages/* β†’ rtsport_coach_api.py or parent_api.py (IN-MEMORY MOCK) 🟑
        └── /api/v1/{other} β†’ rtsport_api_proxy.py β†’ port 8001 βœ…
                                     β”‚
                                     β–Ό
                              Port 8001 (RTSport API β€” real SQLite DB, JWT, role guards)

Key issue: The running server at port 8000 still has mock coach/parent routes loaded. The source main_v2.py has them commented out, but the process hasn't been restarted since the change.

Data Flow Discrepancies

Coach Dashboard

Field Mock API (rtsport_coach_api.py) Real API (dashboard.py on 8001) Frontend Expects
status_summary {out, modified, cleared} with total_athletes N/A β€” uses stats key data.status_summary.out/.modified/.cleared
injury_breakdown [{body_part, count}] sorted desc N/A β€” no equivalent endpoint data.injury_breakdown[].body_part/.count
recovery_timeline [{athlete_name, status, phase, days_remaining}] N/A β€” no equivalent endpoint data.recovery_timeline[].athlete_name/.status/.phase/.days_remaining
teams N/A {Varsity: [{id, name, grade, position, current_status, status_label, case_title}]} Not used (roster tab pulls from separate /roster endpoint)
recent_activity 5 hardcoded entries with type, athlete_name, from, to, timestamp Built from Event table, different schema data.recent_activity[].type/.athlete_name/.from/.to/.timestamp
sport String String (same) Used in header

Bottom line: The Coach frontend renders a different data structure than what the real API provides. The real API's /dashboard/coach endpoint returns stats + teams (roster organized by team), but the frontend needs status_summary + injury_breakdown + recovery_timeline.

Parent Dashboard

Field Mock API Real API Frontend Expects
athlete {id, first_name, last_name, sport, team, grade, jersey_number, current_status} {id, first_name, last_name, sport, team, grade, initials} data.athlete.id/.first_name/.last_name
case Has restrictions[], next_appointment, care_team[], milestones[], phases Has restrictions, next_appointment, care_team, milestones, timeline β€” DIFFERENT FIELD NAMES data.case.restrictions, data.case.next_appointment, data.case.care_team, data.case.phase_label
activity_feed [{type, title, date, note}] recent_activity: [{type, text, time_ago}] data.activity_feed[].type/.title/.date/.note
/parent/children Hardcoded PARENT_CHILDREN list Queries DB by parent_ids data.children[].id/.first_name/.last_name/.sport

Messages

Endpoint Mock Real API
GET /messages/coach Per-athlete conversations from in-memory dict DOES NOT EXIST β€” no messages table
GET /messages/coach/{id} Message threads DOES NOT EXIST
POST /messages/coach/{id} Creates in-memory message DOES NOT EXIST
GET /messages/parent Per-contact conversations DOES NOT EXIST
GET /messages/parent/{id} Message threads by contact DOES NOT EXIST
POST /messages/parent/{id} Creates + mirrors to coach DOES NOT EXIST

Auth Gaps

Issue Detail
Auto-login Hardcoded creds in each dashboard HTML (coach@preble.k12.wi.us, parent@example.com, both with password testpass123)
Auth guard Missing β€” direct dashboard access shows errors instead of redirecting to login
Mock token validation Mock APIs accept any non-empty token; real API validates JWT properly
Session expiry Frontend doesn't handle 401 redirect properly

Seed Data Gaps

Issue Detail Impact
Parent-child link parent_ids is empty for all athletes Parent dashboard returns empty children list / 403
No recent events events table empty (or no parent-visible events) Parent/Coach dashboards show empty activity

Implementation Plan

Phase 1: Enable Proxy Routing (10 minutes)

Goal: Get the running server to forward coach/parent dashboard requests to the real API.

  1. Restart HoffDesk uvicorn process
    - The source main_v2.py already has coach/parent mock routes commented out
    - Running process is stale β€” restart it
    - systemctl restart hoffdesk-api or kill + restart the uvicorn process
    - Verify: curl /api/v1/dashboard/coach?school_id=schl_001&sport=Football returns proxy response instead of "Route is handled locally"

  2. Update proxy file docstring (optional, not blocking)
    - File: /home/hoffmann_admin/.openclaw/workspace-socrates/hoffdesk-api/rtsport_api_proxy.py
    - Remove the outdated comment about coach endpoints being excluded

Goal: parent@example.com can see their children.

  1. Run SQL to link parent user to athletes
    - File: /home/hoffmann_admin/rtsport/backend/rtsport.db
    - User usr_parent001 (parent@example.com) β†’ link to ath_001 (Jake Larson) and ath_007 (Emma Larson)
    - SQL: UPDATE athletes SET parent_ids = '["usr_parent001"]' WHERE id IN ('ath_001', 'ath_007');
    - Verify: Parent login β†’ calls /dashboard/parent/children β†’ returns 2 children

  2. Seed some events for activity feed
    - Insert a few Event records for case_001 with "parent" visibility so the activity feed shows content

Phase 3: Update Frontend JS to Match Real API Shapes (4-6 hours)

Goal: Both Coach and Parent dashboards render data from the real API.

3a: Coach Dashboard Status Tab

File: /home/hoffmann_admin/.openclaw/shared/current/frontend/static/js/dashboard-tabs.js

Changes needed in renderStatusData() function:

Replace the mock-expected data structure handling with real API data mapping:

// Real API returns: { sport, stats: {out, modified, cleared}, teams, recent_activity }
function renderStatusData(data, container) {
  if (!data || !data.stats) {
    renderStatusEmpty(container);
    return;
  }

  const { sport, stats, teams, recent_activity } = data;

  // Stats β†’ status_pills (already uses status_summary.{out,modified,cleared})
  // Change: data.status_summary β†’ data.stats

  // Teams β†’ Compute injury_breakdown and recovery_timeline from teams
  // The real API doesn't have injury_breakdown or recovery_timeline as separate fields
  // We need to EITHER:
  //   Option A: Add injury_breakdown + recovery_timeline to the real API
  //   Option B: Compute them client-side from the teams/athlete data
  //   Option C: Create new real API endpoints that match what the frontend expects

  // recent_activity: Real API returns ActivityItem[{type, text, time_ago}]
  // Mock returned: [{type, athlete_name, from, to, timestamp}]
  // The renderer expects: act.type, act.athlete_name, act.from, act.to, act.timestamp
  // This needs to be updated to handle the new shape
}

Three approaches for the shape mismatch:

Option A (Recommended β€” Server-side): Add injury_breakdown and recovery_timeline to the real /dashboard/coach response.
- File: /home/hoffmann_admin/rtsport/backend/app/api/dashboard.py
- Add computation of injury_breakdown (aggregate body parts from active cases) and recovery_timeline (athletes with active cases + phase + estimated days) to the coach dashboard endpoint
- Add to the CoachDashboardResponse Pydantic model
- This preserves the frontend JS with minimal changes

Option B (Client-side only): Map teams[] athletes into the shapes the renderer expects. More complex JS but no backend changes. Riskier due to needing to compute phases from milestones client-side.

Option C (New endpoint): Create /dashboard/coach/status on the real API that exactly matches the mock shape.

Recommendation: Option A β€” server-side change, 30-50 lines of Python, zero JS changes needed for the status section.

3b: Parent Dashboard

File: /home/hoffmann_admin/.openclaw/shared/current/frontend/static/js/parent-tabs.js

Changes needed:

The mock returns:

data.case.restrictions          // array of {text: "..."}
data.case.next_appointment      // {date, title, provider}
data.case.care_team             // [{name, role, initials, email}]
data.case.phase_label           // "Active Rehab"
data.case.phase_number          // 2
data.case.total_phases          // 4
data.case.milestones            // [{label, status, completed_date, target_date}]
data.activity_feed              // [{type, title, date, note}]

The real API returns:

data.case.restrictions          // [{event_id, text, added_at, timestamp}] β€” different shape
data.case.next_appointment      // {date, title, provider} β€” same shape βœ…
data.case.care_team             // [{name, role}] β€” no initials/email ❌
data.case.phase_label           // "Active Rehab" β€” same βœ…
data.case.phase_number          // computed β€” same βœ…
data.case.total_phases          // computed β€” same βœ…
data.case.milestones            // [{id, title, status, target_date, achieved_at}] β€” no label field ❌
data.recent_activity            // [{type, text, time_ago}] β€” different from activity_feed

Changes:
1. renderRestrictionsCard(data.case) β€” Map restriction.text or compute from restriction shape
2. renderCareTeam(data.case) β€” Care team no longer has initials or email; update renderer to generate initials from name
3. Activity feed — Change from data.activity_feed to data.recent_activity with new field mapping (title→text, note→text)
4. Add initials and email to the real API's ParentCareTeamMember schema (or compute initials client-side)

3c: AT Dashboard

Check AT dashboard response shape vs frontend expectations:
- Real API returns: {stats: {full_count, modified_count, out_count}, active_cases: [{...}], recent_activity: [{...}]}
- Frontend (at/dashboard.html): needs verification β€” likely uses different endpoint path

3d: AD Dashboard

Check AD dashboard shape:
- Real API returns: {school_id, total_athletes, active_cases, out_today, modified, sports, recent_activity}
- Frontend needs: verify against mock expectations

Phase 4: Add Messages to Real API (1-2 days)

Goal: Messages persist in the database instead of in-memory dicts.

  1. Create Messages table in SQLAlchemy models
    - File: /home/hoffmann_admin/rtsport/backend/app/models.py
    - New model: Message with fields: id, school_id, athlete_id, sender_id, sender_role, text, created_at, read_at
    - Thread support via thread_id or parent_message_id

  2. Create Messages API endpoints
    - File: /home/hoffmann_admin/rtsport/backend/app/api/messages.py (new file)
    - GET /api/v1/messages/coach?school_id=XXX&sport=YYY β€” conversations per athlete
    - GET /api/v1/messages/coach/{athlete_id} β€” thread messages
    - POST /api/v1/messages/coach/{athlete_id} β€” send message
    - GET /api/v1/messages/parent?athlete_id=XXX β€” parent conversations
    - GET /api/v1/messages/parent/{contact_id} β€” parent thread
    - POST /api/v1/messages/parent/{contact_id} β€” send parent message

  3. Register router in main.py
    - File: /home/hoffmann_admin/rtsport/backend/app/main.py
    - Add app.include_router(messages_router)

  4. Seed some initial messages
    - Insert messages matching the mock conversation data for demo purposes

Phase 5: Auth Hardening (2-3 hours)

  1. Remove auto-login from all dashboard HTML files
    - Files:

    • /home/hoffmann_admin/.openclaw/shared/current/frontend/templates/coach/dashboard.html
    • /home/hoffmann_admin/.openclaw/shared/current/frontend/templates/parent/dashboard.html
    • /home/hoffmann_admin/.openclaw/shared/current/frontend/templates/at/dashboard.html
    • /home/hoffmann_admin/.openclaw/shared/current/frontend/templates/ad/dashboard.html
    • Remove the checkAuth() auto-login block that hardcodes credentials
    • Keep the getAuthToken() β†’ apiRequest() flow
  2. Add auth guard to each dashboard
    - Add early return in checkAuth(): if no token and no auto-login β†’ redirect to /rtsport/login
    - In apiRequest(): on 401 β†’ redirect to login

  3. Login flow should work end-to-end
    - Login form posts to real API β†’ receives JWT β†’ stores in localStorage β†’ redirects to role-specific dashboard

Phase 6: Clean Up Mock Files (after everything works)

  1. Delete mock API files (or keep as reference)
    - /home/hoffmann_admin/.openclaw/workspace-socrates/hoffdesk-api/rtsport_coach_api.py
    - /home/hoffmann_admin/.openclaw/workspace-socrates/hoffdesk-api/rtsport_parent_api.py
    - The mock JSON data files in /static/data/ if no longer needed

  2. Mark as complete in progress.md and roadmap


Step What Who Effort Depends On
1 Restart HoffDesk proxy Socrates/Wadsworth 5 min β€”
2 Link parent to children in DB Socrates 15 min β€”
3 Add injury_breakdown + recovery_timeline to real API Socrates 1-2 hrs β€”
4 Update frontend coach dashboard JS Daedalus 2-3 hrs Step 3
5 Update frontend parent dashboard JS Daedalus 2-3 hrs Step 2
6 Create Messages table + endpoints Socrates 1-2 days β€”
7 Update frontend message JS Daedalus 2-4 hrs Step 6
8 Auth guard + remove auto-login Daedalus 2-3 hrs β€”
9 Clean up mock files Either 30 min Steps 1-8 working
10 End-to-end verification Both 2-4 hrs Steps 1-9

Estimated total effort: 5-8 days (not continuous; actual work time)


File Change Summary

Backend (Socrates)

File Change Priority
rtsport/backend/rtsport.db SQL UPDATE to link parentβ†’children πŸ”΄ Phase 2
rtsport/backend/app/api/dashboard.py Add injury_breakdown + recovery_timeline to coach response πŸ”΄ Phase 3
rtsport/backend/app/models.py Add Message model 🟑 Phase 4
rtsport/backend/app/api/messages.py (new) Message CRUD endpoints 🟑 Phase 4
rtsport/backend/app/main.py Register messages router 🟑 Phase 4

Proxy Layer (Socrates)

File Change Priority
hoffdesk-api/main_v2.py Already done (coach/parent commented out) β€” just restart πŸ”΄ Phase 1
hoffdesk-api/rtsport_api_proxy.py Update docstring 🟒 Cleanup

Frontend (Daedalus)

File Change Priority
shared/current/frontend/static/js/dashboard-tabs.js renderStatusData() β€” adapt for real API shape πŸ”΄ Phase 3
shared/current/frontend/static/js/parent-tabs.js Adapt for real API shapes (restrictions, care_team, activity_feed) πŸ”΄ Phase 3
shared/current/frontend/static/js/mock-data.js Evaluate if still needed 🟒 Phase 6
shared/current/frontend/templates/coach/dashboard.html Remove auto-login, add auth guard 🟑 Phase 5
shared/current/frontend/templates/parent/dashboard.html Remove auto-login, add auth guard 🟑 Phase 5
shared/current/frontend/templates/at/dashboard.html Remove auto-login, check API shape 🟑 Phase 3/5
shared/current/frontend/templates/ad/dashboard.html Remove auto-login, check API shape 🟑 Phase 3/5

Config

File Change Priority
Systemd Restart hoffdesk-api.service πŸ”΄ Phase 1

Appendix: Real API Response Shapes (for reference)

GET /api/v1/dashboard/coach?school_id=schl_001&sport=Football

{
  "sport": "Football",
  "stats": { "out": 3, "modified": 0, "cleared": 1 },
  "teams": {
    "Varsity": [
      { "id": "ath_001", "name": "Jake Larson", "grade": 12, "position": null,
        "current_status": "out", "status_label": "Out",
        "case_title": "Right ankle sprain", "avatar_color": "amber" }
    ],
    "JV": [
      { "id": "ath_005", "name": "Cameron Davis", "grade": 10, "position": null,
        "current_status": "out", "status_label": "Out",
        "case_title": "Hamstring strain β€” right", "avatar_color": "blue" }
    ],
    "Freshman": []
  },
  "recent_activity": []
}

GET /api/v1/dashboard/parent?school_id=schl_001&athlete_id=ath_001

{
  "athlete": { "id": "ath_001", "first_name": "Jake", "last_name": "Larson",
    "sport": "Football", "team": "Varsity", "grade": 12, "initials": "JL" },
  "case": {
    "id": "case_001", "injury": "Right ankle sprain",
    "severity": "moderate", "attention": "stable", "status": "active",
    "phase_label": "Active Rehab", "total_phases": 4,
    "milestones": [
      { "id": "mil_1", "title": "Initial Assessment", "status": "achieved",
        "target_date": "2026-04-17", "achieved_at": "2026-04-17" }
    ],
    "restrictions": [],
    "next_appointment": null,
    "care_team": [],
    "recent_activity": []
  },
  "restrictions": [],
  "next_appointment": null,
  "care_team": [],
  "recent_activity": []
}

GET /api/v1/dashboard/parent/children?school_id=schl_001

{
  "children": [
    { "id": "ath_001", "first_name": "Jake", "last_name": "Larson",
      "sport": "Football", "team": "Varsity", "grade": 12,
      "has_active_case": true, "current_status": "out",
      "initials": "JL", "avatar_color": "teal" }
  ]
}