Files
bare/docs/ui.md
T
mayatnikovandClaude Opus 5 597c55301c Этап 1: аккаунты — argon2id, сессии, ключевой блоб, вход и регистрация
Сервер: миграция 001 со всей схемой storage.md, store на modernc.org/sqlite
(WAL, foreign_keys, один писатель), фоновая чистка раз в час, argon2id
с параметрами ADR-021 и сверкой constant-time, сессии по SHA-256 токена,
cookie bare_session, глобальная проверка Origin, девять эндпоинтов аккаунта.
Ник в журнал не попадает: для /api/ пишется шаблон маршрута.

Клиент: crypto.js по crypto.md построчно — мастер из пароля, два независимых
ключа из мастера, ключевой блоб с ником в AAD, отпечаток от сырой точки;
db.js со всеми хранилищами версии 1; экран входа и регистрации, настройки
со сменой пароля, выходом и удалением аккаунта.

Пароль не покидает клиент: проверено на боевом сервере — ни пароля, ни priv.d
ни в одном теле запроса, вход на втором устройстве даёт тот же отпечаток.

ADR-027: код internal для 500, причина только в журнале.
ADR-028: тексты состояний клиента сведены в ui.md.
ADR-029: вход под другим ником стирает историю только после подтверждения.
ADR-030: верхняя граница итераций KDF, проверка границ на обеих сторонах.
ADR-031: служебный выход перед повторным входом не заканчивает сеанс.
ADR-032: каталог состояния 0700, файлы базы 0600.

Прямые зависимости: modernc.org/sqlite, golang.org/x/crypto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-22 14:06:07 +03:00

