hermes-hub/agents/inbox/2026-08-21-B2-codex-ui-redesign-phase2.md
Hermes Team c6876e96b2 docs(task): reissue the UI redesign assignment against the published contract
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>
2026-08-21 08:13:46 +07:00

149 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Задание B2 (Codex): переработка интерфейса, фазы 26
## Дата поступления
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».
---
## Главное
Интерфейс должен за несколько секунд отвечать: кто выполняет задачу, через какого провайдера, аккаунт и модель, сколько лимита осталось, кто подхватит при недоступности. Там, где данных нет, — честное «Н/Д». Пустое место лучше выдуманного числа.