Развести номера ADR: 064/065/066 этой ветки → 073/074/075
Ветка отведена от 522ba89, где последним был ADR-063, и заняла
064–072. Тем временем в main через #3 и #4 пришли свои 064
(строка ввода — div вместо form), 065 (стороны сообщений)
и 066 (серверного перца нет).
Переезжают три ADR этой ветки, 067–072 остаются на месте:
их номера свободны, а внутренние ссылки менять незачем.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QR8uS3zGkybzWRW4GEY1oz
This commit is contained in:
+1
-1
@@ -162,7 +162,7 @@ func vapid() error {
|
|||||||
// version печатает, какой код собран в этот бинарь: короткую ревизию
|
// version печатает, какой код собран в этот бинарь: короткую ревизию
|
||||||
// и время коммита. Время коммита, а не компиляции: штамп момента сборки
|
// и время коммита. Время коммита, а не компиляции: штамп момента сборки
|
||||||
// делал бы каждую пересборку одного коммита новым файлом, и сверка хеша
|
// делал бы каждую пересборку одного коммита новым файлом, и сверка хеша
|
||||||
// со сборкой из тега перестала бы что-либо значить (ADR-022, ADR-065).
|
// со сборкой из тега перестала бы что-либо значить (ADR-022, ADR-074).
|
||||||
// Ревизию, которой нет, и время, которого нет, бинарь не выдумывает
|
// Ревизию, которой нет, и время, которого нет, бинарь не выдумывает
|
||||||
// (ADR-057).
|
// (ADR-057).
|
||||||
func version() {
|
func version() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-022: Деплой — nginx, systemd, кросс-сборка
|
# ADR-022: Деплой — nginx, systemd, кросс-сборка
|
||||||
|
|
||||||
Уточнён [ADR-032](032-state-permissions.md) (`StateDirectoryMode` и `UMask` в юните), [ADR-056](056-nginx-access-log-off.md) (`access_log off`), [ADR-057](057-version-marks-dirty-tree.md) (`bare version` помечает сборку из изменённого дерева) и [ADR-065](065-version-and-commit-time.md) (версия и время коммита в `GET /api/config`, времени компиляции в бинаре нет).
|
Уточнён [ADR-032](032-state-permissions.md) (`StateDirectoryMode` и `UMask` в юните), [ADR-056](056-nginx-access-log-off.md) (`access_log off`), [ADR-057](057-version-marks-dirty-tree.md) (`bare version` помечает сборку из изменённого дерева) и [ADR-074](074-version-and-commit-time.md) (версия и время коммита в `GET /api/config`, времени компиляции в бинаре нет).
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-024: Айдентика «Скобы», интерфейс и язык
|
# ADR-024: Айдентика «Скобы», интерфейс и язык
|
||||||
|
|
||||||
Уточнён [ADR-066](066-no-zoom-app-feel.md) (масштабирование запрещено, размеры шрифтов прежние), [ADR-067](067-version-in-sidebar-foot.md) (версия и время коммита в подвале сайдбара) и [ADR-069](069-message-input-is-editable-block.md) (строка сообщения — редактируемый блок).
|
Уточнён [ADR-075](075-no-zoom-app-feel.md) (масштабирование запрещено, размеры шрифтов прежние), [ADR-067](067-version-in-sidebar-foot.md) (версия и время коммита в подвале сайдбара) и [ADR-069](069-message-input-is-editable-block.md) (строка сообщения — редактируемый блок).
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-047: Исходящий запрос к push-сервису
|
# ADR-047: Исходящий запрос к push-сервису
|
||||||
|
|
||||||
Уточняет [ADR-011](011-web-push.md) и [ADR-023](023-push-and-service-worker.md). Код причины из ответа push-сервиса в журнале — [ADR-064](064-push-failure-reason-in-log.md).
|
Уточняет [ADR-011](011-web-push.md) и [ADR-023](023-push-and-service-worker.md). Код причины из ответа push-сервиса в журнале — [ADR-073](073-push-failure-reason-in-log.md).
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Уточняет [ADR-022](022-deploy-nginx-systemd.md): проверка подлинности бинаря опирается на ревизию, значит ревизия обязана быть честной.
|
Уточняет [ADR-022](022-deploy-nginx-systemd.md): проверка подлинности бинаря опирается на ревизию, значит ревизия обязана быть честной.
|
||||||
|
|
||||||
Уточнён [ADR-065](065-version-and-commit-time.md): чтение build info переехало в `internal/build`, ревизия печатается короткой и рядом с временем коммита; `+dirty` и `unknown` — как здесь. Формат стал проверяемым тестом — разбор build info отделён от `debug.ReadBuildInfo`; сама простановка `vcs.*` по-прежнему проверяется руками.
|
Уточнён [ADR-074](074-version-and-commit-time.md): чтение build info переехало в `internal/build`, ревизия печатается короткой и рядом с временем коммита; `+dirty` и `unknown` — как здесь. Формат стал проверяемым тестом — разбор build info отделён от `debug.ReadBuildInfo`; сама простановка `vcs.*` по-прежнему проверяется руками.
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# ADR-067: Версия и время коммита в подвале сайдбара
|
# ADR-067: Версия и время коммита в подвале сайдбара
|
||||||
|
|
||||||
Доводит до интерфейса [ADR-065](065-version-and-commit-time.md): сервер отдаёт `version` и `commitAt`, а где их показывать, там не решено.
|
Доводит до интерфейса [ADR-074](074-version-and-commit-time.md): сервер отдаёт `version` и `commitAt`, а где их показывать, там не решено.
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
`GET /api/config` называет работающую версию и время коммита (ADR-065). Клиент читает конфигурацию при запуске и так — ради `kdfIterations` и `vapidPublicKey`. Показать их негде: `docs/ui.md` такого элемента не знает.
|
`GET /api/config` называет работающую версию и время коммита (ADR-074). Клиент читает конфигурацию при запуске и так — ради `kdfIterations` и `vapidPublicKey`. Показать их негде: `docs/ui.md` такого элемента не знает.
|
||||||
|
|
||||||
Спрашивают об этом при каждой поломке: обновилось ли то, что человек видит. В установленном на «Домой» приложении вопрос острее — там нет ни адресной строки, ни консоли, и другого способа узнать версию у человека нет.
|
Спрашивают об этом при каждой поломке: обновилось ли то, что человек видит. В установленном на «Домой» приложении вопрос острее — там нет ни адресной строки, ни консоли, и другого способа узнать версию у человека нет.
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
- Версия стоит в подвале сайдбара, рядом с «ты: @nick»: это единственное постоянное место интерфейса, где уже написано, кто и где мы.
|
- Версия стоит в подвале сайдбара, рядом с «ты: @nick»: это единственное постоянное место интерфейса, где уже написано, кто и где мы.
|
||||||
- Текст — `версия · дд.мм чч:мм`, время коммита в местной зоне. Год не показывается: вопрос «что сейчас работает», а не летопись. Времени нет (`commitAt = 0`) — остаётся одна версия; конфигурации нет вовсе — строки нет.
|
- Текст — `версия · дд.мм чч:мм`, время коммита в местной зоне. Год не показывается: вопрос «что сейчас работает», а не летопись. Времени нет (`commitAt = 0`) — остаётся одна версия; конфигурации нет вовсе — строки нет.
|
||||||
- Цвет `stone`, размер подписи 11 px, без рамок и фона. Акцентом версия не бывает: `mark` — один смысловой элемент на экран (ADR-024), и это непрочитанные, а не служебная строка.
|
- Цвет `stone`, размер подписи 11 px, без рамок и фона. Акцентом версия не бывает: `mark` — один смысловой элемент на экран (ADR-024), и это непрочитанные, а не служебная строка.
|
||||||
- Не кнопка и не ссылка: нажимать в ней нечего. Стоит рядом с кнопкой «ты: @nick», а не внутри неё — иначе экранный диктор зачитывал бы хеш как часть названия кнопки. Выделяется, в отличие от остального сайдбара (ADR-066): на iPhone скопировать её иначе нечем.
|
- Не кнопка и не ссылка: нажимать в ней нечего. Стоит рядом с кнопкой «ты: @nick», а не внутри неё — иначе экранный диктор зачитывал бы хеш как часть названия кнопки. Выделяется, в отличие от остального сайдбара (ADR-075): на iPhone скопировать её иначе нечем.
|
||||||
- В сайдбаре 224 px строка и версия рядом не помещаются никогда: `версия · дд.мм чч:мм` — это 179 px при внутренней ширине 184, а «ты: @nick» просит ещё восемьдесят с лишним. Значит, на десктопе версия всегда занимает вторую строку и стоит справа; на мобильном, где сайдбар во всю ширину, обе стоят в одной строке. Ник, которому не хватило и целой строки, обрезается многоточием; версия — никогда: половина хеша бесполезна.
|
- В сайдбаре 224 px строка и версия рядом не помещаются никогда: `версия · дд.мм чч:мм` — это 179 px при внутренней ширине 184, а «ты: @nick» просит ещё восемьдесят с лишним. Значит, на десктопе версия всегда занимает вторую строку и стоит справа; на мобильном, где сайдбар во всю ширину, обе стоят в одной строке. Ник, которому не хватило и целой строки, обрезается многоточием; версия — никогда: половина хеша бесполезна.
|
||||||
- Текст ради одной строки не режется: вариант «в подвале ревизия, время в `title`» отклонён — `title` не показывается на телефоне, а телефон и есть место, ради которого строка заведена.
|
- Текст ради одной строки не режется: вариант «в подвале ревизия, время в `title`» отклонён — `title` не показывается на телефоне, а телефон и есть место, ради которого строка заведена.
|
||||||
|
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
- Разговор о поломке начинается с версии, а не с «попробуйте обновиться».
|
- Разговор о поломке начинается с версии, а не с «попробуйте обновиться».
|
||||||
- Подвал сайдбара стал двухстрочным и вырос с 47 px мока до 76. Это принятое отклонение от `docs/identity/screens.html`, блок 2a: там подвал — одна строка «ты: @nick» без версии и без 44 px цели нажатия. Мок остаётся снимком айдентики на момент ADR-024 и за интерфейсом не идёт: в нём нет и строки `+ новый чат`, и баннера установки.
|
- Подвал сайдбара стал двухстрочным и вырос с 47 px мока до 76. Это принятое отклонение от `docs/identity/screens.html`, блок 2a: там подвал — одна строка «ты: @nick» без версии и без 44 px цели нажатия. Мок остаётся снимком айдентики на момент ADR-024 и за интерфейсом не идёт: в нём нет и строки `+ новый чат`, и баннера установки.
|
||||||
- Названа версия сервера, а не оболочки, которую исполняет браузер: `version` приезжает с `GET /api/config`, а страница и её модули — из кэша service worker. После релиза первое открытие показывает новую версию под старой оболочкой; расходятся они до применения обновления (ADR-068), а с недописанным сообщением в поле — сколь угодно долго. Показывать рядом версию кэша — отдельное решение, здесь его нет.
|
- Названа версия сервера, а не оболочки, которую исполняет браузер: `version` приезжает с `GET /api/config`, а страница и её модули — из кэша service worker. После релиза первое открытие показывает новую версию под старой оболочкой; расходятся они до применения обновления (ADR-068), а с недописанным сообщением в поле — сколь угодно долго. Показывать рядом версию кэша — отдельное решение, здесь его нет.
|
||||||
- Записано в `docs/ui.md`, «Список чатов (сайдбар)». Отдельного запроса ради этого нет: поля приезжают с конфигурацией (ADR-065).
|
- Записано в `docs/ui.md`, «Список чатов (сайдбар)». Отдельного запроса ради этого нет: поля приезжают с конфигурацией (ADR-074).
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# ADR-069: Строка сообщения — редактируемый блок, а не поле формы
|
# ADR-069: Строка сообщения — редактируемый блок, а не поле формы
|
||||||
|
|
||||||
Уточняет [ADR-024](024-identity-and-ui.md) («Чат» и «Доступность» в `docs/ui.md`) и [ADR-066](066-no-zoom-app-feel.md): в перечне элементов управления, по которым второй тап не гасится, прибавился редактируемый блок.
|
Уточняет [ADR-024](024-identity-and-ui.md) («Чат» и «Доступность» в `docs/ui.md`) и [ADR-075](075-no-zoom-app-feel.md): в перечне элементов управления, по которым второй тап не гасится, прибавился редактируемый блок.
|
||||||
|
|
||||||
Уточнён [ADR-071](071-input-limit-cuts-what-arrives.md): предел держится до вставки и режет приходящее, а не хвост блока.
|
Уточнён [ADR-071](071-input-limit-cuts-what-arrives.md): предел держится до вставки и режет приходящее, а не хвост блока.
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
На iPhone при фокусе в строке сообщения над клавиатурой висит системная полоса помощника форм: две стрелки перехода между полями и «готово». Стрелки неактивны — поле на экране одно, переходить некуда, — а полоса занимает место и ломает ощущение приложения, ради которого принят ADR-066.
|
На iPhone при фокусе в строке сообщения над клавиатурой висит системная полоса помощника форм: две стрелки перехода между полями и «готово». Стрелки неактивны — поле на экране одно, переходить некуда, — а полоса занимает место и ломает ощущение приложения, ради которого принят ADR-075.
|
||||||
|
|
||||||
Убрать её со страницы нечем: полосу рисует система, и для `input` и `textarea` она бывает всегда. Единственный работающий приём — перестать быть полем формы: у редактируемого блока (`contenteditable`) iOS полосы не показывает.
|
Убрать её со страницы нечем: полосу рисует система, и для `input` и `textarea` она бывает всегда. Единственный работающий приём — перестать быть полем формы: у редактируемого блока (`contenteditable`) iOS полосы не показывает.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# ADR-064: Код причины отказа push-сервиса в журнале
|
# ADR-073: Код причины отказа push-сервиса в журнале
|
||||||
|
|
||||||
Уточняет [ADR-047](047-push-endpoint.md).
|
Уточняет [ADR-047](047-push-endpoint.md).
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# ADR-065: Версия — короткая ревизия, «время сборки» — время коммита
|
# ADR-074: Версия — короткая ревизия, «время сборки» — время коммита
|
||||||
|
|
||||||
Уточняет [ADR-022](022-deploy-nginx-systemd.md) (подлинность бинаря проверяется сверкой хеша со сборкой из тега) и [ADR-057](057-version-marks-dirty-tree.md) (`+dirty` у сборки из изменённого дерева).
|
Уточняет [ADR-022](022-deploy-nginx-systemd.md) (подлинность бинаря проверяется сверкой хеша со сборкой из тега) и [ADR-057](057-version-marks-dirty-tree.md) (`+dirty` у сборки из изменённого дерева).
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# ADR-066: Масштабирования нет, размеры шрифтов прежние
|
# ADR-075: Масштабирования нет, размеры шрифтов прежние
|
||||||
|
|
||||||
Уточняет [ADR-024](024-identity-and-ui.md): типографика из `docs/identity/brief.md` остаётся как записана, а страница перестаёт масштабироваться.
|
Уточняет [ADR-024](024-identity-and-ui.md): типографика из `docs/identity/brief.md` остаётся как записана, а страница перестаёт масштабироваться.
|
||||||
|
|
||||||
+2
-2
@@ -8,7 +8,7 @@
|
|||||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bare ./cmd/bare
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bare ./cmd/bare
|
||||||
```
|
```
|
||||||
|
|
||||||
Версия бинаря — `vcs.revision` и `vcs.time` из `debug.ReadBuildInfo()`. `bare version` печатает семь символов ревизии и время коммита в UTC: `cfd0ec0 2026-08-23T04:54:16Z`. У сборки из изменённого рабочего дерева (`vcs.modified`) к ревизии дописывается `+dirty` — сверка со сборкой из тега не должна проходить молча (ADR-057); у сборки не из git печатается `unknown`. Времени компиляции в бинаре нет: оно делало бы каждую пересборку одного коммита новым файлом (ADR-065). Те же значения отдаёт `GET /api/config` полями `version` и `commitAt`. `/healthz` отвечает только `ok`.
|
Версия бинаря — `vcs.revision` и `vcs.time` из `debug.ReadBuildInfo()`. `bare version` печатает семь символов ревизии и время коммита в UTC: `cfd0ec0 2026-08-23T04:54:16Z`. У сборки из изменённого рабочего дерева (`vcs.modified`) к ревизии дописывается `+dirty` — сверка со сборкой из тега не должна проходить молча (ADR-057); у сборки не из git печатается `unknown`. Времени компиляции в бинаре нет: оно делало бы каждую пересборку одного коммита новым файлом (ADR-074). Те же значения отдаёт `GET /api/config` полями `version` и `commitAt`. `/healthz` отвечает только `ok`.
|
||||||
|
|
||||||
## Первичная настройка сервера (один раз)
|
## Первичная настройка сервера (один раз)
|
||||||
|
|
||||||
@@ -147,6 +147,6 @@ ssh xmatic 'sudo install -m 0755 -o root -g root /tmp/bare /opt/bare/bare && sud
|
|||||||
|
|
||||||
## Логи
|
## Логи
|
||||||
|
|
||||||
Сервер пишет в stdout: время, метод, путь, статус, длительность; для маршрутов `/api/` вместо пути пишется шаблон (`/api/users/{nick}`), чтобы ник не попадал в журнал, а если отказ случился до маршрутизации (`Origin`, предел тела) и шаблона ещё нет — просто `/api/`; ника в журнале нет вовсе, включая отказы по лимитам (ADR-055); IP не пишется. Причины ответов `500 internal` (ADR-027) пишутся отдельной строкой, без данных запроса. Отправитель пушей пишет класс отказа — «таймаут», «имя не разрешилось», «отправка не удалась» — без адреса подписки и идентификатора устройства (ADR-047). Ответ push-сервиса пишется статусом и коротким кодом причины из тела: «пуш: push-сервис ответил 403 (BadJwtToken)». Код — диагностика вендора; всё, что на короткий код не похоже, отбрасывается целиком, и остаётся один статус (ADR-064). journald хранит по своим правилам.
|
Сервер пишет в stdout: время, метод, путь, статус, длительность; для маршрутов `/api/` вместо пути пишется шаблон (`/api/users/{nick}`), чтобы ник не попадал в журнал, а если отказ случился до маршрутизации (`Origin`, предел тела) и шаблона ещё нет — просто `/api/`; ника в журнале нет вовсе, включая отказы по лимитам (ADR-055); IP не пишется. Причины ответов `500 internal` (ADR-027) пишутся отдельной строкой, без данных запроса. Отправитель пушей пишет класс отказа — «таймаут», «имя не разрешилось», «отправка не удалась» — без адреса подписки и идентификатора устройства (ADR-047). Ответ push-сервиса пишется статусом и коротким кодом причины из тела: «пуш: push-сервис ответил 403 (BadJwtToken)». Код — диагностика вендора; всё, что на короткий код не похоже, отбрасывается целиком, и остаётся один статус (ADR-073). journald хранит по своим правилам.
|
||||||
|
|
||||||
nginx журнал запросов не ведёт: `access_log off` в обоих server-блоках (ADR-056). Без этой строки он унаследовал бы `access.log` формата `combined` из `/etc/nginx/nginx.conf` — с адресом клиента и полным URI, то есть с ником и социальным графом. `error_log` остаётся: это журнал сбоев, а не запросов, и при отказе он записывает адрес клиента.
|
nginx журнал запросов не ведёт: `access_log off` в обоих server-блоках (ADR-056). Без этой строки он унаследовал бы `access.log` формата `combined` из `/etc/nginx/nginx.conf` — с адресом клиента и полным URI, то есть с ником и социальным графом. `error_log` остаётся: это журнал сбоев, а не запросов, и при отказе он записывает адрес клиента.
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ font-family: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monos
|
|||||||
|
|
||||||
Размеры: текст сообщений 14 px / 1.55; автор и время 12 px; заголовки секций 10 px, разрядка 0.14em, uppercase; подписи 11 px; имя чата в шапке 15 px. Шрифты не загружаются.
|
Размеры: текст сообщений 14 px / 1.55; автор и время 12 px; заголовки секций 10 px, разрядка 0.14em, uppercase; подписи 11 px; имя чата в шапке 15 px. Шрифты не загружаются.
|
||||||
|
|
||||||
Поля ввода — те же 14 px, исключений нет: масштабирование в клиенте запрещено, и растить шрифт ради обхода фокус-зума iOS не приходится (ADR-066).
|
Поля ввода — те же 14 px, исключений нет: масштабирование в клиенте запрещено, и растить шрифт ради обхода фокус-зума iOS не приходится (ADR-075).
|
||||||
|
|
||||||
## Компоновка
|
## Компоновка
|
||||||
|
|
||||||
- Десктоп: сайдбар 224 px с правой границей `line`, шапка 64 px, отступы контента 32 px; сообщения — сетка `132px 1fr`, column-gap 20, row-gap 6.
|
- Десктоп: сайдбар 224 px с правой границей `line`, шапка 64 px, отступы контента 32 px; сообщения — сетка `132px 1fr`, column-gap 20, row-gap 6.
|
||||||
- Мобильный: шапка 56 px, отступы 20 px, ввод с min-height 44 px. Нижнее поле подвала и строки ввода считается с `env(safe-area-inset-bottom)`: под системную полосу iPhone они не заходят (ADR-066).
|
- Мобильный: шапка 56 px, отступы 20 px, ввод с min-height 44 px. Нижнее поле подвала и строки ввода считается с `env(safe-area-inset-bottom)`: под системную полосу iPhone они не заходят (ADR-075).
|
||||||
- Ввод — рамка 1 px ink, без скруглений, `>` цветом mark слева.
|
- Ввод — рамка 1 px ink, без скруглений, `>` цветом mark слева.
|
||||||
- Активный элемент списка — инверсия: фон ink, текст bone.
|
- Активный элемент списка — инверсия: фон ink, текст bone.
|
||||||
- Разделители — 1 px `line`; разделитель «новые» — 1 px mark.
|
- Разделители — 1 px `line`; разделитель «новые» — 1 px mark.
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@
|
|||||||
```
|
```
|
||||||
embed.go //go:embed web в корне модуля (ADR-025)
|
embed.go //go:embed web в корне модуля (ADR-025)
|
||||||
cmd/bare/main.go подкоманды: serve, vapid, version
|
cmd/bare/main.go подкоманды: serve, vapid, version
|
||||||
internal/build/ ревизия и время коммита из build info (ADR-065)
|
internal/build/ ревизия и время коммита из build info (ADR-074)
|
||||||
internal/config/ переменные BARE_*
|
internal/config/ переменные BARE_*
|
||||||
internal/store/ SQLite, migrations/*.sql (embed), запросы
|
internal/store/ SQLite, migrations/*.sql (embed), запросы
|
||||||
internal/auth/ argon2id, сессии, cookie
|
internal/auth/ argon2id, сессии, cookie
|
||||||
@@ -36,7 +36,7 @@ web/
|
|||||||
js/sync.js устройство, поток событий, приём и отправка
|
js/sync.js устройство, поток событий, приём и отправка
|
||||||
js/pwa.js service worker, самообновление (ADR-068), пуши, установка
|
js/pwa.js service worker, самообновление (ADR-068), пуши, установка
|
||||||
js/ulid.js ULID
|
js/ulid.js ULID
|
||||||
js/zoom.js запрет масштабирования (ADR-066)
|
js/zoom.js запрет масштабирования (ADR-075)
|
||||||
js/ui/*.js экраны из ui.md
|
js/ui/*.js экраны из ui.md
|
||||||
js/export.js .bare
|
js/export.js .bare
|
||||||
scripts/deploy.sh
|
scripts/deploy.sh
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ WrappedKey { to: nick, iv: string, ct: string }
|
|||||||
|
|
||||||
`GET /api/config` → `200 {inviteRequired: bool, vapidPublicKey: string, kdfIterations: number, maxMessageChars: 4000, version: string, commitAt: number}`
|
`GET /api/config` → `200 {inviteRequired: bool, vapidPublicKey: string, kdfIterations: number, maxMessageChars: 4000, version: string, commitAt: number}`
|
||||||
|
|
||||||
`version` — короткая ревизия сборки: семь символов хеша коммита, с суффиксом `+dirty` у бинаря из изменённого дерева (ADR-057) и `unknown` у сборки не из git. `commitAt` — время коммита в миллисекундах Unix, `0` если оно неизвестно. Это время коммита, а не момент компиляции: штамп времени сборки лишил бы смысла сверку хеша бинаря со сборкой из тега (ADR-022, ADR-065). Те же значения печатает `bare version`. Отдельного эндпоинта у них нет.
|
`version` — короткая ревизия сборки: семь символов хеша коммита, с суффиксом `+dirty` у бинаря из изменённого дерева (ADR-057) и `unknown` у сборки не из git. `commitAt` — время коммита в миллисекундах Unix, `0` если оно неизвестно. Это время коммита, а не момент компиляции: штамп времени сборки лишил бы смысла сверку хеша бинаря со сборкой из тега (ADR-022, ADR-074). Те же значения печатает `bare version`. Отдельного эндпоинта у них нет.
|
||||||
|
|
||||||
`GET /api/kdf?nick=<nick>` → `200 {iterations}`. Для неизвестного ника — `kdfIterations` из конфигурации, тем же статусом. Скрытием существования ника ответ не занимается: у аккаунта, не входившего после повышения цели, число итераций своё (ADR-062), а сам факт, что ник существует, публичен (ADR-019).
|
`GET /api/kdf?nick=<nick>` → `200 {iterations}`. Для неизвестного ника — `kdfIterations` из конфигурации, тем же статусом. Скрытием существования ника ответ не занимается: у аккаунта, не входившего после повышения цели, число итераций своё (ADR-062), а сам факт, что ник существует, публичен (ADR-019).
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
Без inline-стилей и inline-скриптов (CSP). Рендер — `document.createElement` и `textContent`; `innerHTML` не используется нигде: сообщения — пользовательские данные.
|
Без inline-стилей и inline-скриптов (CSP). Рендер — `document.createElement` и `textContent`; `innerHTML` не используется нигде: сообщения — пользовательские данные.
|
||||||
|
|
||||||
Страница ведёт себя как приложение, а не как документ (ADR-066): масштабирования нет, резинового отскока нет, выделение снято с шапки, сайдбара, кнопок и разделителей — но не с текста сообщений, отпечатков, полей ввода и версии в подвале сайдбара: их копируют. Установленное на «Домой» приложение объявлено мета-строками, имя на экране — `bare`, системная полоса остаётся над страницей. Подвал сайдбара и строка ввода отступают от нижней системной полосы iPhone.
|
Страница ведёт себя как приложение, а не как документ (ADR-075): масштабирования нет, резинового отскока нет, выделение снято с шапки, сайдбара, кнопок и разделителей — но не с текста сообщений, отпечатков, полей ввода и версии в подвале сайдбара: их копируют. Установленное на «Домой» приложение объявлено мета-строками, имя на экране — `bare`, системная полоса остаётся над страницей. Подвал сайдбара и строка ввода отступают от нижней системной полосы iPhone.
|
||||||
|
|
||||||
## Вход и регистрация
|
## Вход и регистрация
|
||||||
|
|
||||||
@@ -120,4 +120,4 @@
|
|||||||
|
|
||||||
Строка сообщения — редактируемый блок (ADR-069), и семантику ему задают руками: `role="textbox"`, `aria-multiline="true"`, подпись `aria-label` тем же текстом, что подсказка. Заблокированный ввод перестаёт быть редактируемым и говорит об этом `aria-disabled`.
|
Строка сообщения — редактируемый блок (ADR-069), и семантику ему задают руками: `role="textbox"`, `aria-multiline="true"`, подпись `aria-label` тем же текстом, что подсказка. Заблокированный ввод перестаёт быть редактируемым и говорит об этом `aria-disabled`.
|
||||||
|
|
||||||
Масштабирование запрещено — и щипок, и двойной тап (ADR-066). Это сознательный размен: интерфейс ведёт себя как приложение, а человек, которому нужно увеличить мелкое, средства лишается. Размеры шрифтов от запрета не меняются: поля ввода остаются 14 px, как в `docs/identity/brief.md`. Браузер вправе запрет проигнорировать.
|
Масштабирование запрещено — и щипок, и двойной тап (ADR-075). Это сознательный размен: интерфейс ведёт себя как приложение, а человек, которому нужно увеличить мелкое, средства лишается. Размеры шрифтов от запрета не меняются: поля ввода остаются 14 px, как в `docs/identity/brief.md`. Браузер вправе запрет проигнорировать.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
//
|
//
|
||||||
// version и commitAt отвечают на вопрос «какой код сейчас работает»:
|
// version и commitAt отвечают на вопрос «какой код сейчас работает»:
|
||||||
// короткая ревизия сборки и время коммита, а не момент компиляции
|
// короткая ревизия сборки и время коммита, а не момент компиляции
|
||||||
// (ADR-065). Отдельного эндпоинта им не заводится — конфигурацию клиент
|
// (ADR-074). Отдельного эндпоинта им не заводится — конфигурацию клиент
|
||||||
// читает до входа и так.
|
// читает до входа и так.
|
||||||
func (s *server) config(w http.ResponseWriter, r *http.Request) {
|
func (s *server) config(w http.ResponseWriter, r *http.Request) {
|
||||||
info := build.Current()
|
info := build.Current()
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func TestConfig(t *testing.T) {
|
|||||||
t.Errorf("maxMessageChars: получено %d, ожидалось 4000", got.MaxMessageChars)
|
t.Errorf("maxMessageChars: получено %d, ожидалось 4000", got.MaxMessageChars)
|
||||||
}
|
}
|
||||||
// Версия — то же самое, что печатает `bare version`: одно место,
|
// Версия — то же самое, что печатает `bare version`: одно место,
|
||||||
// один формат (ADR-065). Тестовому бинарю vcs.* не проставляются,
|
// один формат (ADR-074). Тестовому бинарю vcs.* не проставляются,
|
||||||
// поэтому здесь проверяется в том числе поведение без build info —
|
// поэтому здесь проверяется в том числе поведение без build info —
|
||||||
// «unknown» и 0.
|
// «unknown» и 0.
|
||||||
if !validVersion(got.Version) {
|
if !validVersion(got.Version) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package build отвечает на вопрос «какой код сейчас работает»: ревизия
|
// Package build отвечает на вопрос «какой код сейчас работает»: ревизия
|
||||||
// и время коммита, которые git оставляет в бинаре при сборке (ADR-065).
|
// и время коммита, которые git оставляет в бинаре при сборке (ADR-074).
|
||||||
// Момента компиляции здесь нет и не будет: штамп времени сборки делал бы
|
// Момента компиляции здесь нет и не будет: штамп времени сборки делал бы
|
||||||
// каждую пересборку одного коммита новым файлом, а подлинность бинаря
|
// каждую пересборку одного коммита новым файлом, а подлинность бинаря
|
||||||
// проверяется сравнением хеша со сборкой из тега (ADR-022).
|
// проверяется сравнением хеша со сборкой из тега (ADR-022).
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
const hash = "9f2c1ab7d3e4c5061728394a5b6c7d8e9f001122"
|
const hash = "9f2c1ab7d3e4c5061728394a5b6c7d8e9f001122"
|
||||||
|
|
||||||
// Формат версии: семь символов ревизии, «+dirty» у изменённого дерева,
|
// Формат версии: семь символов ревизии, «+dirty» у изменённого дерева,
|
||||||
// «unknown» без ревизии (ADR-057, ADR-065).
|
// «unknown» без ревизии (ADR-057, ADR-074).
|
||||||
func TestVersion(t *testing.T) {
|
func TestVersion(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
info Info
|
info Info
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const (
|
|||||||
dropEvery = time.Minute
|
dropEvery = time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// Чтение тела ответа push-сервиса ради кода причины (ADR-064).
|
// Чтение тела ответа push-сервиса ради кода причины (ADR-073).
|
||||||
const (
|
const (
|
||||||
// maxReasonBody — сколько байт тела читаем. Код причины стоит в начале
|
// maxReasonBody — сколько байт тела читаем. Код причины стоит в начале
|
||||||
// ответа; остальное дочитывается в никуда, ради переиспользования
|
// ответа; остальное дочитывается в никуда, ради переиспользования
|
||||||
@@ -331,7 +331,7 @@ func (s *Sender) deliver(j job) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
// Начало тела нужно ради кода причины (ADR-064), остаток дочитывается
|
// Начало тела нужно ради кода причины (ADR-073), остаток дочитывается
|
||||||
// в никуда: иначе соединение не переиспользуется.
|
// в никуда: иначе соединение не переиспользуется.
|
||||||
head, _ := io.ReadAll(io.LimitReader(resp.Body, maxReasonBody))
|
head, _ := io.ReadAll(io.LimitReader(resp.Body, maxReasonBody))
|
||||||
io.Copy(io.Discard, resp.Body)
|
io.Copy(io.Discard, resp.Body)
|
||||||
@@ -432,7 +432,7 @@ func (s *Sender) report(format string, args ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// status — ответ push-сервиса для журнала: код и, если он разобран,
|
// status — ответ push-сервиса для журнала: код и, если он разобран,
|
||||||
// короткий код причины из тела (ADR-064).
|
// короткий код причины из тела (ADR-073).
|
||||||
func status(code int, body []byte) string {
|
func status(code int, body []byte) string {
|
||||||
if r := serviceReason(body); r != "" {
|
if r := serviceReason(body); r != "" {
|
||||||
return fmt.Sprintf("%d (%s)", code, r)
|
return fmt.Sprintf("%d (%s)", code, r)
|
||||||
@@ -443,7 +443,7 @@ func status(code int, body []byte) string {
|
|||||||
// serviceReason достаёт из тела ответа короткий код причины: APNs отвечает
|
// serviceReason достаёт из тела ответа короткий код причины: APNs отвечает
|
||||||
// {"reason":"BadJwtToken"}, Mozilla — {"errno":…,"error":"Not Found"}.
|
// {"reason":"BadJwtToken"}, Mozilla — {"errno":…,"error":"Not Found"}.
|
||||||
// Код — диагностика вендора, а не данные пользователя, и без него отказ
|
// Код — диагностика вендора, а не данные пользователя, и без него отказ
|
||||||
// не читается: голый «403» сутки выглядел как «что-то с пушами» (ADR-064).
|
// не читается: голый «403» сутки выглядел как «что-то с пушами» (ADR-073).
|
||||||
//
|
//
|
||||||
// Тело всё же приходит снаружи, поэтому в журнал идёт не оно, а то, что
|
// Тело всё же приходит снаружи, поэтому в журнал идёт не оно, а то, что
|
||||||
// прошло safeReason.
|
// прошло safeReason.
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func TestVAPIDSubjectInToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Отказ push-сервиса читается по журналу: статус и код причины из тела
|
// Отказ push-сервиса читается по журналу: статус и код причины из тела
|
||||||
// (ADR-064). Без кода 403 от APNs неотличим от любого другого отказа.
|
// (ADR-073). Без кода 403 от APNs неотличим от любого другого отказа.
|
||||||
func TestServiceStatusInLog(t *testing.T) {
|
func TestServiceStatusInLog(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
io.Copy(io.Discard, r.Body)
|
io.Copy(io.Discard, r.Body)
|
||||||
@@ -225,7 +225,7 @@ func TestServiceStatusInLog(t *testing.T) {
|
|||||||
|
|
||||||
// Код причины берётся из тела ответа, но телом распоряжается чужая
|
// Код причины берётся из тела ответа, но телом распоряжается чужая
|
||||||
// сторона: всё, что на короткий код не похоже, в журнал не идёт вовсе
|
// сторона: всё, что на короткий код не похоже, в журнал не идёт вовсе
|
||||||
// (ADR-064, docs/deploy.md, «Логи»).
|
// (ADR-073, docs/deploy.md, «Логи»).
|
||||||
func TestServiceReason(t *testing.T) {
|
func TestServiceReason(t *testing.T) {
|
||||||
const endpoint = "web.push.apple.com"
|
const endpoint = "web.push.apple.com"
|
||||||
cases := []struct{ body, want string }{
|
cases := []struct{ body, want string }{
|
||||||
|
|||||||
+5
-5
@@ -25,7 +25,7 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* масштабирования нет (ADR-066). manipulation снимает зум по двойному тапу
|
/* масштабирования нет (ADR-075). manipulation снимает зум по двойному тапу
|
||||||
и задержку 300 мс, которую браузер держит, ожидая второго касания;
|
и задержку 300 мс, которую браузер держит, ожидая второго касания;
|
||||||
прокрутку и щипок правило не трогает — щипок запрещён в viewport.
|
прокрутку и щипок правило не трогает — щипок запрещён в viewport.
|
||||||
Правило стоит на html: двойной тап — жест документа, и браузер берёт
|
Правило стоит на html: двойной тап — жест документа, и браузер берёт
|
||||||
@@ -41,7 +41,7 @@ body {
|
|||||||
/* виден вырез: viewport-fit=cover пускает страницу под скруглённые углы,
|
/* виден вырез: viewport-fit=cover пускает страницу под скруглённые углы,
|
||||||
под боковой вырез в альбомной ориентации и под верхнюю системную
|
под боковой вырез в альбомной ориентации и под верхнюю системную
|
||||||
полосу. Полосу над страницей держит мета-строка о стиле полосы
|
полосу. Полосу над страницей держит мета-строка о стиле полосы
|
||||||
(ADR-066), поэтому верхняя вставка обычно нулевая и не делает ничего;
|
(ADR-075), поэтому верхняя вставка обычно нулевая и не делает ничего;
|
||||||
там, где она не нулевая, шапка не уезжает под часы */
|
там, где она не нулевая, шапка не уезжает под часы */
|
||||||
padding-top: env(safe-area-inset-top);
|
padding-top: env(safe-area-inset-top);
|
||||||
padding-left: env(safe-area-inset-left);
|
padding-left: env(safe-area-inset-left);
|
||||||
@@ -58,7 +58,7 @@ body {
|
|||||||
/* интерфейс не выделяется: долгое нажатие по шапке, списку и кнопкам
|
/* интерфейс не выделяется: долгое нажатие по шапке, списку и кнопкам
|
||||||
вызывает лупу и «копировать», а копировать там нечего. Текст сообщений,
|
вызывает лупу и «копировать», а копировать там нечего. Текст сообщений,
|
||||||
отпечатки и поля ввода выделяются как обычно — их именно копируют
|
отпечатки и поля ввода выделяются как обычно — их именно копируют
|
||||||
(ADR-066) */
|
(ADR-075) */
|
||||||
|
|
||||||
.side,
|
.side,
|
||||||
.head,
|
.head,
|
||||||
@@ -421,7 +421,7 @@ input[type="password"] {
|
|||||||
gap: 2px 8px;
|
gap: 2px 8px;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
/* нижняя системная полоса iPhone проходит поверх страницы: подвал
|
/* нижняя системная полоса iPhone проходит поверх страницы: подвал
|
||||||
отступает от неё, а не прячется под ней (ADR-066) */
|
отступает от неё, а не прячется под ней (ADR-075) */
|
||||||
padding: 6px 20px calc(6px + env(safe-area-inset-bottom));
|
padding: 6px 20px calc(6px + env(safe-area-inset-bottom));
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
@@ -463,7 +463,7 @@ input[type="password"] {
|
|||||||
/* версия выделяется: с неё начинается разговор о поломке (ADR-067),
|
/* версия выделяется: с неё начинается разговор о поломке (ADR-067),
|
||||||
а на iPhone скопировать её иначе нечем — ни строки адреса, ни консоли
|
а на iPhone скопировать её иначе нечем — ни строки адреса, ни консоли
|
||||||
там нет. Общий запрет выделения снят с неё так же, как с текста
|
там нет. Общий запрет выделения снят с неё так же, как с текста
|
||||||
сообщений и отпечатков (ADR-066) */
|
сообщений и отпечатков (ADR-075) */
|
||||||
|
|
||||||
.build {
|
.build {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|||||||
+1
-1
@@ -483,7 +483,7 @@ function errorText(err) {
|
|||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
// Масштабирования нет: интерфейс ведёт себя как приложение, а не как
|
// Масштабирования нет: интерфейс ведёт себя как приложение, а не как
|
||||||
// страница (ADR-066). Строке viewport Safari верит не всегда, поэтому
|
// страница (ADR-075). Строке viewport Safari верит не всегда, поэтому
|
||||||
// страховка ставится до первого экрана.
|
// страховка ставится до первого экрана.
|
||||||
lock();
|
lock();
|
||||||
db.persist();
|
db.persist();
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ function side(ctx, active) {
|
|||||||
return { nav, dispose: mount(items, ctx, active) };
|
return { nav, dispose: mount(items, ctx, active) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// build — версия и время коммита из GET /api/config (ADR-065, ADR-067).
|
// build — версия и время коммита из GET /api/config (ADR-074, ADR-067).
|
||||||
// Конфигурации нет — офлайн-старт до первого ответа сервера — значит,
|
// Конфигурации нет — офлайн-старт до первого ответа сервера — значит,
|
||||||
// и строки нет: выдумывать версию не из чего. Время без версии не бывает:
|
// и строки нет: выдумывать версию не из чего. Время без версии не бывает:
|
||||||
// её сервер отдаёт всегда, хотя бы как «unknown».
|
// её сервер отдаёт всегда, хотя бы как «unknown».
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Запрет масштабирования — страховка к строке viewport (ADR-066).
|
// Запрет масштабирования — страховка к строке viewport (ADR-075).
|
||||||
//
|
//
|
||||||
// Основное средство — `maximum-scale=1, user-scalable=no` в index.html
|
// Основное средство — `maximum-scale=1, user-scalable=no` в index.html
|
||||||
// и `touch-action: manipulation` в app.css. Safari вправе не послушаться:
|
// и `touch-action: manipulation` в app.css. Safari вправе не послушаться:
|
||||||
|
|||||||
Reference in New Issue
Block a user