From 071a18e4a4e49a49a6801c25d31d36e2dccbf192 Mon Sep 17 00:00:00 2001 From: TopherMayor Date: Thu, 30 Apr 2026 12:03:35 -0700 Subject: [PATCH] fix: keep source selector stable --- public/index.html | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index 5513d66..21a3362 100644 --- a/public/index.html +++ b/public/index.html @@ -1481,6 +1481,7 @@ let priceRefreshTimer = null; let pendingVoteOptionId = null; let pendingVoteRemove = false; + let pendingStableOptionOrder = null; // ── Init ─────────────────────────────────────────────────── function init() { @@ -1760,6 +1761,10 @@ } function setOptionSource(optionId, sourceKey) { + pendingStableOptionOrder = { + tabId: activeTab, + optionIds: getRenderedOptionIds(), + }; state.priceSourceSelections = { ...state.priceSourceSelections, [optionId]: normalizeSourceKey(sourceKey), @@ -1769,12 +1774,19 @@ } function setSortMode(sortMode) { + pendingStableOptionOrder = null; const allowedModes = new Set(['vote-desc', 'vote-asc', 'price-asc', 'price-desc']); state.sortMode = allowedModes.has(sortMode) ? sortMode : 'vote-desc'; localStorage.setItem('cabo_sort_mode', state.sortMode); render(); } + function getRenderedOptionIds() { + return [...document.querySelectorAll('#optionsList .option-card[data-option-id]')] + .map(card => card.dataset.optionId) + .filter(Boolean); + } + function getOptionOrderIndexMap() { return new Map(state.options.map((opt, index) => [opt.id, index])); } @@ -1817,6 +1829,24 @@ }); } + function applyPendingStableOrder(sortedOptions) { + if (!pendingStableOptionOrder || pendingStableOptionOrder.tabId !== activeTab) { + pendingStableOptionOrder = null; + return sortedOptions; + } + + const order = new Map(pendingStableOptionOrder.optionIds.map((optionId, index) => [optionId, index])); + pendingStableOptionOrder = null; + if (!order.size) return sortedOptions; + + return [...sortedOptions].sort((a, b) => { + const aIndex = order.has(a.id) ? order.get(a.id) : Number.MAX_SAFE_INTEGER; + const bIndex = order.has(b.id) ? order.get(b.id) : Number.MAX_SAFE_INTEGER; + if (aIndex !== bIndex) return aIndex - bIndex; + return sortedOptions.indexOf(a) - sortedOptions.indexOf(b); + }); + } + function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') @@ -2308,7 +2338,7 @@ return; } - const sorted = sortOptionsByMode(opts, getOptionOrderIndexMap()); + const sorted = applyPendingStableOrder(sortOptionsByMode(opts, getOptionOrderIndexMap())); const maxVotes = sorted[0] ? getVoteEntries(sorted[0]).length : 1; const budgetBoard = activeTab === 'budget' ? renderBudgetBoard() : ''; @@ -2325,7 +2355,7 @@ : (opt.url ? `🔗 ${opt.url.replace(/^https?:\/\//, '').split('/')[0]}` : ''); return ` -
+
${opt.name}
${voteEntries.length} vote${voteEntries.length !== 1 ? 's' : ''}