diff --git a/agents/inbox/2026-08-21-B2-codex-ui-redesign-phase2.md b/agents/inbox/2026-08-21-B2-codex-ui-redesign-phase2.md new file mode 100644 index 0000000..aef2c71 --- /dev/null +++ b/agents/inbox/2026-08-21-B2-codex-ui-redesign-phase2.md @@ -0,0 +1,149 @@ +# Задание 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». + +--- + +## Главное +Интерфейс должен за несколько секунд отвечать: кто выполняет задачу, через какого провайдера, аккаунт и модель, сколько лимита осталось, кто подхватит при недоступности. Там, где данных нет, — честное «Н/Д». Пустое место лучше выдуманного числа.