95 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Интерфейс
Визуальная система — `docs/identity/brief.md`, эталон экрана чата — `docs/identity/screens.html`. Здесь — состав экранов, поведение и тексты. Все тексты — русские, строчными, как в моке; заглавная только в начале предложений из нескольких слов.
## Каркас
Одна страница `index.html`, роутинг по hash: `#/` — список (на десктопе — первый чат), `#/dm/<nick>`, `#/room/<id>`, `#/room/<id>/members`, `#/contact/<nick>`, `#/settings`, `#/new`. Десктоп (≥ 760 px): сайдбар 224 px + чат. Мобильный: один экран за раз, «назад» — в шапке слева.
Без inline-стилей и inline-скриптов (CSP). Рендер — `document.createElement` и `textContent`; `innerHTML` не используется нигде: сообщения — пользовательские данные.
## Вход и регистрация
Одна страница, два режима переключателем «вход / регистрация». Логотип-знак и `bare` сверху.
Поля: `ник`, `пароль`. В регистрации дополнительно `инвайт-код`, если `config.inviteRequired`, и текст под паролем:
> пароль — это ключ шифрования, а не запись в базе. восстановления нет. не короче 12 символов; лучше — фраза из нескольких слов.
Кнопка одна, в стиле строки ввода. Пока идёт PBKDF2 — состояние «вычисляем ключ…», кнопка заблокирована. Ошибки — строкой под формой цветом `mark`: «неверный ник или пароль», «ник занят», «ник: 2–32 символа, a–z, 0–9, _», «нужен инвайт-код», «инвайт-код не подходит». Форму ника и длину пароля клиент проверяет сам, до PBKDF2, в обоих режимах. Остальные состояния — «Тексты состояний».
Если на устройстве лежат ключи другого ника, до вычисления ключа — подтверждение «на этом устройстве история @nick. вход под другим ником удалит её.» с кнопками «удалить» и «отмена» (ADR-029). База стирается после успешного входа или регистрации; отказ сервера её не трогает.
## Список чатов (сайдбар)
Секции «каналы» и «личные», как в моке. Активный чат — инверсия (ink на bone). Непрочитанные — число цветом `mark` справа. Порядок — по `lastId` по убыванию. Внизу — «ты: @nick», по нажатию — настройки. Над секциями — строка `+ новый чат`.
## Новый чат (`#/new`)
Две строки ввода: `@ник` → открыть личный чат; `#имя комнаты` → создать комнату. Ошибки: «такого ника нет», «нельзя писать себе».
## Чат
Шапка: имя (`#general` / `@marta`), по нажатию — участники или карточка контакта. Без темы и «N онлайн».
Лента: десктоп — сетка «автор 132 px + текст», подряд идущие сообщения одного автора — без повтора автора; мобильный — автор над группой. Свой ник в колонке автора — цветом `mark`. Разделители дат — линия с датой; «новые» — линия цветом `mark` перед первым непрочитанным, исчезает при следующем открытии чата. Pending — текст цветом `stone`; failed — с пометкой «не отправлено · повторить». Нерасшифрованное — курсивом: «не удалось расшифровать: ключ изменился» / «…: нет ключа комнаты». Время — `ts` в локальной зоне, `ЧЧ:ММ`.
Ввод: рамка 1 px ink, слева `>` цветом `mark`, placeholder «сообщение в #general» / «сообщение». Enter — отправить, Shift+Enter — перенос; на мобильном Enter — перенос, отправка — кнопка `>` справа. Подсказка «enter — отправить» только на десктопе. Лимит 4000 — счётчик появляется после 3500.
Предупреждение о ключе — полоса над вводом цветом `mark`: «ключ @marta изменился. сверьте отпечаток лично. [доверять новому ключу]». Ввод заблокирован до подтверждения.
Первое отправленное сообщение за всю историю устройства → запрос разрешения на уведомления (см. «Уведомления»).
## Карточка контакта (`#/contact/<nick>`)
`@nick`, отпечаток 64 hex группами по 4 в две строки, строка «сверьте с собеседником голосом или лично». Если есть `pending` — оба отпечатка, старый и новый, кнопка «доверять новому ключу». Кнопка «убрать из списка».
## Участники (`#/room/<id>/members`)
Список ников; у владельца — пометка «владелец». Владельцу: строка ввода `@ник` + «добавить», у каждого участника «убрать». Всем: «выйти из комнаты»; владельцу — «удалить комнату» с подтверждением. Если клиент-владелец получил `needsRekey` и не может выполнить rekey из-за неподтверждённого ключа — полоса: «нужен новый ключ комнаты: подтвердите ключ @x».
## Настройки (`#/settings`)
- «ты: @nick», свой отпечаток.
- «уведомления»: состояние (`включены` / `выключены` / `запрещены в браузере`), кнопка включить/выключить. На iOS вне PWA — текст про установку.
- «установить приложение»: кнопка, если есть `beforeinstallprompt`; на iOS — инструкция «поделиться → на экран «домой»».
- «устройства»: список `id` (первые 8 символов), дата, «это устройство», «удалить».
- «история»: «занято N МБ»; «экспорт» → скачивание `.bare`; «импорт» → выбор файла → «добавлено N сообщений» / «архив создан другим аккаунтом» / «файл повреждён».
- «сменить пароль»: старый, новый, повтор; чекбокс «выйти на других устройствах». Ответ — «пароль изменён».
- «выйти»: подтверждение «история на этом устройстве будет удалена. экспортировать сначала?» с кнопками «экспортировать», «выйти», «отмена».
- «удалить аккаунт»: пароль + подтверждение «аккаунт и вся история будут удалены навсегда.» с кнопками «удалить» и «отмена».
## Баннер установки (iOS)
Показывается при `iPhone|iPad` и `navigator.standalone !== true`, над списком чатов: «уведомления на iOS работают только у установленного приложения: поделиться → на экран «домой»». Крестик — `installBannerDismissed`, повтор не показывается.
## Уведомления
Запрос разрешения — после первого успешно отправленного сообщения, один раз (`notificationsAsked`). После `granted``pushManager.subscribe` с `vapidPublicKey` и `PUT /api/devices/{id}/push`. Отказ — молча; включить можно в настройках.
## Сеть и состояния
- SSE переподключается браузером; после `ready` клиент перечитывает комнаты и контакты и повторяет `pending`.
- Без сети: полоса «нет соединения» цветом `stone` над вводом; ввод не блокируется — сообщения уходят в `pending`.
- `clock_skew` — «проверьте часы на устройстве: расхождение больше 5 минут».
- `401 unauthenticated` на любом запросе — выход на экран входа с сохранением IndexedDB (сессия истекла, история остаётся). Исключение одно: служебный выход перед повторным входом при смене пароля и удалении аккаунта (ADR-031) — там этот ответ означает, что сессии и так нет.
## Тексты состояний
Общие для всех форм строки (ADR-028). Ошибка — цветом `mark`, ответ об успехе — цветом `mute`, место одно.
| состояние | текст |
|---|---|
| запрос не дошёл | «нет соединения» |
| код ответа, на который нет сценария (`internal`, `too_large`, прочее) | «сервер не справился, попробуйте позже» |
| `429 rate_limited` | «слишком часто, попробуйте позже» |
| пароль короче 12 символов | «пароль: не короче 12 символов» |
| новый пароль и повтор различаются | «пароли не совпадают» |
| ключевой блоб не разобран, не расшифрован или не соответствует публичному ключу | «ключ аккаунта повреждён» |
| `iter` блоба не равен ответу `GET /api/kdf` | «параметры ключа не совпали» |
| `401 invalid_credentials` в настройках | «неверный пароль» |
## Доступность
Семантика: `nav`, `main`, `form`, `button`, `ul/li` для списков; `aria-live="polite"` на ленте; фокус в строку ввода при открытии чата на десктопе; контраст ink/bone и mark/bone не ниже 4.5:1; цели нажатия на мобильном не меньше 44 px.