Timers: no "Other modules" catch-all in admin

The Timers admin had a fourth group, "Other modules", whose one switch gated
every timer provider that wasn't PCO Plan. A provider that earns a switch
earns a section of its own — so the catch-all goes, any other provider found
is consumed as soon as it appears, and its name (not "Other modules") is what
the health readout lists it under.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-14 10:51:08 -04:00
co-authored by Claude Fable 5.1
parent 361b8bdaa8
commit f0fbf8a8f6
3 changed files with 26 additions and 28 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "start",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"start"
],
"port": 3000
}
]
}
-10
View File
@@ -26,10 +26,6 @@
"PCO Plan": { "PCO Plan": {
"toggle": "pcoEnabled", "toggle": "pcoEnabled",
"help": "Timers the PCO Plan module offers — service elapsed, current item, countdown to service — appear here under a PCO Plan heading when that module is installed and configured. Nothing to set up on this side." "help": "Timers the PCO Plan module offers — service elapsed, current item, countdown to service — appear here under a PCO Plan heading when that module is installed and configured. Nothing to set up on this side."
},
"Other modules": {
"toggle": "moduleTimersEnabled",
"help": "Any other installed module that offers timers (\"provides\": [\"timers\"] in its manifest) is found automatically and listed under its own heading. Nothing to configure here."
} }
}, },
"configSchema": { "configSchema": {
@@ -80,12 +76,6 @@
"group": "PCO Plan", "group": "PCO Plan",
"default": true "default": true
}, },
"moduleTimersEnabled": {
"type": "switch",
"label": "Module timers",
"group": "Other modules",
"default": true
},
"timerFont": { "timerFont": {
"type": "select", "type": "select",
"label": "Timer font", "label": "Timer font",
+12 -18
View File
@@ -29,8 +29,9 @@
* read through its /timers/stream when that answers as SSE, * read through its /timers/stream when that answers as SSE,
* else its /timers polled every 2 s. Re-discovered every 30 s * else its /timers polled every 2 s. Re-discovered every 30 s
* so a newly installed provider appears without a restart. * so a newly installed provider appears without a restart.
* PCO Plan sits behind pcoEnabled, everything else behind * PCO Plan sits behind the admin pcoEnabled switch; any other
* moduleTimersEnabled. * provider is consumed as soon as it is found (give it a
* switch of its own in module.json the day one needs it).
* *
* ADDING A SOURCE (another timer platform) is a dozen lines. Write a factory * ADDING A SOURCE (another timer platform) is a dozen lines. Write a factory
* that returns * that returns
@@ -84,7 +85,7 @@ const { createLtcListener } = require('./ltc-listener');
/** This module's id — left out of provider discovery (we don't consume ourselves). */ /** This module's id — left out of provider discovery (we don't consume ourselves). */
const SELF_ID = 'propresenter-timers'; const SELF_ID = 'propresenter-timers';
/** The provider the admin "PCO Plan" switch governs; every other provider follows "Other modules". */ /** The provider the admin "PCO Plan" switch governs; every other provider is always on. */
const PCO_ID = 'pco-plan'; const PCO_ID = 'pco-plan';
/* ProPresenter cadence. */ /* ProPresenter cadence. */
@@ -93,7 +94,7 @@ const RETRY_MS = 2500;
const SLOW_EVERY = 4; const SLOW_EVERY = 4;
const REQUEST_TIMEOUT_MS = 4000; const REQUEST_TIMEOUT_MS = 4000;
/* Other modules' timers. */ /* Timers offered by other modules. */
const DISCOVER_MS = 30000; // re-read /api/modules: a newly installed provider shows up without a restart const DISCOVER_MS = 30000; // re-read /api/modules: a newly installed provider shows up without a restart
const DISCOVER_RETRY_MS = 5000; // …sooner while the shell isn't answering yet (boot) const DISCOVER_RETRY_MS = 5000; // …sooner while the shell isn't answering yet (boot)
const DISCOVER_FIRST_MS = 1500; // init() runs before the shell listens const DISCOVER_FIRST_MS = 1500; // init() runs before the shell listens
@@ -770,7 +771,7 @@ function createModuleDiscovery({ shellPort, log, flags, onAdd, onRemove }) {
let listError = ''; let listError = '';
function wanted(id) { function wanted(id) {
return id === PCO_ID ? flags.pco : flags.modules; return id === PCO_ID ? flags.pco : true;
} }
async function discover() { async function discover() {
@@ -824,7 +825,6 @@ function createModuleDiscovery({ shellPort, log, flags, onAdd, onRemove }) {
return { return {
start() { start() {
if (!flags.pco && !flags.modules) return;
timer = setTimeout(discover, DISCOVER_FIRST_MS); timer = setTimeout(discover, DISCOVER_FIRST_MS);
timer.unref?.(); timer.unref?.();
}, },
@@ -844,8 +844,6 @@ function createModuleDiscovery({ shellPort, log, flags, onAdd, onRemove }) {
else if (s === 'connecting' && status === 'ok') status = 'connecting'; else if (s === 'connecting' && status === 'ok') status = 'connecting';
}; };
if (!flags.pco) parts.push('PCO Plan: off'); if (!flags.pco) parts.push('PCO Plan: off');
if (!flags.modules) parts.push('Other modules: off');
if (!flags.pco && !flags.modules) return { status, parts };
if (listed === null) { if (listed === null) {
parts.push(listError ? `Modules: waiting for the shell (${listError})` : 'Modules: discovering…'); parts.push(listError ? `Modules: waiting for the shell (${listError})` : 'Modules: discovering…');
return { status: 'connecting', parts }; return { status: 'connecting', parts };
@@ -864,14 +862,11 @@ function createModuleDiscovery({ shellPort, log, flags, onAdd, onRemove }) {
parts.push('PCO Plan: not installed, or not offering timers yet'); parts.push('PCO Plan: not installed, or not offering timers yet');
} }
} }
if (flags.modules) { // Every other provider found is listed under its own name.
const others = [...sources.values()].filter((s) => s.id !== PCO_ID); for (const s of [...sources.values()].filter((x) => x.id !== PCO_ID)) {
if (!others.length) parts.push('Other modules: none offering timers'); const h = s.health();
for (const s of others) { parts.push(`${s.label}: ${h.message}`);
const h = s.health(); bump(h.status);
parts.push(`${s.label}: ${h.message}`);
bump(h.status);
}
} }
return { status, parts }; return { status, parts };
}, },
@@ -899,7 +894,6 @@ module.exports = {
pp: config.ppEnabled !== false, pp: config.ppEnabled !== false,
ltc: Boolean(config.ltcEnabled), ltc: Boolean(config.ltcEnabled),
pco: config.pcoEnabled !== false, pco: config.pcoEnabled !== false,
modules: config.moduleTimersEnabled !== false,
}; };
const tileStreams = new Set(); const tileStreams = new Set();
@@ -1006,7 +1000,7 @@ module.exports = {
} }
} }
discovery.start(); discovery.start();
if (!flags.pco && !flags.modules) log('module timers are off (admin switches)'); if (!flags.pco) log('PCO Plan timers are off (admin switch)');
const heartbeat = setInterval(() => { const heartbeat = setInterval(() => {
writeAll(tileStreams, ': ping\n\n'); writeAll(tileStreams, ': ping\n\n');