// Multi-neighbor lazy-load UI: a per-neighbor progress list with a
// "cancel the slow one" button that appears once a single straggler is
// left running.
//
// State maintained for the duration of one fetchNeighborsLazy call:
// progressMap : did -> { handle, status, message }
// order : [did, ...] preserves rank order so the list is stable
// controllers : did -> AbortController (one per neighbor)
// stragglerTimer: timeout id; set when only one neighbor remains,
// shows the cancel button after STRAGGLER_CANCEL_DELAY_MS.
const STRAGGLER_CANCEL_DELAY_MS = 15000;
const COMPACT_LIST_THRESHOLD = 5; // collapse terminal rows once list exceeds this
function renderNeighborProgressList(progressMap, order) {
const el = $("stage-progress-list");
if (!el) return;
el.hidden = false;
// Once we have more than a handful of neighbors, completed
// (done/cancelled/failed) rows are removed entirely — the list stays
// focused on whatever is actively fetching plus a small summary tail.
const compact = order.length > COMPACT_LIST_THRESHOLD;
const isTerminal = (s) => s === "done" || s === "cancelled" || s === "failed";
const visible = compact
? order.filter((did) => {
const e = progressMap.get(did);
return e && !isTerminal(e.status);
})
: order;
const parts = [];
if (compact) {
let doneN = 0, cancelN = 0, failN = 0;
for (const did of order) {
const s = progressMap.get(did)?.status;
if (s === "done") doneN++;
else if (s === "cancelled") cancelN++;
else if (s === "failed") failN++;
}
const total = order.length;
const finished = doneN + cancelN + failN;
if (finished) {
const bits = [];
if (doneN) bits.push(`${doneN} done`);
if (cancelN) bits.push(`${cancelN} cancelled`);
if (failN) bits.push(`${failN} failed`);
parts.push(
`