From 25c0bb2a796d0d2e56bf0fc0049ce9a79ee2a818 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 27 May 2026 17:17:00 +0300 Subject: [PATCH] =?UTF-8?q?fix(tracker):=20mark=5Fread=20=D1=88=D0=BB?= =?UTF-8?q?=D1=91=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=20=D0=9A=D0=90=D0=96?= =?UTF-8?q?=D0=94=D0=AB=D0=99=20=D0=BA=D0=BB=D0=B8=D0=BA=20=D0=BF=D0=B8?= =?UTF-8?q?=D0=BB=D1=8E=D0=BB=D0=B8=20(=D0=B8=D0=B4=D0=B5=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=B5=D0=BD=D1=82=D0=BD=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Старый syncPending-баг успел залить локальный localState.read данными, которых нет на сервере. После фиксов firstTime=false для всех ключей в localState.read, и mark_read иначе никогда не уходил → каталог показывал 0 даже после реальных кликов. Решение: убрать оптимизацию firstTime. Слать mark_read КАЖДЫЙ раз — серверный код if(!arr.includes(mark_read)) arr.push(...) не добавит дубликат. Лишний POST стоит копейки, зато система самовосстанавливается без зависимости от загрузочного backfill. --- frontend/js/textbook-tracker.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/js/textbook-tracker.js b/frontend/js/textbook-tracker.js index 1cb74cd..fdb8f0b 100644 --- a/frontend/js/textbook-tracker.js +++ b/frontend/js/textbook-tracker.js @@ -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 }); }); }