# Задание A16 (Antigravity Flash, другой ПК): веб-клиент

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

## База
Проверочный HEAD на момент выдачи: **`7ae4a28`**.

## Ветка
`antigravity/web-client`

---

## Порядок работы с git

```
cd <каталог репозитория>; git fetch origin --prune; git status
git checkout main; git pull --ff-only origin main
git checkout -b antigravity/web-client
```

Сначала коммит, потом `git push -u origin antigravity/web-client`. В конце — push и проверка:

```
git status                                       <- дерево чистое
git log --oneline -1 origin/antigravity/web-client  <- ваш финальный коммит
```

---

## Что принято по A14

Приложены восемь скриншотов, пять содержательных. Скриншот «Аккаунты» подтверждает компактные карточки фиксированной высоты без раскрытия. В `unified_health` добавлено свойство `auth_label_ru` — правка вне заявленной зоны, но безобидная. Слияние без конфликтов, 316 passed.

**Что не сошлось, и это важно для нового задания.**

`accounts_view.py` и `quotas_view.py` не изменялись — второе задание подряд. Пункты при этом оказались закрыты чужой работой: компактные карточки пришли из `components.py`, а пункт навигации «Квоты и лимиты» убрал Codex ещё в `40b3466`. То есть в отчёте они числятся сделанными вами, а сделаны не вами.

**Три скриншота из восьми — пустой чёрный кадр**: шаг мастера Grok, окно роли, детали аккаунта. Два весят ровно по 1148 байт. Живое подключение Grok ими не подтверждено, а именно оно и требовалось.

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

---

## Решение владельца: переходим на веб-интерфейс

У владельца сервер с Ubuntu Server и Xubuntu. Веб-интерфейс там строго лучше десктопа. Десктоп остаётся рабочим до паритета — `router/ui/**` не трогаем вообще.

**Ваша часть — клиент.** Параллельно A15 делает серверную.

**Читать перед началом: `docs/web-api/CONTRACT.md`.** Это единственный источник истины. Вы пишете против документа, а не против чужого кода — сервера вы не увидите до слияния. Любое отклонение от контракта — дефект, даже если у вас всё работает.

## Вы не заблокированы готовностью сервера

Рядом лежит **`docs/web-api/snapshot.example.json`** — настоящий снапшот с машины владельца: 63 профиля, 187 КБ, почты замаскированы, секретов нет.

Разрабатывайте против этого файла. Пока сервера нет, `fetch('/api/snapshot')` подменяется загрузкой фикстуры — один флаг в начале `app.js`. Когда сервер появится, переключение будет стоить одну строку.

---

## P0-1. Клиент без сборки

```
src/antigravity_provider/router/web/static/
    index.html
    app.js
    style.css
```

**Обычный JavaScript и `fetch`. Без npm, без сборки, без фреймворка.**

Это не вкусовщина. Проект ведут агенты на трёх машинах; любой шаг сборки означает согласование версий Node и дрейф lock-файлов. Страница, которую можно открыть файлом и отладить в браузере без инструментов, снимает целый класс проблем. React в проекте отклонён и раньше, по той же причине.

Опрос `/api/snapshot` раз в 5 секунд и после каждого действия.

**Обязательно:** поле `seq` в снапшоте монотонно растёт. **Ответ с `seq` меньше уже применённого игнорировать** — иначе поздний ответ затрёт свежий. Этот дефект в десктопе уже ловили, повторять не нужно.

## P0-2. Экраны — порядок по ценности

Не пытайтесь сразу повторить все девять разделов десктопа. Порядок такой, и он не случаен:

**1. Аккаунты** — самый нужный экран и самая старая жалоба владельца.

- плотный список фиксированной высоты: провайдер, идентичность, роль, состояние авторизации, квота — одной строкой, **без раскрытия**;
- **квота видна сразу**, числом и полосой, **с указанием пула и периода**. У Antigravity четыре пула, «просто процент» вводит в заблуждение — нужно `Claude/GPT — неделя`, а не `62%`. В фикстуре эти данные есть, посмотрите структуру `quotas`;
- шестнадцать аккаунтов читаются без прокрутки внутрь карточек.

**2. Обзор** — готовность, роли, схема маршрутизации, последние события.

**3. Маршрутизация** — цепочка основной → резервы, активный узел, причина последнего переключения.

Дальше — по остатку времени. Пустой раздел не заводить: либо содержание, либо нет пункта меню.

## P0-3. Действия

