@startuml seq-dashboard !theme plain title sofarr — Dashboard SSE Stream Sequence actor User as user participant "Browser\n(app.js)" as browser participant "Express\n/api/dashboard" as dashboard participant "MemoryCache" as cache participant "Poller" as poller participant "External\nServices" as ext == SSE Connection (on login / page load) == user -> browser : Login success\nor valid session activate browser browser -> dashboard : GET /api/dashboard/stream\n(EventSource, Cookie: emby_user) activate dashboard dashboard -> dashboard : requireAuth: parse cookie\nextract username, isAdmin dashboard -> dashboard : Set headers:\nContent-Type: text/event-stream\nX-Accel-Buffering: no dashboard -> dashboard : Register in activeClients Map\n{ user, type:'sse', connectedAt } alt Polling disabled AND cache empty dashboard -> poller : pollAllServices() activate poller poller -> ext : Parallel API calls ext --> poller : Raw data poller -> cache : set poll:* keys (TTL=30s) deactivate poller end == Initial Payload (sent immediately on connect) == dashboard -> cache : get all poll:* keys dashboard -> dashboard : Build seriesMap, moviesMap,\nsonarrTagMap, radarrTagMap alt showAll=true dashboard -> cache : get('emby:users') alt cache miss dashboard -> ext : GET /Users (Emby) ext --> dashboard : [{ Name, ... }] dashboard -> cache : set('emby:users', map, 60s) end end dashboard -> dashboard : Match SABnzbd/qBit downloads\nvs Sonarr/Radarr records\nextractUserTag / buildTagBadges dashboard --> browser : data: { user, isAdmin, downloads } browser -> browser : hideLoading()\nrenderDownloads() == Pushed Updates (on every poll cycle) == loop Each poll cycle completes poller -> poller : pollAllServices() complete poller -> dashboard : onPollComplete callback fires dashboard -> cache : get all poll:* keys dashboard -> dashboard : Rebuild download payload dashboard --> browser : data: { user, isAdmin, downloads } browser -> browser : renderDownloads() (diff-based) end == Heartbeat (every 25s) == dashboard --> browser : : heartbeat note right : Keeps connection alive\nthrough idle-timeout proxies == Client Disconnects == user -> browser : Close tab / logout browser -> dashboard : TCP close (req 'close' event) dashboard -> dashboard : offPollComplete(callback)\nclearInterval(heartbeat)\ndelete activeClients[key] deactivate dashboard deactivate browser @enduml