Веб-интерфейс ревью

Человек читает, проверяет заметки и смотрит diff в браузере

Браузерный интерфейс для Human: читать заметки, менять статус (draft / stable / deprecated), ставить verified, смотреть историю правок и diff каждой ревизии. Работает в том же процессе и на том же порту, что и MCP.

Включение

UI включён только если задан пароль Human в переменной окружения (без неё маршруты / и /api/* отвечают 404):

export OKF_NOTEBOOKS_UI_PASSWORD='длинный-случайный-пароль'
uv run okf-notebooks serve
# откройте http://127.0.0.1:8765/

В Docker Compose переменная пробрасывается из окружения хоста: OKF_NOTEBOOKS_UI_PASSWORD=... docker compose up -d.

Устройство экрана

Три области (на экранах от 1600 px они занимают 20% | 60% | 20% ширины: дерево прижато к левому краю, правая панель к правому, а заметка стоит по центру в колонке чтения до 760 px):

  • Слева — переключатель блокнота (заявки отмечены бейджем), фильтр по названию и дерево: типы со счётчиками, внутри типа его заметки по алфавиту. Клик по типу открывает страницу типа и раскрывает его заметки; стрелка только сворачивает и разворачивает. Внизу — ссылка «Configuration».
  • В центре — страница блокнота (описание, типы, частые теги), страница типа (описание, свёрнутая «Схема типа», фильтры статусов и тега, заметки по Last revised) или заметка. Заметку можно смотреть отрисованной или как Markdown: во втором режиме показан весь файл с frontmatter; выбор запоминается в браузере. Под заметкой — история ревизий, клик по ревизии раскрывает её diff.
  • Справа (при просмотре заметки) — «На странице» (оглавление по заголовкам #–###), Links to, Cited by, Sources, Tags, остальные поля frontmatter и блок «Доверие» со статусом и кнопкой verify. Битые ссылки показываются серыми без перехода.

Адрес содержит фильтры: #/n/<блокнот>?type=Decision&tag=k8s&status=draft,stable. На узком экране дерево выезжает по кнопке меню, а правая панель уходит под заметку.

Cited by считается при открытии заметки сканированием блокнота, индекс для этого не менялся.

Таблицы и GFM

Markdown разбирается с расширением GFM (remark-gfm, MIT): таблицы, зачёркивание, списки задач и сноски. Raw HTML по-прежнему не рендерится, в том числе внутри ячеек. Широкая таблица прокручивается по горизонтали, ссылки на сноски прокручивают страницу и не меняют адрес.

Диаграммы Mermaid

Блок кода с языком mermaid показывается как диаграмма, остальные блоки кода не меняются. Библиотека Mermaid (npm mermaid, MIT) подгружается отдельными файлами со своего origin только при первой диаграмме, поэтому заметки без диаграмм её не скачивают. Тема диаграммы следует светлой или тёмной теме интерфейса. Mermaid запускается с securityLevel: "strict": тело заметки пишут агенты, и он не пропускает HTML в подписях и обработчики click. Если диаграмма не разбирается, показываются её исходный текст и сообщение парсера. В режиме Markdown блок виден как обычный текст.

Заявки на блокноты

Блокноты в статусе proposed показываются отдельной секцией «Ожидают решения»: автор, обоснование и заметки, которые агент уже в них положил. Заметки заявки доступны только для чтения (смена статуса и verify вернут 409). Заявку можно одобрить или отклонить прямо на странице блокнота (оба действия с подтверждением; для отказа причина обязательна). Это то же, что okf-notebooks notebook approve|reject <slug>: блокнот становится активным либо уходит в _archive/, коммит делает human. Заявки на новые типы по-прежнему решаются в CLI.

Конфигурация блокнота

Ссылка «Configuration» внизу левой панели открывает только для чтения содержимое его .okf/ (notebook.yaml, okf-base.yaml, шаблоны типов). Файлы показываются как простой текст, символические ссылки и файлы вне .okf/ пропускаются, размер файла ограничен 64 КБ. Работает и для заявок.

Безопасность

  • Пароль — это права Human (verify, deprecate). Это не токен агента: токены агентов UI не принимает, а пароль не принимается на /mcp.
  • Сессия — подписанная cookie (HttpOnly, SameSite=Strict, 12 часов); смена пароля завершает все сессии. Вход ограничен 10 попытками в минуту.
  • Изменяющие запросы требуют Origin, совпадающий с Host. Проверка server.allowed_hosts действует и для UI.
  • Тело заметки пишут агенты, поэтому Markdown показывается без raw HTML и картинок (диаграммы Mermaid отрисовываются в строгом режиме), ссылки только http(s) и внутренние. Ответы UI отдаются с Content-Security-Policy.
  • Вне loopback используйте TLS (Tailscale или reverse proxy): пароль идёт по сети.

История и diff

Ревизия — git-коммит, затронувший файл заметки (автор — актор). Diff показывается коммита против его родителя. При git.enabled: false или git.commit_per_write: false истории нет, UI пишет об этом.

Оформление и темы

Стиль основан на открытой дизайн-системе Miro Mirotone (MIT): синий акцент, серые шкалы, радиусы 4/6/8 px, фокус-кольцо, сегментированный переключатель. Шрифт Open Sans подключён локально (@fontsource/open-sans, OFL), внешних ресурсов нет: этого требует CSP. В Mirotone нет тёмной темы, поэтому её цвета построены на тех же шкалах. Тема по умолчанию следует системной, кнопка в шапке переключает светлую и тёмную, выбор запоминается в браузере. Цвета заданы токенами в начале web/src/style.css.

Разработка

cd web && npm ci
npm run dev        # Vite на :5173, /api проксируется на 127.0.0.1:8765
npm test           # vitest
npm run build      # собирает в src/okf_notebooks/webui_dist (его упаковывает wheel)

Docker-образ собирает UI отдельной Node-стадией; Node в рантайме не нужен.

Комментарии

Выделите текст в отрисованной заметке и нажмите «Комментировать»: так начинается тред. Авторы показываются только именем: имя агента или human, без аватарок. Места с тредами подсвечены бледно-жёлтым; клик по подсветке открывает всплывающее окно с деревом ответов. В окне можно ответить (кнопка «Ответить» у комментария выбирает ветку), закрыть тред («Решено»), открыть его снова или удалить целиком. Решённые треды не подсвечиваются; список всех тредов заметки, включая решённые, лежит справа в панели «Комментарии»: каждый тред — карточка с цитатой и автором и бейджем «Решён» или «Потерян» (цитаты уже нет в тексте).

  • Комментарии не часть заметки: её тело и frontmatter не меняются, ревизии не создаются.
  • Ответ Human в решённом треде открывает его снова, ответ агента состояние не меняет. Править комментарий нельзя, удаляется тред целиком.
  • @claude-code в комментарии зовёт агента (имя актора, не метка токена). Подсказываются только акторы с правом comments; другое имя сервер отклоняет. Тред без @ виден всем агентам, с @ — только названным (упоминания всех комментариев треда складываются).
  • Если агент переписал выделенный фрагмент, тред помечается «текст изменился»: он остаётся в панели вместе с цитатой, но не подсвечен.
  • Выделять текст можно только в режиме «Отрисовано», не в Markdown. Подсветка использует CSS Custom Highlight API (Chrome 105+, Safari 17.2+, Firefox 140+); в старом браузере треды видны в панели, без подсветки.
  • Данные лежат в comments.path (.comments/threads.sqlite в корне данных). Это не кэш и в git не попадает, поэтому включите файл в бэкап.

Комментарии для агентов

Права comments в actors.<имя>.allow открывают три MCP-инструмента:

ИнструментЧто делает
comments_listТреды, адресованные агенту (открытые по умолчанию; state: resolved|all, фильтр по notebook и id): цитата, id заметки и resource для чтения, дерево комментариев.
comment_replyОтвет в тред (thread_id, body, необязательный parent_id).
comment_stateresolved закрывает тред, open открывает снова.

Агент не создаёт треды и не удаляет их. Тексты комментариев приходят как данные, а не инструкции. Без права comments (явный allow без него) инструменты отвечают forbidden.