# Веб-UI Конвенция: *как* мы пишем код приложения. Это правила оформления кода (How), а не спецификация поведения — что именно приложение показывает и какие действия обязано поддерживать, живёт в спеке OpenSpec. **Фреймворк не выбран, и до выбора эта конвенция пуста.** Здесь стоит только то, что решено и от фреймворка не зависит; всё остальное появится по итогу разведки `spa-framework-choice`, которая же заведёт запись в `research/` и ADR. Прежняя редакция описывала htmx с прямым запретом на шаг сборки и реактивные фреймворки. Решение сменилось на SPA 2026-08-10, и та редакция снята целиком: частичная подмена фрагментов, ветвление по `HX-Request` и деградация без JS к SPA не относятся ни одним пунктом. Логирование запросов — [logging.md](logging.md). Трансляция доменных ошибок наружу — [errors.md](errors.md). ## Что решено - **Приложение — SPA**, а не страницы, отрисованные сервером. Сервер отдаёт контракт данных, разметку собирает клиент. - **Приложение ставится на телефон** и запускается с ярлыка: манифест, иконки, service worker. - **Статика вшивается в бинарник** через `go:embed` и раздаётся им же. Внешнего веб-сервера под статику не заводим, бинарник остаётся самодостаточным. - **Шрифты и скрипты — со своего хоста**, без внешних. Внешних ресурсов времени выполнения нет. - **Офлайн-чтения расшифровок и очереди отправки без сети не делаем** — граница цели [web-access](../../tasks/items/web-access.md). Без сети приложение показывает состояние, а не пустой экран. - **Web Push не делаем**: уведомления идут через apprise и ntfy, цель [ready-notification](../../tasks/items/ready-notification.md). - **Записи звука в приложении не делаем** — файл выбирают системным диалогом. ## Что не решено Фреймворк, форма сборки, способ хранения состояния на клиенте, правила разбиения на компоненты, обращение к API и показ ошибок. Всё это — предмет `spa-framework-choice`; писать их наперёд, не зная фреймворка, значит написать правила, которые придётся выбросить второй раз.