Нарезка задач под целевое состояние: PWA, вход, хранилище
Роадмап: web-access переименована под приложение, которое ставится на телефон; заведена цель ready-notification — уведомление о готовности без открытого приложения. Беклог: десять задач. Многопользовательская цепочка (oidc-login, record-ownership, telegram-account-link), веб (json-api-for-spa, spa-skeleton, upload-and-status-screen, records-list-screen, installable-pwa), уведомления через apprise и ntfy, разведка выбора фреймворка. Очередь: долги, хранилище, вход, приложение. Решение сменилось с htmx на SPA, поэтому conventions/web-ui.md снята целиком и оставлена честной строкой до итога разведки. Открытые вопросы архитектуры, границы паспорта и триггеры метки ревью приведены в соответствие.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# ✨ Сделать приложение устанавливаемым на телефон
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфра
|
||||
- **Зачем:** Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Двигает пункты 5 и 6 «Завершения» цели: приложение ставится с телефона и
|
||||
запускается с ярлыка без адресной строки, а открытое без сети показывает это
|
||||
состоянием.
|
||||
|
||||
Берётся после того, как есть что ставить, — то есть после
|
||||
`records-list-screen`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- манифест приложения: имя, иконки под размеры экранов, цвета, режим показа;
|
||||
- service worker: регистрация, обновление версии, поведение без сети;
|
||||
- раздача манифеста и service worker с правильными заголовками и без длинного
|
||||
кэша;
|
||||
- версионирование собранной статики, чтобы установленное приложение обновлялось;
|
||||
- `docs/conventions/web-ui.md` — раздел про статику и кэш;
|
||||
- `docs/security.md` — service worker перехватывает запросы, и это новая
|
||||
поверхность.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Приложение предлагается к установке и после установки запускается с ярлыка на
|
||||
отдельном экране, без адресной строки. Оракул — проверка на телефоне и
|
||||
сверка манифеста инструментом браузера.
|
||||
- Открытое без сети приложение показывает состояние «связи нет», а не пустую
|
||||
страницу и не ошибку браузера. Оракул — тест либо ручная проверка с
|
||||
выключенной сетью.
|
||||
- Новая выкладка доходит до установленного приложения: старая статика из кэша не
|
||||
переживает обновление. Оракул — установка, выкладка изменённой версии,
|
||||
перезапуск приложения показывает новую.
|
||||
- Service worker не кэширует ответы API и не отдаёт чужие данные после смены
|
||||
сессии. Оракул — тест: после выхода и входа другой учётной записью список
|
||||
записей чужих не содержит.
|
||||
|
||||
## Рамки
|
||||
|
||||
Офлайн-чтения готовых расшифровок и очереди отправки без сети **не делаем** —
|
||||
это за границей цели. Web Push не делаем: уведомления идут через apprise и ntfy,
|
||||
цель `ready-notification`.
|
||||
@@ -0,0 +1,44 @@
|
||||
# ✨ Свести приём и чтение записей к одному контракту для приложения
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Двигает пункты 1, 2 и 4 «Завершения» цели: экраны заводят задачу, видят её
|
||||
состояние и листают список — всё через один контракт.
|
||||
|
||||
Обработчик `GET /api/status/:id` сегодня отвечает `404` на **любую** ошибку
|
||||
чтения, включая сбой базы, а `POST /api/audio` — `500` на любую ошибку заведения,
|
||||
включая негодный файл. Экран, построенный на таком контракте, показывает «не
|
||||
найдено» при упавшей базе.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `POST /api/audio` и `GET /api/status/:id` — коды ответа и форма ошибки;
|
||||
- новый эндпоинт списка своих записей с постраничным чтением;
|
||||
- единая точка отображения доменной ошибки в код ответа — её сегодня нет
|
||||
([conventions/errors.md](../../docs/conventions/errors.md));
|
||||
- `internal/contract` — доменные ошибки под отображение;
|
||||
- `internal/controller/http` целиком;
|
||||
- `docs/architecture.md`, раздел «Единые точки проекта».
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Сбой базы при чтении задачи даёт `500`, а не `404`. Оракул — тест с
|
||||
репозиторием, возвращающим ошибку драйвера.
|
||||
- Негодный файл даёт `400` с человекочитаемым текстом, а не `500`. Оракул —
|
||||
тест: файл, который отвергает разбор метаданных.
|
||||
- Тело ошибки одной формы на всех эндпоинтах и не содержит сырого `err.Error()`.
|
||||
Оракул — тест на четырёх ветвях отказа: форма совпадает, текста внутренней
|
||||
ошибки в теле нет.
|
||||
- Список записей отдаётся страницами и упорядочен по времени создания. Оракул —
|
||||
тест на выборке больше страницы.
|
||||
- Отображение ошибки живёт в одной функции, и она названа в
|
||||
`docs/architecture.md`. Оракул — `task gate`, шаг `docs.py check`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Аутентификацию и владельца не заводим — это `oidc-login` и `record-ownership`;
|
||||
задача про форму контракта. Публичный контракт после мерджа обратной правкой не
|
||||
откатывается.
|
||||
@@ -0,0 +1,46 @@
|
||||
# ✨ Отправлять готовый текст через apprise и ntfy
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** Пользователь веба узнаёт о готовности только опросом с открытого экрана.
|
||||
- **Теги:** goal:ready-notification
|
||||
|
||||
Двигает все пять пунктов «Завершения» цели: готовый текст и отказ доходят до
|
||||
пользователя веба без открытого приложения, адрес канала свой у каждого, а отказ
|
||||
канала задачу не роняет.
|
||||
|
||||
Сегодня `completeJob` и `failJob` отвечают только источнику `telegram`;
|
||||
источник `api` не получает ничего. Здесь появляется второй способ доставки, и
|
||||
выбор между ними обязан остаться в одной точке — той же, что сейчас.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/contract` — интерфейс отправителя уведомления;
|
||||
- `internal/service`, `completeJob` и `failJob` — выбор канала по источнику
|
||||
задачи;
|
||||
- новый адаптер поверх apprise либо прямого HTTP к ntfy;
|
||||
- адрес канала у учётной записи: колонка и её миграция, экран настройки;
|
||||
- секция конфигурации: адрес сервера ntfy, способ вызова apprise;
|
||||
- `docs/architecture.md` — новая внешняя зависимость и чем она отказывает;
|
||||
- `docs/security.md` — текст расшифровки уходит на внешний сервис;
|
||||
- `docs/database.md` — настройки числами.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Задача из веба, дошедшая до `done`, отправляет текст в канал владельца.
|
||||
Оракул — тест с подставным отправителем: вызов ровно один, с текстом задачи и
|
||||
адресом владельца.
|
||||
- Задача из Telegram по-прежнему отвечает ботом и вторым каналом не дублируется.
|
||||
Оракул — тот же тест на источнике `telegram`: подставной отправитель ntfy не
|
||||
вызван.
|
||||
- Недоступный канал уведомлений не мешает задаче завершиться: состояние `done` и
|
||||
текст в приложении остаются. Оракул — тест с отправителем, возвращающим
|
||||
ошибку: задача в `done`, текст на месте, в логе одна запись уровня `WARN`.
|
||||
- Отказ задачи доходит тем же каналом и тем же человекочитаемым текстом, что
|
||||
видит пользователь Telegram. Оракул — тест на ветке `failJob`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Web Push с VAPID и своим хранением подписок не делаем. Своего сервера ntfy не
|
||||
поднимаем — адрес приходит конфигом. Текст расшифровки уходит на внешний сервис,
|
||||
и это сдвиг периметра: строка в `docs/security.md` обязательна.
|
||||
@@ -0,0 +1,50 @@
|
||||
# ✨ Пускать в приложение только после входа через OIDC
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
|
||||
- **Теги:** goal:multi-user, question
|
||||
|
||||
Двигает пункт 1 «Завершения» цели: неаутентифицированный запрос к записям не
|
||||
проходит ни к странице, ни к API.
|
||||
|
||||
Провайдер — Authelia по OIDC. Своей регистрации и своих паролей не делаем, это
|
||||
граница из [паспорта](../../docs/passport.md). Разграничения записей по владельцу
|
||||
здесь ещё нет: после входа видно всё, что видно сейчас, — этим занимается
|
||||
`record-ownership`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- новые эндпоинты входа и выхода, обработка ответа провайдера;
|
||||
- `POST /api/audio` и `GET /api/status/:id` — оба уходят за аутентификацию;
|
||||
- `GET /health` и `GET /metrics` — решить и записать, остаются ли открытыми;
|
||||
- хранение сессии: таблица или подписанная кука;
|
||||
- секция конфигурации под провайдера: адрес, идентификатор клиента, секрет;
|
||||
- `docs/security.md` — периметр меняется, и первая его строка перестаёт быть
|
||||
верной;
|
||||
- `config.dist.toml` и `internal/config`.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Запрос к `POST /api/audio` и `GET /api/status/:id` без сессии получает отказ, а
|
||||
не заводит задачу и не отдаёт текст. Оракул — тест на обоих эндпоинтах без
|
||||
куки: код ответа 401 либо 302 на вход, тело без данных задачи.
|
||||
- Сессия переживает перезапуск приложения. Оракул — тест: запрос с прежней кукой
|
||||
после пересоздания сервера проходит.
|
||||
- Выход из сессии закрывает доступ. Оракул — тест: после выхода тот же запрос
|
||||
получает отказ.
|
||||
- Секрет провайдера не попадает ни в лог, ни в ответ. Оракул — тест на отсутствие
|
||||
значения секрета в записанном выводе логгера.
|
||||
- Первая строка `docs/security.md` описывает новый периметр. Оракул — `task
|
||||
gate`, шаг `docs.py check`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Владельца у записи здесь не заводим и выборку не сужаем: после входа видно
|
||||
столько же, сколько сейчас. Инвариант «бот отвечает только тем, кто в белом
|
||||
списке» не трогаем — он живёт до `telegram-account-link`.
|
||||
|
||||
## Вопросы
|
||||
|
||||
Остаются ли `GET /health` и `GET /metrics` открытыми? Прокси и система сбора
|
||||
метрик сессии не имеют, но и наружу их отдавать незачем.
|
||||
@@ -0,0 +1,25 @@
|
||||
# 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Запланировано
|
||||
- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
|
||||
|
||||
Задача уходит в работу — человек закрывает приложение и получает сообщение,
|
||||
когда текст готов. Пользователь Telegram это уже имеет: бот отвечает сам.
|
||||
Пользователь веба — нет.
|
||||
|
||||
Доставку берём внешнюю: apprise как отправитель, ntfy как канал. Web Push с
|
||||
VAPID и собственным хранением подписок за целью **не стоит** — это отдельная
|
||||
инфраструктура ради того же результата.
|
||||
|
||||
## Завершение
|
||||
|
||||
1. Готовый текст доходит до пользователя веба сообщением, без открытого
|
||||
приложения.
|
||||
2. Отказ задачи доходит тем же путём и тем же человекочитаемым текстом, что
|
||||
видит пользователь Telegram.
|
||||
3. Адрес канала уведомлений задаёт пользователь, а не общий конфиг: у каждого
|
||||
свой.
|
||||
4. Недоступность канала уведомлений не роняет задачу и не мешает ей завершиться:
|
||||
текст остаётся в приложении.
|
||||
5. Пользователь Telegram получает ответ по-прежнему ботом, а не вторым каналом.
|
||||
@@ -0,0 +1,38 @@
|
||||
# ✨ Привязать запись к владельцу и отдавать только свои
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
|
||||
- **Теги:** goal:multi-user
|
||||
|
||||
Двигает пункт 2 «Завершения» цели: выборка чужой записи по её идентификатору
|
||||
возвращает «не найдено», а не содержимое.
|
||||
|
||||
Берётся после `oidc-login`: до входа неизвестно, кто владелец.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- таблица задач и таблица файлов — колонка владельца и её миграция;
|
||||
- `internal/contract`, интерфейсы репозиториев: чтение сужается владельцем;
|
||||
- `internal/adapter/repo/sqlite` — все четыре запроса задач;
|
||||
- `internal/service`, оба метода заведения задачи;
|
||||
- `GET /api/status/:id` — ответ на чужую запись;
|
||||
- `docs/database.md` — схема и правило выборки.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Запрос чужой записи по её идентификатору возвращает «не найдено», а не
|
||||
содержимое и не «доступ запрещён». Оракул — тест: две сессии, задача первой
|
||||
запрашивается второй, ответ 404 и пустое тело.
|
||||
- Запись, заведённая из веба, принадлежит вошедшему; заведённая ботом —
|
||||
пользователю, за которым закреплён чат. Оракул — тест на оба входа со сверкой
|
||||
колонки владельца.
|
||||
- Выборка воркера владельцем **не** сужается: конвейер обрабатывает записи всех.
|
||||
Оракул — тест: задачи двух владельцев проходят конвейер одним воркером.
|
||||
- База заводится с чистого листа, колонка владельца обязательна и без умолчания.
|
||||
Оракул — прогон миграций на пустой базе и попытка вставки без владельца.
|
||||
|
||||
## Рамки
|
||||
|
||||
Совместного доступа, ролей и передачи записи другому не делаем: владелец один и
|
||||
неизменяем. Данные не переносим — чистый лист.
|
||||
@@ -0,0 +1,37 @@
|
||||
# ✨ Сделать экран списка своих записей и чтения текста
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Двигает пункты 3 и 4 «Завершения» цели: список своих записей открывается и
|
||||
листается, а готовый текст читается и копируется целиком.
|
||||
|
||||
Берётся после `upload-and-status-screen`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- новый экран списка и экран одной записи;
|
||||
- постраничное чтение из контракта API;
|
||||
- копирование текста в буфер обмена;
|
||||
- показ записи, которая ещё в работе, — тем же состоянием, что на экране
|
||||
загрузки.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Список показывает записи вошедшего, новые сверху, и листается дальше первой
|
||||
страницы. Оракул — тест экрана на подставном API с двумя страницами.
|
||||
- Текст готовой записи открывается целиком, без деления на части. Оракул — тест
|
||||
на записи с текстом длиннее предела сообщения Telegram: на экране весь текст.
|
||||
- Текст копируется одним действием. Оракул — тест: после действия в буфере обмена
|
||||
тот же текст.
|
||||
- Запись в работе показывается состоянием и доходит до текста без перезагрузки
|
||||
экрана. Оракул — тест: подставной API отвечает `transcribe`, затем `done`;
|
||||
экран переходит к тексту сам.
|
||||
|
||||
## Рамки
|
||||
|
||||
Поиска по тексту, переименования и удаления записей не делаем. Правки текста не
|
||||
делаем — это граница из [паспорта](../../docs/passport.md): расшифровку отдаём как
|
||||
есть.
|
||||
@@ -0,0 +1,34 @@
|
||||
# 🔬 Выбор фреймворка для приложения
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфра
|
||||
- **Зачем:** Конвенция веб-UI написана под htmx, а решено делать SPA: до выбора фреймворка не заводится ни сборка, ни первый экран.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Конвенция [web-ui.md](../../docs/conventions/web-ui.md) описывала htmx с прямым
|
||||
запретом на шаг сборки и реактивные фреймворки. Решение сменилось на SPA, и
|
||||
теперь эта конвенция ждёт переписывания — но написать её не под что, пока
|
||||
фреймворк не назван.
|
||||
|
||||
Разведка, а не задача: исход — знание и переписанная конвенция, а не работающий
|
||||
экран.
|
||||
|
||||
## Вопрос
|
||||
|
||||
Какой фреймворк берём под приложение на четыре экрана, которое собирается в
|
||||
статику, вшивается в бинарник Go и ставится на телефон как PWA.
|
||||
|
||||
## Куда ляжет ответ
|
||||
|
||||
`docs/research/spa-framework.md` — сравнение с числами: размер собранной статики,
|
||||
время холодной сборки, число зависимостей. Выбор и причина отказа от остальных —
|
||||
в `docs/adr/`, потому что переделка на другой фреймворк стоит дороже переписывания
|
||||
одного файла. Следом переписывается `docs/conventions/web-ui.md`, и до тех пор
|
||||
она стоит честной строкой «фреймворк не выбран».
|
||||
|
||||
## Рамки
|
||||
|
||||
Сравнение идёт на одном и том же пробном экране: список записей с опросом
|
||||
состояния. Кандидаты названы человеком; сегодня в разговоре звучали Svelte, Vue
|
||||
и React, все с Vite. Мера — размер статики, простота вшивания в `go:embed` и
|
||||
цена шага сборки в гейте, а не популярность.
|
||||
@@ -0,0 +1,43 @@
|
||||
# ✨ Собрать каркас приложения и раздать его из бинарника
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфра
|
||||
- **Зачем:** Экранов нет и собирать их нечем: ни сборки фронтенда, ни раздачи статики в проекте не существует.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Двигает «Завершение» цели опосредованно: сам по себе каркас пользователю ничего
|
||||
не даёт, но без него ни один экран не соберётся. Видимая польза — приложение
|
||||
открывается и показывает, что оно живо и кто вошёл.
|
||||
|
||||
Берётся после `spa-framework-choice`: собирать не на чем, пока фреймворк не
|
||||
выбран.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- новый каталог фронтенда: исходники, зависимости, конфигурация сборки;
|
||||
- `Taskfile.yml` — шаг сборки статики и его место в `task gate` и `task image`;
|
||||
- `Dockerfile` — сборка статики внутри образа, чтобы выкладка не зависела от
|
||||
машины разработчика;
|
||||
- раздача статики из бинарника через `go:embed` и роут под неё;
|
||||
- `.gitignore` — каталог зависимостей и собранная статика;
|
||||
- `docs/conventions/web-ui.md` — переписывается под выбранный фреймворк;
|
||||
- `docs/architecture.md` — новый компонент и новая внешняя зависимость сборки.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Статика собирается одной командой и вшивается в бинарник: запущенный бинарник
|
||||
отдаёт приложение без каталога рядом. Оракул — сборка, запуск бинарника из
|
||||
пустого каталога, запрос корня возвращает разметку приложения.
|
||||
- Образ собирается на чистой машине без предустановленного окружения фронтенда.
|
||||
Оракул — `task image` без локально установленных зависимостей фронтенда.
|
||||
- Приложение показывает, кто вошёл, и обращается к API живого сервера. Оракул —
|
||||
тест либо ручная проверка на запущенном сервере с сессией.
|
||||
- Шаг сборки статики входит в `task gate` и краснеет при ошибке сборки. Оракул —
|
||||
намеренно сломанный исходник роняет `task gate`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Экранов, кроме заглушки с именем вошедшего, не делаем — это `upload-and-status-screen`
|
||||
и `records-list-screen`. Установку на телефон не делаем — это `installable-pwa`.
|
||||
Появление шага сборки в гейте меняет требования к машине разработчика: это
|
||||
решение уровня ADR, и оно записывается.
|
||||
@@ -0,0 +1,43 @@
|
||||
# ✨ Сопоставить пользователя Telegram с учётной записью
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
|
||||
- **Теги:** goal:multi-user
|
||||
|
||||
Двигает пункты 4 и 5 «Завершения» цели: записи из бота видны тому же человеку в
|
||||
приложении, а белый список перестаёт быть отдельным механизмом — право писать
|
||||
боту выводится из учётной записи.
|
||||
|
||||
Берётся после `record-ownership`: связывать не с чем, пока у записи нет
|
||||
владельца.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- связь учётной записи с чатом Telegram: таблица и её миграция;
|
||||
- команда бота для привязки и способ её подтвердить;
|
||||
- `internal/controller/tg`, проверка отправителя — сегодня `slices.Contains` по
|
||||
строке автора;
|
||||
- ключ конфигурации `[server] users_while_list` — он уходит;
|
||||
- `config.dist.toml`, `internal/config`;
|
||||
- `docs/security.md`, раздел «Что разграничивает доступ»;
|
||||
- инвариант «Бот отвечает только тем, кто в белом списке» в `CLAUDE.md`.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Сообщение от непривязанного чата обрабатывать не начинаем и файл не скачиваем.
|
||||
Оракул — тест: обновление от неизвестного чата не заводит задачу и не ходит в
|
||||
сеть.
|
||||
- Привязка идёт по идентификатору чата, а не по имени пользователя: смена имени
|
||||
в Telegram доступа не меняет. Оракул — тест: то же обновление с изменённым
|
||||
`From.UserName` по-прежнему принимается.
|
||||
- Запись, пришедшая ботом, видна тому же человеку в приложении. Оракул — тест:
|
||||
задача из привязанного чата читается сессией связанной учётной записи и не
|
||||
читается чужой.
|
||||
- Ключ `users_while_list` из конфига и из документов убран. Оракул — `task
|
||||
gate`, шаг `docs.py check`, и `grep users_while_list` по репозиторию пуст.
|
||||
|
||||
## Рамки
|
||||
|
||||
Одна учётная запись — один чат: нескольких чатов на человека не заводим.
|
||||
Инвариант в `CLAUDE.md` переписывается этой же задачей, а не остаётся врать.
|
||||
@@ -0,0 +1,38 @@
|
||||
# ✨ Сделать экран загрузки записи и её состояния
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро
|
||||
- **Зачем:** Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
|
||||
- **Теги:** goal:web-access
|
||||
|
||||
Двигает пункты 1 и 2 «Завершения» цели: экран принимает файл и заводит задачу, а
|
||||
её состояние обновляется само, пока задача не дошла до `done` или `failed`.
|
||||
|
||||
Берётся после `spa-skeleton` и `json-api-for-spa`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- новый экран приложения: выбор файла, отправка, показ состояния;
|
||||
- опрос состояния задачи и его остановка;
|
||||
- показ отказа человекочитаемым текстом из контракта API;
|
||||
- предел размера загружаемого файла на стороне приложения и на стороне сервера
|
||||
(`router.MaxMultipartMemory`, сегодня 32 МиБ);
|
||||
- `docs/database.md` — частота опроса числом.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Выбранный файл уходит на сервер и заводит задачу; экран сразу показывает её
|
||||
состояние. Оракул — тест экрана на подставном API: после отправки на экране
|
||||
идентификатор задачи и состояние `created`.
|
||||
- Опрос сам прекращается, когда задача дошла до `done` или `failed`. Оракул —
|
||||
тест: после ответа `done` новых запросов к API нет.
|
||||
- Отказ задачи показывается человекочитаемым текстом, а не кодом и не сырой
|
||||
ошибкой. Оракул — тест на ответе с состоянием `failed`.
|
||||
- Файл больше предела отклоняется на экране до отправки, с названным числом.
|
||||
Оракул — тест на файле сверх предела: запроса к API нет, на экране предел
|
||||
числом.
|
||||
|
||||
## Рамки
|
||||
|
||||
Записи звука в приложении не делаем — файл выбирают в системном диалоге. Список
|
||||
прошлых записей не делаем, это `records-list-screen`.
|
||||
@@ -1,19 +1,26 @@
|
||||
# 🎯 Записи загружаются и читаются в браузере
|
||||
# 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Запланировано
|
||||
- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
|
||||
|
||||
Приложение получает поверхность, на которой запись загружают и забирают текст,
|
||||
не открывая Telegram и не вызывая API руками. Конвейер обработки при этом
|
||||
остаётся прежним — меняется только вход и способ показать результат.
|
||||
не открывая Telegram и не вызывая API руками. Ставится оно на телефон и
|
||||
открывается с ярлыка, как обычное приложение. Конвейер обработки при этом
|
||||
остаётся прежним — меняется вход и способ показать результат.
|
||||
|
||||
Границы взяты уже: запись звука в самом приложении и работа без сети за целью
|
||||
**не стоят** — файл выбирают в системном диалоге, а без сети приложение
|
||||
показывает, что связи нет.
|
||||
|
||||
## Завершение
|
||||
|
||||
1. Страница принимает файл формой и заводит задачу — ту же, что заводит бот.
|
||||
2. Состояние задачи видно на странице и обновляется само, пока задача не дошла
|
||||
до `done` или `failed`; отказ показывается человекочитаемым текстом.
|
||||
3. Готовый текст читается и копируется со страницы целиком, без деления на
|
||||
части.
|
||||
1. Экран принимает файл и заводит задачу — ту же, что заводит бот.
|
||||
2. Состояние задачи видно на экране и обновляется само, пока задача не дошла до
|
||||
`done` или `failed`; отказ показывается человекочитаемым текстом.
|
||||
3. Готовый текст читается и копируется с экрана целиком, без деления на части.
|
||||
4. Список своих записей открывается и листается.
|
||||
5. Всё перечисленное работает без JavaScript — формой и переходом по ссылке.
|
||||
5. Приложение ставится на телефон из браузера и запускается с ярлыка на
|
||||
отдельном экране, без адресной строки.
|
||||
6. Открытое без сети, приложение показывает это состоянием, а не пустой
|
||||
страницей и не ошибкой браузера.
|
||||
|
||||
Reference in New Issue
Block a user