From 89ddc4f68f10d5f3ed7ca42b05f7303e9b56d713 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 27 May 2026 17:10:33 +0300 Subject: [PATCH] =?UTF-8?q?fix(tracker):=20backfill=20=E2=80=94=20local-on?= =?UTF-8?q?ly=20mark=5Fread'=D1=8B=20=D0=B4=D0=BE=D1=81=D1=8B=D0=BB=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=20=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=83=D0=B7=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Старый syncPending-баг (теперь починен в коммите dacc0eb) оставил у учеников локальное состояние с прочитанными параграфами, но сервер ничего не знал. После фикса firstTime=false для всех уже-кликнутых пилюль, и mark_read не уходил на сервер при повторном клике. Решение: loadServerProgress теперь вычисляет diff между local.read и server.read; для каждого ключа, которого нет на сервере, дёргает syncToServer({mark_read: k}). Coalesce в pendingExtra гарантирует, что все запросы упорядочатся. Эффект: при следующей загрузке учебника каталог автоматически догоняется. --- frontend/js/textbook-tracker.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/js/textbook-tracker.js b/frontend/js/textbook-tracker.js index 7422b0b..1cb74cd 100644 --- a/frontend/js/textbook-tracker.js +++ b/frontend/js/textbook-tracker.js @@ -53,7 +53,11 @@ }).catch(() => {}); } - /* ── 2. Initial load: merge server data into local state ──────── */ + /* ── 2. Initial load: merge server data into local state + push back ── + Если в локальном кэше есть ключи, которых нет на сервере + (последствие старого бага syncPending) — досылаем их через + отдельные mark_read POST'ы. Это лечит «вечный 0/N» у пользователей, + которые до фикса уже накликали кучу пилюль. */ function loadServerProgress() { if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return; fetch('/api/textbooks/' + slug, { @@ -62,11 +66,17 @@ .then(r => r.ok ? r.json() : null) .then(d => { if (!d || !d.progress) return; - const merged = Array.from(new Set([...(localState.read || []), ...(d.progress.read || [])])); + const serverRead = new Set(d.progress.read || []); + const localRead = localState.read || []; + const missing = localRead.filter(k => !serverRead.has(k)); + // объединяем для UI + const merged = Array.from(new Set([...localRead, ...d.progress.read || []])); localState.read = merged; if (!localState.last) localState.last = d.progress.last_para; localStorage.setItem(lsKey, JSON.stringify(localState)); refreshAllUI(); + // догоняем сервер последовательно: syncToServer уже коалесцирует + missing.forEach(k => syncToServer({ mark_read: k })); }) .catch(() => {}); }