- заведены записка разведки docs/research/spa-framework.md со сравнением Svelte, Vue и React на одном экране и решение ADR-2026-08-11-spa-on-vue - docs/conventions/web-ui.md переписан под Vue: компоненты, маршруты, состояние, обращение к API и показ ошибок - закрыт вопрос «Приложение» в docs/architecture.md, уточнена задача spa-skeleton
8.4 KiB
Веб-UI
Конвенция: как мы пишем код приложения. Это правила оформления кода (How), а не спецификация поведения — что именно приложение показывает и какие действия обязано поддерживать, живёт в спеке OpenSpec.
Фреймворк выбран 2026-08-11 разведкой spa-framework-choice:
ADR, сравнение кандидатов в
research/spa-framework.md. Прежняя редакция
описывала htmx с прямым запретом на шаг сборки и реактивные фреймворки; она снята
целиком вместе со сменой решения на SPA 2026-08-10.
Кода приложения ещё нет. Правила ниже выведены из выбора и из замера на
пробном экране, а не из написанного кода: первым их применяет и проверяет
spa-skeleton. Место, где правило разойдётся с тем, что окажется удобным, —
повод править эту запись, а не обходить её молча.
Логирование запросов — logging.md. Трансляция доменных ошибок наружу — errors.md.
Механизировано: типы разметки и кода проверяет vue-tsc, и он входит в
команду сборки, а не стоит отдельным шагом. Правил линтера для кода приложения
пока нет.
Что решено про само приложение
- Приложение — SPA, а не страницы, отрисованные сервером. Сервер отдаёт контракт данных, разметку собирает клиент.
- Приложение ставится на телефон и запускается с ярлыка: манифест, иконки, service worker.
- Статика вшивается в бинарник через
go:embedи раздаётся им же. Внешнего веб-сервера под статику не заводим, бинарник остаётся самодостаточным. - Шрифты и скрипты — со своего хоста, без внешних. Внешних ресурсов времени выполнения нет.
- Офлайн-чтения расшифровок и очереди отправки без сети не делаем — граница цели web-access. Без сети приложение показывает состояние, а не пустой экран.
- Web Push не делаем: уведомления идут через apprise и ntfy, цель ready-notification.
- Записи звука в приложении не делаем — файл выбирают системным диалогом.
Фреймворк и сборка
- Vue 3, TypeScript, сборка Vite. Серверной отрисовки нет, надстройки над фреймворком (Nuxt) нет: она ждёт рядом процесс Node, а у нас статика в бинарнике.
- Компонент — однофайловый,
<script setup lang="ts">. Options API не пишем: два способа объявить компонент в одном приложении — второй способ делать то же самое. - Собранная статика неизменяема и адресуется хешем в имени. Имена придумывает Vite, руками их не задаём: от этого зависит обновление установленного приложения.
- Шаг сборки входит в
task gateи в сборку образа. Красная сборка статики роняет гейт наравне сgo build.
Маршруты
- Четыре экрана, одна таблица маршрутов через
createRouter. Маршруты по файлам не включаем: сборочная надстройка роутера пятой версии стоит 34 пакета в установке и на четырёх маршрутах не окупается. - Адреса обычные, а не после решётки (
createWebHistory). Отсюда требование к серверу: неизвестный путь вне/api/отдаётindex.html, а не404; пути внутри/api/в приложение не проваливаются никогда. - Экран не знает, как он открыт. Данные экран берёт по своему адресу, а не получает от предыдущего: приложение открывают по ссылке и обновляют страницу посередине.
Состояние и обращение к API
- Состояние экрана живёт в экране —
refиcomputedпо месту. Общее между экранами выносим в composable-функциюuse…. - Хранилища состояния (Pinia) не заводим, пока два экрана не потребуют одних и тех же данных одновременно. Заведём — это правка этой записи с названной причиной.
- Обращение к API — через
fetchи через одну свою обёртку. Сторонних клиентов (axios и подобных) не берём: внешних ресурсов у нас нет, а разбор ответа и отображение ошибки всё равно свои. - Обёртка — единственное место, где читается код ответа. Она же превращает
ошибку контракта в доменную ошибку приложения; экран получает готовый текст, а
не
Response.
Показ ошибок и состояний
- Текст ошибки приходит с сервера и показывается как есть. Своих текстов под коды ответа приложение не сочиняет: единая форма ошибки — обязанность API (json-api-for-spa), и второй словарь на клиенте разошёлся бы с первым.
- Отсутствие связи — состояние, а не ошибка. Сорванный запрос показывается строкой «связи нет», а не пустым экраном и не сообщением браузера.
- У каждого списка три состояния и все три нарисованы: загружается, пусто, есть данные. Пустой список без надписи неотличим от незагруженного.
- Текст, который видит пользователь, — русский (CLAUDE.md, «Язык»). Код и идентификаторы английские, включая имена компонентов и файлов.
Что не решено
- Набор компонентов и стили. Своя разметка или готовый набор — не решено, а готовый способен удвоить собранный файл (research/spa-framework.md, «Чего разведка не узнала»).
- Устройство service worker и версионирование статики — задача installable-pwa.
- Где живёт сессия и как приложение узнаёт вошедшего — открытый вопрос «Учётные записи» в ../architecture.md.