# Задание B2 (Codex): переработка интерфейса, фазы 2–6

## Дата поступления
2026-08-21

## База
Проверочный HEAD на момент выдачи: **`39b1f92`**, `origin/main` = `39b1f92`.
Перед началом: `git fetch`, зафиксировать фактический `BASE_SHA`, **обязательно подтянуть `main`** — с момента вашей PHASE 1 в него вошло 8 коммитов.

## Ветка
`codex/ui-redesign` — она отстала, начните с `git rebase origin/main` либо создайте новую от `origin/main`.

---

## Это заменяет предыдущее задание

Предыдущая выдача была ошибочной: вам передали Задание A (слой состояния), предназначенное Antigravity. Ту работу Antigravity уже выполнил и она влита в `main`. **Всё, что вы делали по слою состояния, не нужно** — не пытайтесь это дослать или смержить, оно конфликтует с влитым. Возьмите чистый `origin/main` и работайте только по этому документу.

---

## ГРАНИЦА РАБОТ

Antigravity продолжает Задание A (реальный сбор квот, релизная инфраструктура, остатки долгов). Разделение по файлам прежнее.

**Ваша зона:**
```
src/antigravity_provider/router/ui/**
src/antigravity_provider/router/hermes_hub_app.py
tests/test_ui_*.py
```

**Чужая зона (НЕ трогать):**
```
src/antigravity_provider/router/*.py      (state_store, unified_health, scheduler,
                                           event_bus, quota_collector, model_registry,
                                           router_engine, profile_manager, *_oauth, …)
src/antigravity_provider/router/adapters/**
src/antigravity_provider/*.py
scripts/**, installer/**, config/**, legacy/**
tests/** кроме tests/test_ui_*.py
```

Не хватает данных для отрисовки — **не добавляйте сбор в UI**. Зафиксируйте в отчёте разделом «Backend gaps» и покажите честное отсутствие данных.

---

## Что уже сделано — не переделывать

**Ваша PHASE 1 влита** (`a7027b4`): дизайн-токены с семантическими ролями, уплотнённая типографика, библиотека компонентов (`PlanBadge`, `QuotaBar`, `QuotaBucketWidget`, `AccountCardWidget`, `AgentCardWidget`, `RouteTargetWidget`, `EmptyState`, `SearchField`, `FilterButton`, `ActionButton`, `IconButton`, `ConfirmDialog`, `Toast`, `ellipsize_text`, `Tooltip`). Стройте на них.

Одна правка в вашем тесте: `test_unknown_quota_is_supported_explicitly` сравнивал объект аннотации со строкой и не мог пройти — переписан через `typing.get_args`. Причина в том, что вы прогоняли тесты без `customtkinter`, где ваши UI-тесты **пропускаются**. Поставьте зависимости локально, иначе снова будете отчитываться о непроверенном.

**Сделано другими, не откатывать:**

- `hermes_hub_app.py` больше не передаёт `app_state` в `update_data`; все view защищены проверкой `isinstance(snapshot, HubSnapshot)` — это чинило краш при старте `'dict' object has no attribute 'readiness'`;
- `add_account_wizard.py` — распаковка `start_profile_oauth` приведена к 3-кортежу `(session_id, auth_url, port)` и добавлено поле `self.oauth_port`. **Без этого подключение Antigravity-аккаунта не работает вовсе.** Если ваша ветка содержит старый двухэлементный вариант — при rebase возьмите версию из `main`;
- `conftest.py` пропускает GUI-тесты только по маркеру `ui`, не по имени; модули с GUI-импортом обязаны иметь `pytest.importorskip("customtkinter")` на уровне модуля — это проверяется `tests/test_import_invariants.py`;
- `scan_all()` из UI убран: все view уже принимают `snapshot` и не ходят в backend сами. Сохраните это свойство.

---

## Контракт данных

**`docs/UI_STATE_CONTRACT.md` опубликован** — это единственный источник правды о том, какие поля существуют. Раздел 7 «Backend gaps» перечисляет 12 пробелов; из них прямо влияют на вас:

| Пробел | Следствие для UI |
|---|---|
| Живых числовых квот **нет ни у одного провайдера**, все корзины `baseline` | Показывать как оценку; точные проценты не выдавать за измерение |
| Разделение корзин Antigravity есть структурно, значения не измеряются | Отрисовать структуру, значения — с пометкой оценки |
| Происхождение тарифа не доходит до `ProfileViewModel` | `PlanBadge` не показывать, пока поле не появится |
| `AgentViewModel` не содержит активной сессии и квоты | В карточке агента эти строки — `Н/Д` либо отсутствуют |
| `PipelineNode` не содержит идентичности аккаунта, состояния квоты и причины переключения | В цепочке маршрута показать то, что есть; остальное не выдумывать |
| Большинство точечных событий объявлены, но никто их не публикует | Не рассчитывать на дельта-события; обновление по снапшоту |
| Латентность, RPS, проценты ошибок, стоимость отсутствуют | Только `Н/Д` либо блок скрыт |

Если поле нужно, а его нет — это строка в вашем отчёте, а не повод достать данные самому.

