From 9192eed318a12fae135f0100a767ef68fb83178f Mon Sep 17 00:00:00 2001 From: Waters Church Date: Wed, 23 Sep 2026 16:07:13 -0400 Subject: [PATCH] AbleSet 1.0.1: a remounted tile asks for the state instead of waiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every gear change remounts the tile, and the remounted tile joins the page's shared stream mid-flow, so it heard nothing until AbleSet next changed — with the transport stopped, never. Tiles went blank until a reload. The tile now fetches /state when it starts and whenever the stream (re)opens. Co-Authored-By: Claude Opus 5.5 --- modules/ableset/client.js | 35 +++++++++++++++++++++++++++-------- modules/ableset/module.json | 2 +- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/modules/ableset/client.js b/modules/ableset/client.js index 62ffd9f..c770819 100644 --- a/modules/ableset/client.js +++ b/modules/ableset/client.js @@ -647,6 +647,27 @@ export default function create({ root, moduleApi }) { return moduleApi.setStatus('ok', state.setlist.name ? `AbleSet · ${state.setlist.name}` : 'AbleSet'); } + function applyState(next) { + if (!next || typeof next !== 'object') return; + takePos(next.pos); + state = next; + reportStatus(); + render(); + } + + /* The page's one stream is shared, so a tile mounted onto it (a gear + change remounts the tile) joins mid-flow and hears nothing until + AbleSet next changes — which, stopped, may be never. Ask for the state. */ + let stopped = false; + async function backfill() { + try { + const res = await moduleApi.fetch('/state'); + if (!res.ok) return; + const body = await res.json(); + if (!stopped) applyState(body.state); + } catch { /* the stream's error handler reports the outage */ } + } + function takePos(p) { if (!p) return; pos = p; @@ -666,19 +687,15 @@ export default function create({ root, moduleApi }) { sectionsView.syncButton(); } stream = moduleApi.sse('/stream', { - open() {}, + open() { backfill(); }, error() { moduleApi.setStatus('error', 'Lost the ProdDash stream — reconnecting'); }, events: { state(e) { - try { - const next = JSON.parse(e.data); - takePos(next.pos); - state = next; - } catch { return; } - reportStatus(); - render(); + let next; + try { next = JSON.parse(e.data); } catch { return; } + applyState(next); }, pos(e) { try { takePos(JSON.parse(e.data)); } catch { return; } @@ -687,6 +704,7 @@ export default function create({ root, moduleApi }) { }, }, }); + backfill(); resizeObserver = new ResizeObserver(() => { fitAll(); if (view === 'sections' && state) sectionsView.place(); @@ -696,6 +714,7 @@ export default function create({ root, moduleApi }) { tickTimer = setInterval(() => { if (state && !moving()) impl.frame(); }, 1000); }, stop() { + stopped = true; if (stream) stream.close(); stream = null; cancelAnimationFrame(raf); diff --git a/modules/ableset/module.json b/modules/ableset/module.json index 0ed3391..f6eb6d4 100644 --- a/modules/ableset/module.json +++ b/modules/ableset/module.json @@ -1,7 +1,7 @@ { "id": "ableset", "name": "AbleSet", - "version": "1.0.0", + "version": "1.0.1", "proddash": ">=1.5.0", "description": "The song AbleSet is playing — tempo, time left, sections, setlist and status", "permissions": [