From c49e6eac31308bdd105ac9b2f18d9d555f7d8078 Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Mon, 24 Aug 2026 20:40:13 +0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D0=BE=D0=BA=D0=BD=D0=BE=20=D0=B0?= =?UTF-8?q?=D0=BA=D0=BA=D0=B0=D1=83=D0=BD=D1=82=D0=B0=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F=D0=BB=D0=BE=D1=81?= =?UTF-8?q?=D1=8C=20=D0=B8=20=D0=B7=D0=B0=D1=81=D1=82=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B3=D0=BB?= =?UTF-8?q?=D1=83=D1=88=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец открыл карточку Grok и увидел «Grok 2h — Н/Д» и статус «Не проверялся» — хотя сервер в этот момент уже отдавал настоящие данные: подписка 14%, GrokChat 13%, GrokBuild 1%. Проверено на живом сервере: и /api/snapshot, и quota_snapshot внутри профиля содержали правильные корзины. Дефект целиком в клиенте — окно рисовалось ОДИН РАЗ при открытии и на опрос не реагировало. Открытое до завершения прогрева квот, оно навсегда оставалось с заглушкой _generate_baseline_snapshot, а после успешной проверки подключения по-прежнему показывало «Не проверялся». Теперь открытый профиль отслеживается и перерисовывается при каждом применении снапшота. Объявление переменной поднято выше первого использования: let не поднимается, и обращение раньше объявления роняло бы обработчик снапшота целиком. При закрытии окна сбрасывается отслеживание и останавливается опрос кода устройства — раньше он продолжал работать после закрытия мастера. Тесты: 431 passed, ruff чисто. Co-Authored-By: Claude Opus 5 --- .../router/web/static/app.js | 18 +++++++++ tests/test_account_modal_refresh.py | 37 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/test_account_modal_refresh.py diff --git a/src/antigravity_provider/router/web/static/app.js b/src/antigravity_provider/router/web/static/app.js index 463761f..0644f00 100644 --- a/src/antigravity_provider/router/web/static/app.js +++ b/src/antigravity_provider/router/web/static/app.js @@ -8,6 +8,9 @@ const USE_MOCK_FIXTURE = false; let lastAppliedSeq = -1; +// Какой профиль сейчас открыт в окне аккаунта: нужно, чтобы перерисовывать +// его по свежему снапшоту, а не оставлять с заглушкой. +let _openAccountModalProfile = null; let currentSnapshot = null; let activeView = 'overview'; let pollTimer = null; @@ -226,6 +229,18 @@ function applySnapshot(snapshot) { currentSnapshot = snapshot; updateGlobalHeader(); + // Открытое окно аккаунта рисовалось один раз и на опрос не реагировало. + // Если его открыть до того, как придут живые квоты, оно навсегда + // оставалось с заглушкой («Grok 2h — Н/Д») и со статусом «Не проверялся» + // даже после успешной проверки. Перерисовываем по свежему снапшоту. + if (_openAccountModalProfile) { + try { + openAccountDetailsModal(_openAccountModalProfile, true); + } catch (e) { + console.warn('[Hub] Не удалось обновить окно аккаунта:', e); + } + } + if (isFirstLoad) { const params = new URLSearchParams(window.location.search); const targetView = params.get('view'); @@ -1296,6 +1311,7 @@ async function saveHubServerSettings() { // ── MODALS (Account Details, Model Choice, Routing, Wizard) ── function openAccountDetailsModal(profileId) { + _openAccountModalProfile = profileId; if (!currentSnapshot) return; const profile = (currentSnapshot.all_profiles || {})[profileId]; if (!profile) return; @@ -1939,6 +1955,8 @@ function showModal() { } function closeModal() { + _openAccountModalProfile = null; + stopDeviceAuthPolling(); if (elements.modalBackdrop) elements.modalBackdrop.classList.add('hidden'); } diff --git a/tests/test_account_modal_refresh.py b/tests/test_account_modal_refresh.py new file mode 100644 index 0000000..91858bb --- /dev/null +++ b/tests/test_account_modal_refresh.py @@ -0,0 +1,37 @@ +"""Открытое окно аккаунта обязано обновляться по свежему снапшоту. + +Владелец открыл карточку Grok сразу после запуска — до того, как пришли +живые квоты, — и она навсегда осталась с заглушкой «Grok 2h — Н/Д» и +статусом «Не проверялся», хотя сервер уже отдавал 14% расхода подписки и +проверка подключения прошла успешно. + +Окно рисовалось один раз при открытии и на опрос не реагировало. +""" + +from __future__ import annotations + +import pathlib + +APP_JS = pathlib.Path("src/antigravity_provider/router/web/static/app.js").read_text(encoding="utf-8") + + +def test_open_modal_is_tracked_and_redrawn(): + assert "_openAccountModalProfile" in APP_JS, "открытое окно не отслеживается" + + # Объявление обязано идти ДО использования: let не поднимается, и + # обращение к нему раньше объявления роняет обработчик снапшота. + declaration = APP_JS.index("let _openAccountModalProfile") + first_use = APP_JS.index("if (_openAccountModalProfile)") + assert declaration < first_use, "переменная используется раньше объявления" + + +def test_modal_redraw_happens_on_snapshot_apply(): + """Перерисовка должна быть привязана к применению снапшота.""" + head = APP_JS[: APP_JS.index("if (_openAccountModalProfile)")] + assert "currentSnapshot = snapshot" in head, "перерисовка не там, где применяется снапшот" + + +def test_closing_modal_stops_tracking_and_polling(): + block = APP_JS.split("function closeModal()", 1)[1][:300] + assert "_openAccountModalProfile = null" in block, "после закрытия окно продолжает перерисовываться" + assert "stopDeviceAuthPolling" in block, "опрос кода устройства не останавливается при закрытии"