This document describes the overlay expectations for /overlay/, the polling strategy, and the /state.json schema the daemon exposes. It is intended as a guide for building and using the overlay per AGENTS.md.
{
"ts": 1734160000.123,
"server": {
"schandlerid": 1,
"channel_id": 7,
"channel_name": "OBS Audio"
},
"counts": {
"approved_total": 7,
"present_approved": 2,
"present_unknown": 1,
"present_ignored": 1
},
"users": [
{
"uid": "uid1",
"nickname": "Glytcho",
"talking": true,
"approved": true,
"ignored": false,
"assets": {
"avatar_idle": "assets/users/uid1/avatar.png",
"avatar_talk": "assets/users/uid1/avatar.gif",
"frame_idle": "assets/frames/tv_idle.png",
"frame_talk": "assets/frames/tv_talk.png"
}
}
]
}users[]contains only approved and non-ignored users in the monitored channel unless a policy explicitly includes ignored users.- Sorting is pre-applied by the daemon (nickname), but overlay may re-sort.
- A missing talk asset (
null) should result in omitting the element or reusing the idle asset. - Empty
users[]is valid and should render an empty rail.
Root container and per-user elements:
<div id="tsrail-rail">
<div class="tsrail-user" data-uid="uid1">
<div class="tsrail-frame">
<img class="frame frame-idle" src="assets/frames/tv_idle.png" />
<img class="frame frame-talk" src="assets/frames/tv_talk.png" />
</div>
<div class="tsrail-avatar">
<img class="avatar avatar-idle" src="assets/users/uid1/avatar.png" />
<img class="avatar avatar-talk" src="assets/users/uid1/avatar.gif" />
</div>
<div class="tsrail-nickname">Glytcho</div>
</div>
</div>.tsrail-user: Wrapper for one user; may receivetalking/idleclasses..tsrail-frameand.tsrail-avatar: Containers for frame and avatar layers..frame-idle/.frame-talk: Idle and talk variants of the monitor frame; visibility toggled via classes on the parent..avatar-idle/.avatar-talk: Idle and talk variants of the avatar; also toggled via parent classes..tsrail-nickname: Label under the monitor.- Visibility toggling: apply
.talkingon.tsrail-userwhentalking=true; otherwise.idle. CSS should hide the opposite-state assets and drive animations (e.g.,transformand brightness for “hop and illuminate”).
- Poll
/state.jsonevery 200–500 ms. - On fetch failure, optionally back off and display a lightweight error indicator without breaking existing DOM.
- Rebuild or patch DOM from the latest payload; keep both idle and talk elements present for each user.
- When
talking=truefor a user:- Add
.talkingand remove.idleon.tsrail-user. - Show
frame-talkandavatar-talk(or reuse idle if talk assets are missing). - Trigger hop/illumination animations via CSS transitions.
- Add
- When
talking=false:- Add
.idleand remove.talking. - Show
frame-idleandavatar-idle. - Return to the steady idle pose.
- Add
- Assets resolve relative to
/assets/served by the daemon, defaulting to~/.local/share/tsrail/assets/. - Recommended layout:
assets/frames/tv_idle.pngassets/frames/tv_talk.pngassets/users/<uid>/avatar.png(idle)assets/users/<uid>/avatar.giforavatar.apng(talk)
- If a talk asset is missing or null in the JSON, reuse the idle asset.
- The overlay is transparent-friendly for OBS browser sources.
- Renders a vertical rail (e.g., left side of the canvas) with stacked frame + avatar per user and nickname labels.
- Depends entirely on
/state.jsonfor authority; no client-side voice detection.