# Hermes Hub UI redesign — фазы 2–6 Дата: 2026-08-21 Ветка: `codex/ui-redesign` INITIAL_BASE_SHA: `c6876e96b274b598ada964a6639b6c3ad038aebf` BASE_SHA после финального обязательного rebase на `origin/main`: `e8a404be035fa04b5f76e3e572c6539fba0e83e4`. Main продвинулся во время работы одним документационным коммитом Antigravity; этот файл не изменялся Codex. FINAL_SHA: `git rev-parse codex/ui-redesign` в момент handoff (точный SHA указан в итоговом сообщении; commit не может содержать собственный SHA). ## Изменённые файлы - `src/antigravity_provider/router/hermes_hub_app.py` - `src/antigravity_provider/router/ui/components.py` - `src/antigravity_provider/router/ui/views/accounts_view.py` - `src/antigravity_provider/router/ui/views/dashboard_view.py` - `src/antigravity_provider/router/ui/views/health_view.py` - `src/antigravity_provider/router/ui/views/logs_view.py` - `src/antigravity_provider/router/ui/views/providers_view.py` - `src/antigravity_provider/router/ui/views/routing_view.py` - `src/antigravity_provider/router/ui/views/settings_view.py` - `src/antigravity_provider/router/ui/views/team_view.py` - `tests/test_ui_phase2_6.py` - `CODEX_UI_REDESIGN_REPORT.md` Файлы state/data/router, adapters, installer, scripts, config, legacy и чужие тесты не изменялись. ## Переработанные экраны ### Аккаунты и квоты - Одна карточка соответствует одному `profile_id` и переиспользуется между поколениями snapshot. - `QuotaBucketWidget` хранится по стабильному `QuotaBucket.id`; существующая корзина обновляется на месте. - Идентичность выбирается в порядке: email → `account_identity` → display name → profile ID. - `PlanBadge` не создаётся: provenance тарифа отсутствует в `ProfileViewModel`. - Все переданные корзины показываются отдельно; baseline/estimated корзины имеют явную пометку «оценка». - Неизвестный остаток и время сброса показываются как `Н/Д`. - Есть компактный/развёрнутый режим, сворачиваемые группы провайдеров, поиск и фильтры по провайдеру, здоровью и роли. - Удаление профиля из snapshot уничтожает только соответствующую карточку. ### Обзор - Экран добавлен в основную навигацию. - Показывает readiness, доступные провайдеры, подключённые аккаунты, готовые роли, активные маршруты и реальные warnings. - Низкая квота показывается только для неоценочного snapshot с известным числом. - Истёкшая авторизация показывается из `auth_state`. - Выдуманные throughput/SLA/cost метрики отсутствуют. ### Команда - Иерархия разделена на «Оркестратор» и «Роли и агенты». - Карточки keyed по `role_id` и содержат роль, провайдера, аккаунт, модель и здоровье. - UI больше не запрашивает quota service; активная сессия и квота агента не выдумываются. ### Маршрутизация - Роли keyed по `role_id`, узлы цепочки — по `profile_id`. - Отображается порядок основной → резерв 1 → резерв 2 → резерв 3 и активный узел. - Показываются только доступные provider/model/status поля. - Причина переключения честно обозначена как `Н/Д`. - Редактирование остаётся кнопочным; drag-and-drop не добавлялся. ### Второстепенные экраны - Providers и Health используют keyed cards/rows. - Logs не читает backend или файлы напрямую: события отсутствуют в `HubSnapshot`, поэтому экран показывает честное `Н/Д`. - Settings стал presentation-only и отправляет save/update команды в action layer приложения. - About и мастер подключения сохранены; `_init_antigravity_oauth` и шесть рабочих потоков подключения не изменялись. ## Замер производительности: 50 аккаунтов Тест: `test_fifty_accounts_update_one_quota_without_rebuilding_other_cards`. | Состояние | Карточек создано | Карточек уничтожено | Quota widgets создано | Quota widgets уничтожено | |---|---:|---:|---:|---:| | Первичная отрисовка | 50 | 0 | 50 | 0 | | После изменения квоты аккаунта A | 50 | 0 | 50 | 0 | | Дельта операции | **0** | **0** | **0** | **0** | Object identity всех 50 карточек до и после обновления совпадает. Отдельный тест удаления подтверждает: уничтожается одна карточка, соседняя сохраняет object identity. ## Проверки ### Без UI-зависимостей ```powershell uv run --isolated --no-project --with pytest --with pytest-asyncio --with anyio --with pyyaml --with pydantic --with requests --with httpx pytest -q ``` Результат: `160 passed, 28 skipped, 3 deselected in 9.55s`. ### С UI-зависимостями Полный набор успешно проходил одной командой (`202 passed, 9 skipped, 3 deselected`). Для воспроизводимого финального прогона в uv-managed Python использованы два свежих Tk-процесса: его Tcl runtime нестабильно повторно создаёт root после `root.destroy()` в соседнем OAuth-тесте. ```powershell uv run --with customtkinter --with pillow --with psutil --with pytest --with pytest-asyncio pytest -q -k "not test_f_copy_before_open_browser" uv run --with customtkinter --with pillow --with psutil --with pytest --with pytest-asyncio pytest -q tests/test_oauth_lifecycle.py::test_f_copy_before_open_browser ``` Результаты: `201 passed, 9 skipped, 4 deselected in 26.42s` и `1 passed in 2.86s`. В сумме весь UI-enabled набор зелёный; разделение процессов относится только к Tcl/Tk test runtime, не к приложению. ### Статика и release gate ```powershell uv run --with ruff ruff check . uv run python scripts/release_gate.py ``` Результат: Ruff — `All checks passed`; release gate — `PASSED`. Live manifest не менялся; package URL по-прежнему имеет внешний статус pending upload/404, как и на базе задачи. ## Backend gaps 1. Ни один провайдер не отдаёт live numeric quotas через опубликованный контракт; baseline отображается только как оценка. 2. Разделение Antigravity на Claude/Gemini существует структурно, но численные значения не измеряются. 3. Provenance тарифа не входит в `ProfileViewModel`; поэтому `PlanBadge` скрыт. 4. В `AgentViewModel` нет активной сессии и квоты. 5. В `PipelineNode` нет идентичности аккаунта, quota state и причины переключения. 6. События журнала не входят в `HubSnapshot`; Dashboard и Logs не обращаются к `EventLogService` напрямую. 7. Латентность, RPS, error rate, стоимость и удалённые SLA отсутствуют. 8. UI не компенсирует эти пробелы собственным опросом, сканированием диска или синтетическими числами. ## Известные ограничения - В текущем контракте журнал нельзя показать без расширения `HubSnapshot`. - Настройки читаются/сохраняются action layer приложения; snapshot не содержит отдельной settings-модели. - Live release package остаётся внешней задачей. По B2 тег, релиз и manifest не создавались и не изменялись.