Все через `POST /api/action` с телом `{"action": "...", "data": {...}}`. Семнадцать имён перечислены в контракте.

Ответ `{"ok": bool, "message": str}`. **`ok: false` приходит с кодом `200`** — это нормальный отказ действия, а не ошибка сети. Обрабатывать как результат, а не как сбой.

Требования к обратной связи, купленные прошлыми раундами:

- **результат действия виден в месте действия**, а не в строке состояния внизу, где его не замечают;
- **при ошибке модальное окно не закрывается** — человек должен видеть причину;
- долгие операции (`refresh_all`, `test`) возвращают «запущено» сразу, результат приходит следующим снапшотом. Показывать это как выполняющееся, а не как зависшее.

## P0-4. Честность на экране

Правило проекта действует в вебе без исключений.

- **Ни одного числа, идентификатора или названия модели без измерения.**
- Нет данных — «Н/Д» **и причина рядом**, доступная пользователю. Причина уже приходит в снапшоте полем `unavailable_reason`. Владелец прямо спрашивал: «что означает н/д в маршрутизации» — прочерк без объяснения его не устраивает.
- **Отличать «данных нет» от «данные ещё грузятся».** Найдено при проверке прошлого раунда: квота появляется только после фонового опроса, а до него карточки стоят пустыми и это выглядит как поломка. A15 добавит в снапшот поле состояния — до его появления показывайте загрузку по отсутствию значения при `is_stale`.

## P0-5. Авторизация на сервере без экрана — сказать правду

Разобрано по коду:

| Провайдер | Поток | На headless-сервере |
|---|---|---|
| OpenAI Codex | device-code | **работает** — код вводится с любой машины |
| Grok | device-code | **работает** |
| Antigravity | redirect на localhost | **не работает** |
| Claude | redirect на localhost | **не работает** |

У двух последних редирект придёт на машину пользователя, а не сервера.

A15 сообщит клиенту доступность потоков с причиной. **Недоступный поток показывать отключённым с объяснением и обходным путём, а не рабочей кнопкой, которая молча ничего не сделает.** Это то же правило честности: кнопка, обещающая невозможное, — та же ложь, что выдуманная квота.

Для device-code повторить то, что уже отработано в десктопе: пронумерованная последовательность, код крупно и моноширинным, кнопки копирования ссылки и кода. Владелец жаловался «появляется код, куда его вставлять, не понятно» — не повторяйте.

## P0-6. Оформление

Тёмная тема как основная — по образцу существующего десктопа, чтобы переход не выглядел другой программой. Семантика цвета прежняя: зелёный работает, янтарный предупреждение, красный ошибка, серый нет данных. **Золото остаётся брендом, а не статусом.**

Ширина от 1280 и выше. Мобильную вёрстку не делать.

---

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

- Ваши файлы: `src/antigravity_provider/router/web/static/**`, `tests/test_web_client_*.py`. **Не ваши:** `router/web/` кроме `static/`, `router/ui/**`, backend целиком.
- Контракт **не менять в одностороннем порядке**. Нужна правка — скажите, она согласуется с A15.
- Секретов на клиенте не хранить и в URL не передавать.
- Тег `v0.1.1` не создавать.

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

1. Ветка в `origin`, финальный коммит виден, `git status` чист.
2. Клиент работает против `snapshot.example.json` без сервера; переключение на живой API — не более одной строки.
3. Никакой сборки: страница открывается и отлаживается без npm и без инструментов.
4. Ответ с устаревшим `seq` игнорируется; проверено тестом.
5. «Аккаунты»: плотный список без раскрытия, квота видна сразу с пулом и периодом, шестнадцать аккаунтов без внутренней прокрутки.
6. «Обзор» и «Маршрутизация» на реальных данных фикстуры.
7. У каждого «Н/Д» доступна причина; загрузка отличается от отсутствия данных.
8. `ok: false` обрабатывается как результат, а не как сбой; ошибка видна в месте действия; окно при ошибке не закрывается.
9. Недоступные на сервере потоки авторизации показаны отключёнными с причиной и обходным путём.
10. **Скриншоты каждого экрана — открытые и проверенные.** Пустой кадр не прикладывать.
11. `ruff check .` чисто; релизный гейт остаётся 7/7.
12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.

## Главное

Веб-интерфейс — единственный способ пользоваться Hub на сервере владельца. Первый экран, который должен заработать по-настоящему, — «Аккаунты» с видимыми квотами: это самая старая незакрытая жалоба, и она тянется с самого начала проекта.

## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`. Сдано только после появления коммита в `origin`.
