Files
bare/docs/ui.md
T
mayatnikovandClaude Fable 5 ae55c846aa ADR-015…024 и спецификации v1: криптография, протокол, хранение, UI, деплой, план
Закрыты все открытые вопросы проектирования. Пароль не покидает клиент
(два ключа из мастера), TOFU для публичных ключей, устройства и конверт,
атомарный rekey комнат, регистрация и контакты, схема SQLite и драйвер
без cgo, сессии и CSRF, деплой через nginx+systemd, правила пушей,
айдентика «Скобы» на системном mono. Иконки PWA в web/icons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-22 10:53:24 +03:00

9.2 KiB
Raw Blame History

Интерфейс

Визуальная система — 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, _», «нужен инвайт-код», «инвайт-код не подходит».

Список чатов (сайдбар)

Секции «каналы» и «личные», как в моке. Активный чат — инверсия (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). После grantedpushManager.subscribe с vapidPublicKey и PUT /api/devices/{id}/push. Отказ — молча; включить можно в настройках.

Сеть и состояния

  • SSE переподключается браузером; после ready клиент перечитывает комнаты и контакты и повторяет pending.
  • Без сети: полоса «нет соединения» цветом stone над вводом; ввод не блокируется — сообщения уходят в pending.
  • clock_skew — «проверьте часы на устройстве: расхождение больше 5 минут».
  • 401 на любом запросе — выход на экран входа с сохранением IndexedDB (сессия истекла, история остаётся).

Доступность

Семантика: nav, main, form, button, ul/li для списков; aria-live="polite" на ленте; фокус в строку ввода при открытии чата на десктопе; контраст ink/bone и mark/bone не ниже 4.5:1; цели нажатия на мобильном не меньше 44 px.