132 lines
16 KiB
Markdown
132 lines
16 KiB
Markdown
# Task 10: Чистка мёртвого слоя и подготовка релиза (hermes-android)
|
||
|
||
**Repo:** `ochenstarik-ui/hermes-android`
|
||
**Assigned to:** Antigravity (режим оркестратора, два кодера)
|
||
**Priority:** HIGH (без этого приложение нельзя опубликовать)
|
||
**Date:** 2026-08-24
|
||
**Base SHA:** результат задания 09 — указать фактический SHA при выдаче
|
||
**Зависимость:** задания 01–09 приняты. Это последнее задание цикла: оно закрывает остаток находок аудита и приводит проект к состоянию, пригодному для выпуска.
|
||
|
||
## Роли и протокол
|
||
|
||
| Роль | Модель | Что делает |
|
||
|---|---|---|
|
||
| Оркестратор | Antigravity | Разбивает работу, маршрутизирует, принимает результат |
|
||
| Кодер 1 | Gemini Flash 3.7 high | Реализация §Scope целиком |
|
||
| Кодер 2 | Gemini Pro high | Независимая проверка работы кодера 1 и доработка |
|
||
|
||
**Раунд 1 (кодер 1).** Пункты §Scope по порядку. Пункт 1 (удаление) выполняется **после** пункта 2, чтобы перенос зависимости не потерялся вместе с удаляемым файлом.
|
||
**Раунд 2 (кодер 2).** Независимая проверка: сборка релизного варианта из чистого клона, проход §Anti-checklist с явными отметками, доводка, findings по шкале.
|
||
**Раунд 3 (оркестратор).** Приёмка при собранном и установленном на устройство релизном APK с включённым R8 и при закрытии всех 63 находок аудита — итоговая таблица обязательна.
|
||
|
||
## Проблема
|
||
|
||
**1. Полторы тысячи строк прошлой архитектуры в сборке (`DEAD-01`).** Недостижимы из `MainActivity`: `feature/connections/ConnectionsScreen.kt` (435 строк), `feature/connections/ConnectionsViewModel.kt` (122), `feature/sessions/SessionsScreen.kt` (271), `feature/sessions/SessionsViewModel.kt`, `core/repository/HermesGatewayRepository.kt` (553), `core/repository/ConnectionRepository.kt`. Два репозитория с похожими именами путают при чтении и раздувают сборку.
|
||
Ловушка: `ConnectionRepository.kt:16` объявляет расширение `val Context.dataStore` — от него зависит `MigrationHelper`. Удаление файла без переноса этой строки ломает миграцию, исправленную заданием 08.
|
||
|
||
**2. Мелкий мёртвый код (`DEAD-02`).** Неиспользуемое поле `json` в `GatewayEvents.kt:178`; лишние импорты в `MainActivity`; `A2AContextBinding`, `HermesMessage`, `UnifiedMessageSource.A2A`, алиас `TokenVault.getAllConnectionIds` (`:19`), `HermesDatabase.createInMemory` — если он не был задействован заданием 04. `HermesApplication.onTerminate` (`:14-17`) на реальных устройствах не вызывается: код создаёт ложное впечатление, что `applicationScope` освобождается.
|
||
|
||
**3. Release-вариант непригоден к публикации (`BUILD-02`).** `app/build.gradle.kts:23-30` — `isMinifyEnabled = false`, `signingConfig` отсутствует; `app/proguard-rules.pro` содержит одну строку `-keepattributes`. Собрать подписанный релиз командой из README нельзя, R8 не работает, размер APK не оптимизируется.
|
||
|
||
**4. Приложение без иконки (`UI-05`).** `AndroidManifest.xml:12-14` — `android:icon="@android:drawable/sym_def_app_icon"`, системная заглушка. Ни adaptive-иконки, ни монохромной для Material You, ни каталога `mipmap`.
|
||
|
||
**5. Ни локализации, ни доступности (`UI-08`).** `stringResource` не используется ни разу — 59 текстов зашиты литералами, `strings.xml` мёртвый файл. `contentDescription = null` встречается 32 раза, в том числе на функциональных иконках переключения хоста и остановки выполнения: для TalkBack они невидимы.
|
||
|
||
**6. Тема конфликтует с edge-to-edge (`UI-09`).** `values/themes.xml` — родитель `android:Theme.Material.Light.NoActionBar` с жёстко заданными цветами системных полос, при том что `MainActivity.kt:44` вызывает `enableEdgeToEdge()`, а Compose-тема поддерживает тёмную схему. В тёмном режиме полосы и стартовое окно остаются светлыми.
|
||
|
||
**7. Экран настроек недостижим (`UI-10`).** `MainActivity.kt:156-162` объявляет маршрут `"settings"`, `navigate("settings")` не вызывается нигде. Содержимое — статический текст, утверждающий про «одноразовые WS-тикеты с TTL 30 с», хотя клиент это свойство не проверяет.
|
||
|
||
## Scope
|
||
|
||
**1. Перенос зависимости, затем удаление (`DEAD-01`).**
|
||
Сначала перенести объявление `Context.dataStore` в `MigrationHelper` (или в отдельный файл рядом с ним) и убедиться, что тест миграции задания 08 проходит.
|
||
Затем удалить шесть перечисленных файлов одним коммитом. Тесты, ссылающиеся на удаляемое, к этому моменту уже переписаны заданием 04 (`TEST-04`) — проверить, что не осталось ни одной ссылки.
|
||
|
||
**2. Мелкая чистка (`DEAD-02`).**
|
||
Удалить перечисленное. `onTerminate` убрать; если реакция на уход в фон нужна — опереться на `ProcessLifecycleOwner`, но только если это требуется заданием 08, иначе просто удалить.
|
||
`createInMemory` не трогать, если задание 04 сделало его используемым.
|
||
|
||
**3. Релизная сборка (`BUILD-02`).**
|
||
Включить `isMinifyEnabled = true` и `shrinkResources = true`. Правила ProGuard для kotlinx.serialization, Room и моделей, участвующих в сериализации, — с проверкой, что сериализация после R8 работает на устройстве, а не только компилируется.
|
||
`signingConfigs` из `keystore.properties` / переменных окружения; сам keystore в репозиторий не класть. Ключ и пароли предоставляет владелец — запросить, не генерировать самовольно ключ, которым потом будет подписан публичный релиз.
|
||
Добавить сборку релизного APK в пайплайн задания 04 (без подписи, если секретов в CI нет).
|
||
|
||
**4. Иконка (`UI-05`).**
|
||
`ic_launcher` с foreground/background/monochrome во всех плотностях. Заглушку из манифеста убрать.
|
||
|
||
**5. Строки и доступность (`UI-08`).**
|
||
Вынести тексты в `strings.xml`. `contentDescription` задать всем интерактивным иконкам; для декоративных оставить `null` осознанно, а не по умолчанию — список декоративных привести в отчёте.
|
||
Прогнать экран чата и список хостов через Accessibility Scanner, результат приложить.
|
||
|
||
**6. Тема (`UI-09`).**
|
||
Перейти на `Theme.Material3.DayNight.NoActionBar`, системные полосы прозрачные, добавить `values-night`. Проверить стартовое окно в тёмном режиме.
|
||
|
||
**7. Настройки (`UI-10`).**
|
||
Либо добавить вход в настройки и наполнить экран реальными переключателями (доступ к списку хостов, выход из аккаунта хоста, диагностика соединения, версия), либо удалить маршрут и экран. Выбор обосновать.
|
||
Утверждение про «одноразовые тикеты с TTL 30 с» либо подтвердить фактическим поведением, либо убрать: клиент не должен заявлять свойств, которых не проверяет.
|
||
|
||
**8. Итоговая сверка.**
|
||
Таблица «находка аудита → задание → статус» на все 63 позиции. Каждая — `закрыто` (с указанием задания и SHA), `перенесено` (куда и почему) или `UNVERIFIED` (что именно не удалось проверить). Пустых клеток быть не должно.
|
||
|
||
## Do not change
|
||
|
||
- Продуктовую логику заданий 01–09: это задание чистит и упаковывает, а не правит поведение.
|
||
- Схему БД.
|
||
- Протокол сопряжения — задание 07.
|
||
- Тексты при переносе в ресурсы: перенос дословный, переформулировки отдельным заданием.
|
||
|
||
## Anti-checklist
|
||
|
||
1. `ConnectionRepository` удалён вместе с `Context.dataStore`, миграция сломана. Проверить тест миграции **после** удаления, а не до.
|
||
2. Файлы удалены, но остались ссылки в тестах — сборка тестов красная, «поправили» удалением теста.
|
||
3. `isMinifyEnabled = true` включён, сборка проходит, а на устройстве падает разбор JSON: правила R8 для сериализации неполны. Требование: проверка на устройстве, а не факт компиляции.
|
||
4. `shrinkResources` выкинул ресурсы, используемые через рефлексию. Проверить экраны на устройстве после R8.
|
||
5. Ключ подписи сгенерирован исполнителем самостоятельно.
|
||
6. `keystore.properties` попал в коммит. Проверить `git log -p` на секреты, а не только рабочее дерево.
|
||
7. Иконка добавлена, но `roundIcon` в манифесте всё ещё указывает на системную заглушку.
|
||
8. Строки вынесены механически, включая те, что собираются из подстановок, — получились битые форматные строки. Проверить экраны на устройстве.
|
||
9. `contentDescription` проставлен строкой «icon» / «button» вместо описания действия.
|
||
10. Экран настроек оставлен недостижимым, но «оформлен».
|
||
11. Итоговая таблица содержит `закрыто` для находки, по которой в отчёте задания стоит `UNVERIFIED`. Сверять с фактическими отчётами, а не по памяти.
|
||
12. В отчёте `green` для незапущенной команды (`AGENTS.md §3`).
|
||
|
||
## Definition of Done
|
||
|
||
- В дереве нет шести перечисленных файлов; ссылок на них не осталось; миграция работает.
|
||
- Мелкий мёртвый код удалён; сборка без предупреждений о неиспользуемом из изменённых файлов.
|
||
- `assembleRelease` даёт подписанный APK с включённым R8; приложение установлено на устройство, проходит сценарий: добавление хоста по QR → вход → отправка промпта → получение ответа со стримом → подтверждение команды.
|
||
- Иконка отображается в лаунчере, включая круглую и монохромную темы.
|
||
- Ни одного пользовательского текста в коде; Accessibility Scanner не показывает критичных замечаний на двух основных экранах.
|
||
- Тёмная тема применяется к системным полосам и стартовому окну.
|
||
- Экран настроек либо достижим и содержателен, либо удалён; ложных утверждений о свойствах протокола в UI нет.
|
||
- Итоговая таблица закрывает все 63 находки без пустых клеток.
|
||
- Все существующие тесты зелёные, ни один не удалён и не ослаблен.
|
||
|
||
## Required tests
|
||
|
||
`app/src/test/.../DeadReferenceTest` не имеет смысла — факт удаления проверяется сборкой. Доказательства:
|
||
|
||
1. `git ls-files` не содержит удалённых путей; `grep -r` по именам классов пуст;
|
||
2. полный прогон тестов и миграции после удаления — зелёный;
|
||
3. `assembleRelease` + установка на устройство + прохождение сценария из §DoD, с логом `adb logcat` без исключений;
|
||
4. отчёт Accessibility Scanner по двум экранам;
|
||
5. скриншоты лаунчера (обычная и круглая иконка) и стартового окна в тёмной теме.
|
||
|
||
Отдельный androidTest `ReleaseSerializationTest` — разбор и сериализация моделей после R8 на устройстве.
|
||
|
||
## Required verification
|
||
|
||
```text
|
||
./gradlew --no-daemon testDebugUnitTest
|
||
./gradlew --no-daemon connectedDebugAndroidTest
|
||
./gradlew --no-daemon lint
|
||
./gradlew --no-daemon assembleDebug
|
||
./gradlew --no-daemon assembleRelease
|
||
cd hermes-pair && cargo test --all-targets && cargo clippy -- -D warnings
|
||
```
|
||
|
||
`assembleRelease` без предоставленного владельцем ключа выполняется без подписи; это указывается явно, и пункт подписи остаётся открытым, а не помечается закрытым.
|
||
|
||
## Result
|
||
|
||
`agents/antigravity/done/TASK-2026-08-24-10-cleanup-and-release.md` — разделы `## Кодер 1`, `## Кодер 2 (review + доработка)`, `## Итоговая сверка по 63 находкам`, `## Вердикт оркестратора`, `## Вопросы владельцу` (ключ подписи, судьба экрана настроек).
|