Codex was handed Task A by mistake and duplicated the state-layer work that
Antigravity had already merged. This reissues its own scope, rebased on the
facts at 39b1f92: PHASE 1 is merged, docs/UI_STATE_CONTRACT.md now exists, and
the twelve backend gaps it declares are mapped to what the UI may and may not
render.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
14 KiB
Markdown
149 lines
14 KiB
Markdown
# Задание 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».
|
||
|
||
---
|
||
|
||
## Главное
|
||
Интерфейс должен за несколько секунд отвечать: кто выполняет задачу, через какого провайдера, аккаунт и модель, сколько лимита осталось, кто подхватит при недоступности. Там, где данных нет, — честное «Н/Д». Пустое место лучше выдуманного числа.
|