/* ── the queue window, across the bridge ─────────────────── Round forty-six put a bounded window of the queue into every card update — the current item and ten either side, each with its id, title, artist, album, duration and art key, plus where the current one sits among them — and left MediaService ignoring every bit of it, because media3's SimpleBasePlayer.getState() will want a playlist and an index, and that data should be a round old before anything depends on it. Which means every check above this one is blind to it: a window that arrived empty, an index off by one, or a field the bridge dropped because a key was spelled the wrong way would each look exactly like success. So: what the phone SENT — __ss.card, the mirror media.svelte.js keeps beside xfernote's — against what the plugin LOGGED on the far side, field for field, through a next and a previous. Field for field is the point: a camelCase key spelled snake_case arrives as nothing at all on both hops of this bridge, with no error anywhere, so `art` (the only camelCase field an entry has) is compared as well as the size and the index. ARMED TO A KNOWN ROW, index one, rather than to wherever the walk left off. Three things fall out of that and every one of them is load-bearing: there is a row on either side, so next() steps instead of stopping at the end and prev() steps instead of seeking to zero at the top; the track starts at zero, so prev() is still a step two and a half seconds later rather than the seek it becomes past five, and no track ends underneath the three readings; and while the queue index is under ten the window's own index IS the queue index, because the slice starts at zero and nothing is clamped off the front — which is what makes `got.i` comparable to `base` below without recomputing the arithmetic here. Nothing is added, removed or reordered: this is the queue the walk has been playing all along, stepped one row forward and one row back inside itself. Not skipped when it cannot be armed: a queue shorter than three rows, or repeat-one — under which next() seeks to zero instead of stepping — FAILS, with `armed` in the message saying which. A check that passes by not running is the thing this walk is most careful never to be. */ const CARD = `(function () { var s = window.__ss; if (!s || !s.card || !s.player) return null; var c = s.card, p = s.player; return { u: c.updates, n: c.window, i: c.index, art: c.art, at: c.at, qi: p.qi, title: p.track ? p.track.title : '' }; })()`; const armed = await cdp.evaluate(`(function () { var p = window.__ss && __ss.player; return p ? { n: p.queue.length, qi: p.qi, rep: p.repeat } : null; })()`); const base = 1; const room = (armed?.n ?? 0) >= 3 && armed.rep !== 2; const wins = []; if (room) { await cdp.evaluate(`__ss.playQueueAt(${base})`); await sleep(2500); wins.push(["here", await cdp.evaluate(CARD), mediaWindow()]); await cdp.evaluate(`__ss.next()`); await sleep(2500); wins.push(["next", await cdp.evaluate(CARD), mediaWindow()]); await cdp.evaluate(`__ss.prev()`); await sleep(2500); wins.push(["prev", await cdp.evaluate(CARD), mediaWindow()]); } /* each reading: the window SENT is a real one — at least two entries, the index inside it, and the entry AT the index is the track on the bar, which is what an off-by-one fails even when the counts agree — and what the plugin LOGGED is that same window, field for field. */ const sane = ([, sent, got]) => !!sent && sent.n >= 2 && sent.i >= 0 && sent.i < sent.n && !!sent.at && sent.at === sent.title && got.n === sent.n && got.i === sent.i && got.art === sent.art && got.at === String(sent.at).trim(); const winOk = room && wins.length === 3 && wins.every(sane) /* and it MOVED. The queue index steps forward and back; the LOGGED window index steps with it, which is the assertion an off-by-one or a frozen index dies on. And each reading is a FRESH one at both ends — the phone pushed again (`u`) and the plugin logged again (`lines`) — because a prev() that lands back on the row it started from reads identically to a stale line otherwise. */ && wins[0][1].qi === base && wins[1][1].qi === base + 1 && wins[2][1].qi === base && wins[0][2].i === base && wins[1][2].i === base + 1 && wins[2][2].i === base && wins[1][1].u > wins[0][1].u && wins[2][1].u > wins[1][1].u && wins[1][2].lines > wins[0][2].lines && wins[2][2].lines > wins[1][2].lines; if (!winOk) problems.push("EXPECT queue-window " + JSON.stringify({ armed, base, wins })); console.log((winOk ? " ok " : " FAIL ") + "queue-window".padEnd(16) + " " + JSON.stringify({ armed, seen: wins.map(([k, sent, got]) => [k, sent && sent.qi, got.n, got.i, got.art, got.at]) }));