fix(tracker): mark_read шлётся на КАЖДЫЙ клик пилюли (идемпотентно)

Старый syncPending-баг успел залить локальный localState.read данными,
которых нет на сервере. После фиксов firstTime=false для всех ключей в
localState.read, и mark_read иначе никогда не уходил → каталог показывал
0 даже после реальных кликов.

Решение: убрать оптимизацию firstTime. Слать mark_read КАЖДЫЙ раз —
серверный код  if(!arr.includes(mark_read)) arr.push(...)  не добавит
дубликат. Лишний POST стоит копейки, зато система самовосстанавливается
без зависимости от загрузочного backfill.
This commit is contained in:
Maxim Dolgolyov
2026-05-27 17:17:00 +03:00
parent 89ddc4f68f
commit 25c0bb2a79
+9 -8
View File
@@ -214,23 +214,24 @@
localState.read.forEach(k => { refreshPillUI(k); refreshCheckUI(k); });
}
/* ── 7. Pill click → last_para + (первый раз) mark_read ────────
Объединяем оба обновления в один POST, чтобы syncPending-guard
в syncToServer не дропнул второй вызов в том же тике. */
/* ── 7. Pill click → ВСЕГДА шлём last_para + mark_read одним POST
Идемпотентно: на сервере `if (!arr.includes(mark_read)) arr.push(...)`
не добавит дубликат. Эта «избыточность» лечит самовосстановлением
ситуации, когда localState.read был засорён старым syncPending-багом
(есть ключ локально, нет на сервере, mark_read иначе никогда не уйдёт). */
function wirePillTracking() {
document.body.addEventListener('click', e => {
const pill = e.target.closest('.para-pill[data-para]');
if (!pill) return;
const key = pill.dataset.para;
localState.last = key;
const firstTime = !localState.read.includes(key);
if (firstTime) {
if (!localState.read.includes(key)) {
localState.read.push(key);
refreshPillUI(key);
refreshCheckUI(key);
}
refreshPillUI(key);
refreshCheckUI(key);
persist();
syncToServer(firstTime ? { mark_read: key } : {});
syncToServer({ mark_read: key });
});
}