chore(tracker): убрать отладку — console.log, debug-бейдж, server-лог

Прогресс работает, отладочная обвязка больше не нужна:
- tracker.js: удалены все console.log/console.warn (boot, click,
  POST, HTTP-ответ, patch-успех), удалены ensureDebugBadge и
  updateDebugBadge (визуальный бейдж в правом нижнем углу),
  recordParaVisit больше не вызывает updateDebugBadge
- 5 хуков (bubble, capture, setParaTab-patch, .tab[refN] sidebar,
  polling .active) сохранены в production-виде — без логов, но
  с теми же действиями
- backend/routes/textbooks.js: убран '[progress]' console.log из
  POST /:slug/progress

Pre-commit hook теперь проходит без --no-verify.
This commit is contained in:
Maxim Dolgolyov
2026-05-27 18:12:12 +03:00
parent 908e7f3f1c
commit 08d259bfa2
2 changed files with 15 additions and 63 deletions
-2
View File
@@ -214,8 +214,6 @@ router.get('/:slug', (req, res) => {
/* POST /api/textbooks/:slug/progress — update progress */
router.post('/:slug/progress', (req, res) => {
// DEBUG: log incoming progress writes
console.log('[progress]', new Date().toISOString(), 'user', req.user && req.user.id, '| slug=', req.params.slug, '| body=', JSON.stringify(req.body || {}));
const t = db.prepare('SELECT id FROM textbooks WHERE slug=? AND is_active=1').get(req.params.slug);
if (!t) return res.status(404).json({ error: 'Учебник не найден' });
+15 -61
View File
@@ -31,32 +31,26 @@
let syncPending = false;
let pendingExtra = null;
function syncToServer(extra) {
if (typeof LS === 'undefined' || !LS.getToken) { console.warn('[tracker] LS не загружен — пропускаем sync'); return; }
if (!LS.getToken()) { console.warn('[tracker] нет токена в localStorage — пользователь не залогинен'); return; }
if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
if (syncPending) {
pendingExtra = Object.assign(pendingExtra || {}, extra || {});
return;
}
syncPending = true;
const body = JSON.stringify({ last_para: localState.last, ...(extra || {}) });
console.log('[tracker]', slug, '→ POST', body);
fetch('/api/textbooks/' + slug + '/progress', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + LS.getToken(),
},
body,
}).then(r => {
console.log('[tracker]', slug, '← HTTP', r.status);
if (!r.ok) r.text().then(t => console.warn('[tracker] ошибка:', t));
body: JSON.stringify({ last_para: localState.last, ...(extra || {}) }),
}).finally(() => {
syncPending = false;
if (pendingExtra) {
const next = pendingExtra; pendingExtra = null;
syncToServer(next);
}
}).catch(e => console.warn('[tracker] fetch упал:', e));
}).catch(() => {});
}
/* ── 2. Initial load: merge server data into local state + push back ──
@@ -235,100 +229,63 @@
refreshCheckUI(key);
persist();
syncToServer({ mark_read: key });
updateDebugBadge();
}
// DEBUG: визуальный бейдж прогресса в правом нижнем углу.
function ensureDebugBadge() {
if (document.getElementById('tb-debug-badge')) return;
const b = document.createElement('div');
b.id = 'tb-debug-badge';
b.style.cssText = 'position:fixed;bottom:12px;right:12px;z-index:99999;background:rgba(15,23,42,.92);color:#fff;padding:10px 14px;border-radius:10px;font-family:monospace;font-size:12px;line-height:1.5;box-shadow:0 4px 14px rgba(0,0,0,.3);max-width:260px';
document.body.appendChild(b);
updateDebugBadge();
}
function updateDebugBadge() {
const b = document.getElementById('tb-debug-badge');
if (!b) return;
const activeParaEl = document.querySelector('.para-pill.active[data-para]');
const active = activeParaEl ? activeParaEl.dataset.para : '?';
b.innerHTML =
'<div><b>tracker debug</b></div>' +
'<div>slug: ' + slug + '</div>' +
'<div>active pill: ' + active + '</div>' +
'<div>localState.last: ' + (localState.last || '?') + '</div>' +
'<div>read [' + localState.read.length + ']: ' + (localState.read.slice(-8).join(',') || '—') + '</div>' +
'<div style="opacity:.6;margin-top:4px">Кликай пилюли — должно меняться</div>';
}
function wirePillTracking() {
// Hook 1: всплытие click до body. Работает в обычном HTML.
// Hook 1: всплытие click body. Работает в обычном HTML.
document.body.addEventListener('click', e => {
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
if (!pill) return;
console.log('[tracker] клик по пилюле (bubble)', pill.dataset.para);
recordParaVisit(pill.dataset.para);
});
// Hook 2: capture-фаза (ловит до того, как кто-то остановит propagation).
// Hook 2: capture-фаза ловит до того, как кто-то остановит propagation.
document.addEventListener('click', e => {
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
if (!pill) return;
// Защита от двойного срабатывания — отметим pill сразу.
if (pill.__tbVisited) return;
pill.__tbVisited = true;
setTimeout(() => { pill.__tbVisited = false; }, 100);
console.log('[tracker] клик по пилюле (capture)', pill.dataset.para);
recordParaVisit(pill.dataset.para);
}, true);
// Hook 3: monkey-patch setParaTab — кликом по пилюле химия/физика 9 вызывают
// inline onclick="setParaTab('pN')". Перехват напрямую = работает даже если
// event-bubbling сломан расширением браузера или CSS overlay.
// Hook 3: monkey-patch setParaTab. chemistry-9 / physics-9 зовут её inline
// через onclick="setParaTab('pN')" — перехват на уровне JS-функции
// работает даже если event-стек сломан расширением или overlay.
function patchSetParaTab() {
if (typeof window.setParaTab !== 'function' || window.setParaTab.__tbPatched) return;
const orig = window.setParaTab;
const wrapped = function (para) {
try {
if (para && /^p\d+$/i.test(String(para))) {
console.log('[tracker] setParaTab перехвачен', para);
recordParaVisit(String(para));
}
} catch (e) { console.warn('[tracker] patch error:', e); }
if (para && /^p\d+$/i.test(String(para))) recordParaVisit(String(para));
} catch (e) {}
return orig.apply(this, arguments);
};
wrapped.__tbPatched = true;
window.setParaTab = wrapped;
console.log('[tracker] setParaTab успешно обёрнут');
}
patchSetParaTab();
// Если страница определяет setParaTab позже — поймаем через короткие опросы.
let tries = 0;
const ivl = setInterval(() => {
patchSetParaTab();
if (typeof window.setParaTab === 'function' && window.setParaTab.__tbPatched) clearInterval(ivl);
if (++tries > 20) clearInterval(ivl);
}, 100);
// Hook 4b: боковая панель-справочник в chemistry-9 / physics-9 использует
// .tab[data-tab="refN"] вместо .para-pill. Маппим ref<N> → p<N> и фиксируем.
// Hook 4: справочник в chemistry-9 / physics-9 использует .tab[data-tab=refN]
// (отдельная панель). Маппим ref<N> → p<N> и фиксируем как просмотр параграфа.
document.addEventListener('click', e => {
const tab = e.target && e.target.closest && e.target.closest('.tab[data-tab]');
if (!tab) return;
const m = String(tab.dataset.tab || '').match(/^ref(\d+)$/);
if (!m) return;
const para = 'p' + m[1];
console.log('[tracker] клик по справ. табу', tab.dataset.tab, '→', para);
recordParaVisit(para);
recordParaVisit('p' + m[1]);
}, true);
// Hook 4: polling — наблюдаем за классом .active на пилюлях.
// Если кто-то поменял активный параграф (через клик, через JS вызов
// setParaTab, через любой механизм) — мы это поймаем за 500мс и зафиксируем.
// Самый robust способ; не зависит ни от событий, ни от наличия функций.
// Hook 5: polling — наблюдаем за классом .active на пилюлях.
// Срабатывает на любую программную смену активного параграфа.
let lastActivePara = null;
setInterval(() => {
const active = document.querySelector('.para-pill.active[data-para]');
if (!active) return;
const para = active.dataset.para;
if (para && para !== lastActivePara) {
if (lastActivePara !== null) console.log('[tracker] активный параграф изменился на', para);
lastActivePara = para;
recordParaVisit(para);
}
@@ -566,9 +523,6 @@
}
function boot() {
console.log('[tracker] boot, slug =', slug, '| LS:', typeof LS !== 'undefined', '| token:', typeof LS !== 'undefined' && LS.getToken && !!LS.getToken());
ensureDebugBadge();
setInterval(updateDebugBadge, 1000);
injectStyles();
installBackButton();
installBookmarksBtn();