docs: фреймворком приложения выбран Vue 3 с роутером 5 и сборкой Vite

- заведены записка разведки 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
This commit is contained in:
av
2026-08-11 14:51:55 +03:00
parent f1524fefd8
commit 54ca4c0e50
8 changed files with 324 additions and 29 deletions
+3 -4
View File
@@ -39,10 +39,9 @@ htmx, а здесь решено делать SPA — и перенесённы
`0600`, самодокументируемый `config.dist.toml`, проверка на старте.
- [database.md](database.md) — БД и идентификаторы: время в UTC RFC 3339, TEXT
ULID, разбор на входной границе, естественные ключи у деталей.
- [web-ui.md](web-ui.md) — веб-UI: что решено про приложение (SPA, установка на
телефон, статика в бинарнике) и что ждёт выбора фреймворка. **Почти пуста:**
редакция на htmx снята 2026-08-10 вместе со сменой решения, а новую писать не
под что до разведки `spa-framework-choice`.
- [web-ui.md](web-ui.md) — веб-UI: Vue 3 с Vite и статикой в бинарнике,
однофайловые компоненты, таблица маршрутов, состояние в экране, одна обёртка
над `fetch`, показ ошибок и состояний списка.
## Механизировано
+75 -12
View File
@@ -4,19 +4,25 @@
спецификация поведения — что именно приложение показывает и какие действия
обязано поддерживать, живёт в спеке OpenSpec.
**Фреймворк не выбран, и до выбора эта конвенция пуста.** Здесь стоит только то,
что решено и от фреймворка не зависит; всё остальное появится по итогу разведки
`spa-framework-choice`, которая же заведёт запись в `research/` и ADR.
Фреймворк выбран 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.
Прежняя редакция описывала htmx с прямым запретом на шаг сборки и реактивные
фреймворки. Решение сменилось на SPA 2026-08-10, и та редакция снята целиком:
частичная подмена фрагментов, ветвление по `HX-Request` и деградация без JS к
SPA не относятся ни одним пунктом.
**Кода приложения ещё нет.** Правила ниже выведены из выбора и из замера на
пробном экране, а не из написанного кода: первым их применяет и проверяет
`spa-skeleton`. Место, где правило разойдётся с тем, что окажется удобным, —
повод править эту запись, а не обходить её молча.
Логирование запросов — [logging.md](logging.md). Трансляция доменных ошибок
наружу — [errors.md](errors.md).
## Что решено
**Механизировано:** типы разметки и кода проверяет `vue-tsc`, и он входит в
команду сборки, а не стоит отдельным шагом. Правил линтера для кода приложения
пока нет.
## Что решено про само приложение
- **Приложение — SPA**, а не страницы, отрисованные сервером. Сервер отдаёт
контракт данных, разметку собирает клиент.
@@ -33,9 +39,66 @@ SPA не относятся ни одним пунктом.
[ready-notification](../../tasks/items/ready-notification.md).
- **Записи звука в приложении не делаем** — файл выбирают системным диалогом.
## Фреймворк и сборка
- **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](../../tasks/items/json-api-for-spa.md)), и второй словарь
на клиенте разошёлся бы с первым.
- **Отсутствие связи — состояние, а не ошибка.** Сорванный запрос показывается
строкой «связи нет», а не пустым экраном и не сообщением браузера.
- **У каждого списка три состояния и все три нарисованы:** загружается, пусто,
есть данные. Пустой список без надписи неотличим от незагруженного.
- **Текст, который видит пользователь, — русский** ([CLAUDE.md](../../CLAUDE.md),
«Язык»). Код и идентификаторы английские, включая имена компонентов и файлов.
## Что не решено
Фреймворк, форма сборки, способ хранения состояния на клиенте, правила
разбиения на компоненты, обращение к API и показ ошибок. Всё это — предмет
`spa-framework-choice`; писать их наперёд, не зная фреймворка, значит написать
правила, которые придётся выбросить второй раз.
- **Набор компонентов и стили.** Своя разметка или готовый набор — не решено, а
готовый способен удвоить собранный файл
([research/spa-framework.md](../research/spa-framework.md), «Чего разведка не
узнала»).
- **Устройство service worker и версионирование статики** — задача
[installable-pwa](../../tasks/items/installable-pwa.md).
- **Где живёт сессия и как приложение узнаёт вошедшего** — открытый вопрос
«Учётные записи» в [../architecture.md](../architecture.md).