fix(web): окно аккаунта не обновлялось и застревало на заглушке

Владелец открыл карточку 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 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-24 20:40:13 +07:00
parent 0df7dba2ce
commit c49e6eac31
2 changed files with 55 additions and 0 deletions

View file

@ -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');
}

View file

@ -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, "опрос кода устройства не останавливается при закрытии"