- ссылки на tasks/ROADMAP.md переведены на BACKLOG.md и на openspec/specs, упоминания целей — на задачи, которые эту работу делают; - судьи документации нашли восемь расхождений: Purpose спеки pipeline объявлял неописанным то, что уже нормирован пятью требованиями, вид времени в конвенции спорил со схемой, а квоты, шесть часов и отказ от Web Push жили сразу в двух документах без ссылки друг на друга; - два числа получили провенанс: 259 200 запросов в сутки и потолок в шесть часов теперь ведут к записке разведки, а не читаются как замер.
113 lines
9.1 KiB
Markdown
113 lines
9.1 KiB
Markdown
# Веб-UI
|
||
|
||
Конвенция: *как* мы пишем код приложения. Это правила оформления кода (How), а не
|
||
спецификация поведения — что именно приложение показывает и какие действия
|
||
обязано поддерживать, живёт в спеке OpenSpec.
|
||
|
||
Фреймворк выбран 2026-08-11 разведкой `spa-framework-choice`:
|
||
[ADR](../adr/ADR-2026-08-11-spa-on-vue.md), сравнение кандидатов в
|
||
[research/spa-framework.md](../research/spa-framework.md). Прежняя редакция
|
||
описывала htmx с прямым запретом на шаг сборки и реактивные фреймворки; она снята
|
||
целиком вместе со сменой решения на SPA 2026-08-10.
|
||
|
||
**Кода приложения ещё нет.** Правила ниже выведены из выбора и из замера на
|
||
пробном экране, а не из написанного кода: первым их применяет и проверяет
|
||
`spa-skeleton`. Место, где правило разойдётся с тем, что окажется удобным, —
|
||
повод править эту запись, а не обходить её молча.
|
||
|
||
Логирование запросов — [logging.md](logging.md). Трансляция доменных ошибок
|
||
наружу — [errors.md](errors.md).
|
||
|
||
**Механизировано:** типы разметки и кода проверяет `vue-tsc`, и он входит в
|
||
команду сборки, а не стоит отдельным шагом. Правил линтера для кода приложения
|
||
пока нет.
|
||
|
||
## Что решено про само приложение
|
||
|
||
- **Приложение — SPA**, а не страницы, отрисованные сервером. Сервер отдаёт
|
||
контракт данных, разметку собирает клиент.
|
||
- **Приложение ставится на телефон** и запускается с ярлыка: манифест, иконки,
|
||
service worker.
|
||
- **Статика вшивается в бинарник** через `go:embed` и раздаётся им же. Внешнего
|
||
веб-сервера под статику не заводим, бинарник остаётся самодостаточным.
|
||
- **Шрифты и скрипты — со своего хоста**, без внешних. Внешних ресурсов времени
|
||
выполнения нет.
|
||
- **Офлайн-чтения расшифровок и очереди отправки без сети не делаем** — граница
|
||
из [паспорта](../passport.md). Без сети приложение показывает состояние, а не
|
||
пустой экран.
|
||
- **Web Push не делаем**: уведомления идут через apprise и ntfy — решение живёт
|
||
в [architecture.md](../architecture.md), «Уведомления», делает его
|
||
[ntfy-delivery](../../tasks/items/ntfy-delivery.md).
|
||
- **Записи звука в приложении не делаем** — граница из
|
||
[паспорта](../passport.md), «Диктофон»; файл выбирают системным диалогом.
|
||
|
||
## Фреймворк и сборка
|
||
|
||
- **Vue 3, TypeScript, сборка Vite.** Серверной отрисовки нет, надстройки над
|
||
фреймворком (Nuxt) нет: она ждёт рядом процесс Node, а у нас статика в
|
||
бинарнике.
|
||
- **Компонент — однофайловый, `<script setup lang="ts">`.** Options API не
|
||
пишем: два способа объявить компонент в одном приложении — второй способ
|
||
делать то же самое.
|
||
- **Собранная статика неизменяема и адресуется хешем в имени.** Имена придумывает
|
||
Vite, руками их не задаём: от этого зависит обновление установленного
|
||
приложения.
|
||
- **Шаг сборки входит в `task gate` и в сборку образа.** Красная сборка статики
|
||
роняет гейт наравне с `go build`.
|
||
|
||
## Маршруты
|
||
|
||
- **Одна таблица маршрутов** через `createRouter`. Маршруты по файлам не
|
||
включаем: сборочная надстройка роутера пятой версии стоит 34 пакета в
|
||
установке и на нашем числе маршрутов не окупается
|
||
([research/spa-framework.md](../research/spa-framework.md), «Vue»). Сколько
|
||
экранов и какие — не здесь: состав нормирует спека приложения, а до неё его
|
||
держит [spa-skeleton](../../tasks/items/spa-skeleton.md).
|
||
- **Адреса обычные, а не после решётки** (`createWebHistory`). Отсюда требование
|
||
к серверу: неизвестный путь **вне** `/api/` отдаёт `index.html`, а не `404`;
|
||
пути внутри `/api/` в приложение не проваливаются никогда.
|
||
- **Экран не знает, как он открыт.** Данные экран берёт по своему адресу, а не
|
||
получает от предыдущего: приложение открывают по ссылке и обновляют страницу
|
||
посередине.
|
||
|
||
## Состояние и обращение к API
|
||
|
||
- **Состояние экрана живёт в экране** — `ref` и `computed` по месту. Общее между
|
||
экранами выносим в composable-функцию `use…`.
|
||
- **Хранилища состояния (Pinia) не заводим**, пока два экрана не потребуют одних
|
||
и тех же данных одновременно. Заведём — это правка этой записи с названной
|
||
причиной.
|
||
- **Обращение к API — через `fetch` и через одну свою обёртку.** Сторонних
|
||
клиентов (axios и подобных) не берём: внешних ресурсов у нас нет, а разбор
|
||
ответа и отображение ошибки всё равно свои.
|
||
- **Обёртка — единственное место, где читается код ответа.** Она же превращает
|
||
ошибку контракта в доменную ошибку приложения; экран получает готовый текст, а
|
||
не `Response`.
|
||
- **Сессия живёт кукой `transcriber_session`**, и приложение её не читает: кука
|
||
`HttpOnly`, браузер шлёт её сам, а вошедшего экран узнаёт по ответу API. Норма
|
||
— [access](../../openspec/specs/access/spec.md).
|
||
|
||
## Показ ошибок и состояний
|
||
|
||
- **Текст ошибки приходит с сервера и показывается как есть.** Своих текстов под
|
||
коды ответа приложение не сочиняет: единая форма ошибки — обязанность API
|
||
([json-api-for-spa](../../tasks/items/json-api-for-spa.md)), и второй словарь
|
||
на клиенте разошёлся бы с первым.
|
||
- **Отсутствие связи — состояние, а не ошибка.** Сорванный запрос показывается
|
||
строкой «связи нет», а не пустым экраном и не сообщением браузера.
|
||
- **У каждого списка три состояния и все три нарисованы:** загружается, пусто,
|
||
есть данные. Пустой список без надписи неотличим от незагруженного.
|
||
- **Текст, который видит пользователь, — русский** ([CLAUDE.md](../../CLAUDE.md),
|
||
«Язык»). Код и идентификаторы английские, включая имена компонентов и файлов.
|
||
|
||
## Что не решено
|
||
|
||
- **Набор компонентов и стили.** Своя разметка или готовый набор — не решено, а
|
||
готовый способен удвоить собранный файл
|
||
([research/spa-framework.md](../research/spa-framework.md), «Чего разведка не
|
||
узнала»).
|
||
- **Устройство service worker и версионирование статики** — задача
|
||
[installable-pwa](../../tasks/items/installable-pwa.md).
|
||
- **Как связываются пользователь Telegram и пользователь веба** — открытый вопрос
|
||
«Учётные записи» в [../architecture.md](../architecture.md).
|