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.
-
Restart HoffDesk uvicorn process
- The sourcemain_v2.pyalready has coach/parent mock routes commented out
- Running process is stale β restart it
-systemctl restart hoffdesk-apior kill + restart the uvicorn process
- Verify:curl /api/v1/dashboard/coach?school_id=schl_001&sport=Footballreturns proxy response instead of "Route is handled locally" -
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
Phase 2: Fix Seed Data β Link Parent to Children (30 minutes)
Goal: parent@example.com can see their children.
-
Run SQL to link parent user to athletes
- File:/home/hoffmann_admin/rtsport/backend/rtsport.db
- Userusr_parent001(parent@example.com) β link toath_001(Jake Larson) andath_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 -
Seed some events for activity feed
- Insert a fewEventrecords 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.
-
Create Messages table in SQLAlchemy models
- File:/home/hoffmann_admin/rtsport/backend/app/models.py
- New model:Messagewith fields:id,school_id,athlete_id,sender_id,sender_role,text,created_at,read_at
- Thread support viathread_idorparent_message_id -
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 -
Register router in
main.py
- File:/home/hoffmann_admin/rtsport/backend/app/main.py
- Addapp.include_router(messages_router) -
Seed some initial messages
- Insert messages matching the mock conversation data for demo purposes
Phase 5: Auth Hardening (2-3 hours)
-
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
-
Add auth guard to each dashboard
- Add early return incheckAuth(): if no token and no auto-login β redirect to/rtsport/login
- InapiRequest(): on 401 β redirect to login -
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)
-
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 -
Mark as complete in
progress.mdand roadmap
Execution Order (Recommended)
| 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" }
]
}