---

## Область задачи

### PHASE 2 — Аккаунты и квоты (приоритет)

Экран уже на keyed-дельте (`self._cards[profile_id].update_from_model(...)`) — сохранить и достроить:

- **внутренний `quota_box` всё ещё пересоздаётся целиком** — перевести на `QuotaBucketWidget` со стабильным ключом и обновлением на месте;
- идентичность аккаунта по контракту: email → username → provider user ID → внятный fallback; «Google account #1» недопустимо, если реальная идентичность есть;
- мульти-корзинные квоты: сколько корзин отдал `QuotaSnapshot`, столько и показать, со своими метками и временем сброса; признак `is_estimated` — визуально явный;
- компактный и развёрнутый режим карточки;
- группировка по провайдерам со сворачиванием, поиск, фильтры по провайдеру/здоровью/роли;
- удаление аккаунта затрагивает только его карточку.

### PHASE 3 — Обзор

Dashboard: состояние системы, число доступных провайдеров, подключённых аккаунтов, готовых ролей, предупреждения и ошибки, компактное представление активного маршрута, аккаунты с низкой квотой и с истёкшей авторизацией, последние реальные события журнала.

Всё, чего нет в контракте, — `Н/Д` или блок скрыт. Никаких «842 rps» и «99.98 %» из макетов.

### PHASE 4 — Команда

Иерархия «оркестратор → роли → агенты». Для агента: имя, роль, провайдер, аккаунт, модель, здоровье. Активная сессия и квота — по контракту отсутствуют, поэтому либо `Н/Д`, либо не показывать вовсе.

### PHASE 5 — Маршрутизация

Цепочка отказоустойчивости по ролям: основной → резерв 1 → 2 → 3, с активным узлом. Провайдер и модель есть; идентичность аккаунта, состояние квоты и причина переключения в `PipelineNode` отсутствуют — не выдумывать. Редактор оставить кнопочным/селекторным, drag-and-drop не делать.

### PHASE 6 — Второстепенные экраны

Единый стиль для состояния, журнала, настроек, «о программе», мастера и диалогов. `health_view`, `providers_view`, `routing_view`, `logs_view` пересоздают виджеты через `winfo_children()` + `destroy()` — перевести на обновление по ключу там, где список стабилен.

**Мастер подключения не ломать.** Работающие потоки: Antigravity OAuth, Codex OAuth, Codex API, OpenCode, Claude, Grok, вставка из буфера. Особенно бережно с `_init_antigravity_oauth` — он только что чинился.

---

## Ограничения

- Не переписывать на Tauri/React. Готовить архитектуру к разделению — да, мигрировать — нет.
- Не удалять `router/ui` целиком ради переписывания заново; после каждой фазы приложение запускается.
- Сеть, опрос OAuth, subprocess, сканирование диска — только в фоне, никогда в UI-потоке.
- Секреты: не логировать токены и коды, ключи показывать маскированно.
- Тег `v0.1.1` не создавать, релиз не публиковать, манифест не трогать.

---

## Критерии приёмки

1. Ни один файл чужой зоны не изменён: `git diff --name-only BASE_SHA..HEAD` не содержит путей из списка.
2. Приложение запускается после каждой фазы; экран «Команда» открывается без ошибок.
3. `quota_box` обновляется по ключу; изменение аккаунта A не перерисовывает карточку B; удаление затрагивает одну карточку.
4. Идентичность аккаунта берётся по приоритету из контракта.
5. Оценочные квоты визуально помечены; ни одно значение не подано как измерение провайдера.
6. `PlanBadge` не отображается, пока происхождение тарифа не приходит в модель.
7. Отсутствующие данные — `Н/Д` или скрытый блок; выдуманных метрик нет.
8. Мастер: URL авторизации доступен и копируется до открытия браузера; все шесть потоков подключения работают; вставка из буфера работает.
9. Ни один view не обращается к backend напрямую — только к переданному снапшоту.
10. UI-тесты начинаются с `pytest.importorskip("customtkinter")`.
11. Прогон **в обоих окружениях**: без UI-зависимостей и с установленными `customtkinter`, `pillow`, `psutil`. Оба зелёные. Отчёт содержит обе команды и оба результата.
12. `ruff check .` чисто; release gate не ухудшен.

## Проверка производительности
На 50 аккаунтах изменить квоту одного и зафиксировать в отчёте число уничтоженных и созданных виджетов до и после. Ожидается обновление одной карточки.

## Отчёт
`CODEX_UI_REDESIGN_REPORT.md`: `BASE_SHA`, `FINAL_SHA`, ветка, изменённые файлы, переработанные экраны, что реально поддержано по идентичности/тарифам/квотам, замер производительности, точные команды и результаты **обоих** прогонов, известные ограничения и раздел «Backend gaps».

---

## Главное
Интерфейс должен за несколько секунд отвечать: кто выполняет задачу, через какого провайдера, аккаунт и модель, сколько лимита осталось, кто подхватит при недоступности. Там, где данных нет, — честное «Н/Д». Пустое место лучше выдуманного числа.
