PCO Plan 1.10.0: follow several service types in priority order, and read any of them from any dashboard

An account runs more than one service type — a Sunday morning, a Wednesday
night, a youth service, a second campus — and until now that meant one
ProdDash per type. The module now follows as many as are configured.

Admin picks them in priority order. Shell 1.12.0 gives `multiselect` an
`orderable: true` that stops being a ticklist: the picks sit in a numbered
list with a nudge either way and a remove, over a dropdown holding what is
left. Forty switches to read past is not a picker, and a ticklist cannot
carry an order anyway. The order is the setting — when two types have a plan
at the same date and time, the one higher up wins.

The server gathers candidate plans from every ticked type and tiers them:
today's plan first, then the next upcoming, then the most recent past, with
the configured order breaking a tie. A type that fails is skipped rather
than failing the read, unless it is an auth or rate-limit answer, which
belongs to the whole connection. The types not being followed are read on a
slower lane (45 s) into `state.others`, so they are there to look at without
a second connection.

Every tile's title bar carries a service-type control. Picking one moves
every PCO Plan tile on that browser's dashboard — plan, teams and song key
together — onto its plan, over a small page bus in `viewed-type.js`; the
choice is per browser and survives a reload. The control hides itself when
only one type is configured.

Live machinery stays with the type being followed: another type's plan shows
its running order with no now marker, no AbleSet and no service clock — it
shows that plan's own scheduled time instead, since borrowing the running
clock of a different service is exactly the misreading the amber top-bar
stripe exists to prevent.

Old configs carry `serviceTypeId`; it is read once into the new array and
saved, and plan pins still resolve bare, so nothing has to be re-entered.

`tools/pco-mock.js` takes `--types N` to pad the list out, for trying the
picker against an account with dozens of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-10-06 14:32:19 -04:00
co-authored by Claude Opus 5
parent f9cd292693
commit 7bb1131378
13 changed files with 800 additions and 131 deletions
+19
View File
@@ -8,6 +8,9 @@
* Serves JSON:API shapes matching api.planningcenteronline.com/services/v2:
* GET /services/v2/folders nested folders (parent rel)
* GET /services/v2/folders/{id}
* Run: node tools/pco-mock.js [port] [--types N] (--types pads the service
* type list out to N, for trying a picker against an account that has dozens.)
*
* GET /services/v2/service_types every service type, with its
* parent folder relationship
* GET /services/v2/service_types/{id}
@@ -49,6 +52,10 @@
const http = require('http');
const PORT = Number.parseInt(process.argv[2] || '24700', 10);
// --types N pads the list out to N with planless filler, for accounts that
// carry dozens of them: enough to see whether a picker still works at that size.
const TYPE_COUNT = Number.parseInt((process.argv.find((a) => a.startsWith('--types')) || '').split('=')[1]
|| (process.argv[process.argv.indexOf('--types') + 1] || ''), 10);
const bootedAt = Date.now();
/* ── data ───────────────────────────────────────────────────────────── */
@@ -68,6 +75,18 @@ const SERVICE_TYPES = [
{ id: '1005', name: 'Youth Night', frequency: 'Every 2 weeks', parentId: '' },
];
if (Number.isFinite(TYPE_COUNT) && TYPE_COUNT > SERVICE_TYPES.length) {
FOLDERS.push({ id: '300', name: 'Other Campuses', parentId: '' });
for (let i = SERVICE_TYPES.length; i < TYPE_COUNT; i += 1) {
SERVICE_TYPES.push({
id: String(2000 + i),
name: `Campus ${String.fromCharCode(65 + (i % 26))}${i >= 26 + 5 ? Math.floor(i / 26) : ''} Service`,
frequency: 'Every week',
parentId: i % 3 === 0 ? '300' : (i % 3 === 1 ? '110' : ''),
});
}
}
function iso(ms) {
return new Date(ms).toISOString();
}