Skip to content

fix: a show UI that falls behind the state feed skips frames instead of freezing - #121

Merged
pyramation merged 1 commit into
mainfrom
fix/embedded-animation-throttling
Aug 19, 2026
Merged

fix: a show UI that falls behind the state feed skips frames instead of freezing#121
pyramation merged 1 commit into
mainfrom
fix/embedded-animation-throttling

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Chasing "the inner show UI stopped updating while the rig and the phone kept running, and only quitting the app fixed it". #114 removed one cause (a throwing send aborting the whole broadcast loop). This is the other one it left: the state feed had no per-client bound.

broadcastState() runs at 60fps for every client. ws.send buffers rather than blocks, so a client that cannot drain that fast — an embedded page the compositor isn't painting, a busy renderer — accumulates frames in its socket. It then renders state that is seconds old while messages keep arriving, so nothing looks wrong: the socket is OPEN, the stale-feed watchdog never trips, no reconnect happens, and there is no recovery short of a new connection. Exactly the reported symptom, and exactly why the outer Nova panel was unaffected — it never went through this feed.

Server. State frames are superseded by the next one, so they are now sent lossily:

export function fanoutLossy(sockets, payload, onFailure, limitBytes = FEED_BACKLOG_LIMIT_BYTES) {
  for (const socket of sockets) {
    if (socket.readyState !== OPEN_READY_STATE) continue;
    if ((socket.bufferedAmount ?? 0) > limitBytes) continue;  // skip, don't queue
    ...

Only broadcastState uses it. Commands, orientation, playlist and config-sync stay on reliable fanout — those are events, not a feed, and must not be dropped.

Client. A hidden page can't paint the feed and its timers are throttled, so it was the most reliable way to build that backlog. useSocket now closes on visibilitychange → hidden and takes a fresh connection on return (suspended distinguishes that from a break, so onclose doesn't schedule a reconnect into a hidden page). Returning to the show route therefore shows the present, not a queue.

Honest scope

This is a mechanism that provably produces the symptom, fixed from code reading — not a captured reproduction of the incident. Whether the embedded WebContentsView reports visibilityState: 'hidden' when the desktop shell hides it is the one assumption worth confirming on the show machine; if it doesn't, the server-side half still bounds the damage.

Link to Devin session: https://app.devin.ai/sessions/ec43152136134467a853ec0bbf783ea9
Requested by: @pyramation

…eezing

The show UI renders whatever the brain's 60fps state feed hands it. Nothing
bounded that feed per client: a client that could not keep up (or a page the
compositor was not painting) accumulated frames in its socket, so it rendered
seconds-old state on a connection that looked perfectly healthy — no stale-feed
watchdog, no reconnect, nothing but quitting the app to fix it. That is the
inner show UI going static while the rig, and every other client, ran on.

Two halves: state frames now go out lossily (a client past the backlog limit
skips the frame, since the next one supersedes it), and a hidden page drops its
socket and takes a fresh one on return rather than queueing a feed nobody can
see.
@pyramation pyramation self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit f187c22 into main Aug 19, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant