Обновлено:

На этой странице

Общие заметки проекта

Страница: https://vm.ya-erm.ru/civilization/notes/. Пользователь явно выбрал общую ссылку без входа: любой, у кого есть адрес, может читать и редактировать один документ. Приглашения, учётные записи и права отдельных участников не добавлены.

2026-10-28 добавлен отдельный SVG favicon: зелёный лист с золотистым карандашом на тёмном фоне. Источник — services/shared-notes/favicon.svg; сборщик копирует его в dist/notes/ и добавляет хеш содержимого в URL, штатный публикатор включает файл. Для локального просмотра добавлен статический маршрут с MIME image/svg+xml; на основном сайте иконку отдаёт Caddy. Проверены отображение при 16/32/64 px и публичный ответ 200 с совпадением исходных байтов. Обновлены только favicon и ссылка в HTML, CSS/JS и пользовательский документ не менялись. Отчёт.

В редакторе можно одновременно писать Markdown, видеть имена, курсоры и выделения другого участника. Цвета участников различаются. Справа показывается просмотр; галочки задач - [ ] / - [x] меняют общий текст. Есть кнопки задачи, заголовка и жирного начертания, личные отмена/повтор, копирование ссылки и скачивание текущего Markdown. На телефоне редактор и просмотр переключаются вкладками.

Добавлена кнопка «Изображение». Она принимает публичный HTTP(S) URL, прежний путь к файлу или папке из загрузчика. При вставке папки добавляются все её картинки. Описание становится alt-текстом; место вставки хранится как относительные позиции Yjs и учитывает правки друга, пока открыто окно. Можно также скопировать готовый Markdown на странице загрузки и вставить его прямо в редактор. Картинки видны обоим участникам в просмотре, ограничены шириной панели и сохраняются как обычная Markdown-разметка. Доступ к исходным картинкам публичный по ссылке; список всех загрузок и служебные данные не открываются.

По дополнению 2026-10-28 в этом же окне можно загрузить одну картинку: кнопка «Обзор» справа от поля ссылки либо Ctrl+V / ⌘V с картинкой в буфере, когда фокус в поле. Обычная текстовая вставка ссылки/пути сохраняется. Лимит — 20 МиБ, как в отдельном загрузчике; файл попадает в ту же датированную папку проекта и получает публичный URL. Ссылка подставляется в поле после загрузки; описание и размер можно выбрать перед нажатием «Вставить». Есть прогресс, блокировка повторной отправки, отмена при закрытии окна и защита от результата старой операции после повторного открытия. Уже сохранённый файл остаётся в проекте, если закрыть окно без вставки.

services/shared-notes/image-upload.mjs использует существующий API наборов: создание → потоковая передача → восстановление по состоянию набора. Продолжающаяся передача не имеет общего двухминутного предела: 90 секунд без прогресса, 30 секунд на подтверждение, проверка сохранения через три секунды и одна автоматическая попытка при сетевом сбое. Потерянный ответ восстанавливается без повторного сохранения файла. Сервис загрузки и сервис заметок при публикации не перезапускались.

По уточнению 2026-10-28 размеры задаются непосредственно в Markdown:

![Описание](https://example.test/image.png =300x200)
![Описание](<https://example.test/image.png> =300x)
![Описание](https://example.test/image.png =x200)

300x200 задаёт рамку 300×200 px, 300x — ширину и пропорциональную высоту, x200 — высоту и пропорциональную ширину. Изображение вписывается без искажений; ширина ограничена панелью просмотра. В окне вставки есть поле размера, в том числе для целой папки. Допускаются положительные размеры до 10000 px. Расширение image-sizes.mjs применяет штатный валидатор URL Markdown-it; примеры в коде и кодовых блоках остаются текстом, неподдерживаемые/опасные адреса не превращаются в картинки. image-sizes.test.mjs прошёл. Публичные два браузера подтвердили фактические размеры 300×200, 300×112,5 и 533,33×200 для исходной картинки 320×120; значения сохраняются в общем Markdown. Сквозной отчёт.

Верх страницы также уплотнён: навигация, небольшой заголовок и ссылка другу объединены, слоганы и название игры убраны, сокращены отступы и инструменты. Начало рабочей области проверено выше 200 px на компьютере и 420 px при ширине телефона 390 px. Вкладка телефона называется «Просмотр». Старый путь к картинке/папке принимается в окне вставки заметок; дублирующая форма на странице загрузчика удалена. Сервисы и пользовательский документ при публикации не менялись.

Освободившаяся высота теперь отдана рабочей области: вместо 62vh/58vh и потолка 850 px редактор и просмотр заполняют остаток динамической высоты окна, прокручиваясь внутри панелей. На телефоне выбранная вкладка использует ту же область. Проверены 1920×1080, 1440×1600, 390×844, 390×600 и 320×700: на высоком окне собственно контент вырос до 1337,84 px; при высоте от 700 px вся страница помещается в окно без внешней прокрутки. При совсем низком окне сохраняется минимальная рабочая область 160 px и допускается прокрутка страницы, чтобы инструменты оставались доступны.

По замечанию о плохой видимости выделения заменён закрывавшийся непрозрачной строкой слой на нативное выделение браузера: светло-золотой фон и тёмный текст, измеренный контраст 11,16:1. Выделение другого участника стало плотнее, с белыми буквами и светлой границей; курсоры и выделения по-прежнему передаются Yjs. Для тёмного редактора также заданы читаемые цвета Markdown-ссылок, заголовков и маркеров вместо стандартных цветов светлой темы.

После отдельного замечания пользователя о синих ссылках они заменены на ярко-золотистые #ffd166 с постоянным подчёркиванием 1,5 px и отступом 3 px в редакторе и просмотре. Обычный текст сохраняет цвет #eee7d6; посещённые ссылки имеют тот же читаемый цвет, наведение усиливает подчёркивание. Публичный Chromium на отдельном документе подтвердил отличие от обычного текста, контраст 10,08:1 в редакторе и 9,55:1 в просмотре, отсутствие ошибок и горизонтальной прокрутки на телефоне. Отчёт проверки ссылок. Опубликованы только статические файлы заметок, пользовательский документ не редактировался.

По уточнению пользователя 2026-10-28 максимальная ширина контейнера увеличена с 1320 до 1600 px. Публичная проверка Chromium подтвердила контейнер 1600 px и две панели по 792 px при окне 1920 px; при окне 390 px контейнер занимает 358 px без горизонтальной прокрутки. Опубликованы только CSS и HTML с новым хешем стилей, сервис не перезапускался.

Синхронизация и хранение

Уточнение оформления 2026-10-28: подпись «Ваше имя» не переносится, поле имени сужено до 170 px, статусы сети и сохранения расположены рядом с разделителем. Панель стала компактнее: на компьютере высота 56 px. Публичная проверка Chromium при ширине 1365, 390 и 320 px подтвердила одну строку подписи и статусов, совпадение вертикальных центров и отсутствие горизонтальной прокрутки. Опубликованы только HTML/CSS, документ и сервис не изменялись.

Используются стабильные Yjs 13.6.33, y-websocket 3.1.0, y-codemirror.next 0.3.6 и CodeMirror 6. Правки объединяются CRDT; сервер не заменяет весь документ последним полученным текстом. Индивидуальный UndoManager отменяет свои правки, сохраняя изменения друга. Курсоры и имена передаются через awareness, не записываются в Markdown и исчезают после отключения участника.

В браузере y-indexeddb хранит локальную копию. Можно продолжить писать в открытой вкладке при обрыве связи; при подключении независимые изменения объединяются. Это не отдельное приложение для открытия без интернета: HTML/JavaScript без сети специально не кешируются сервис-воркером. Если локальное хранилище недоступно, серверная синхронизация продолжает работать, а интерфейс предлагает сохранить Markdown перед закрытием.

Сервер — services/shared-notes/server.mjs. Он использует стандартные sync/awareness-протоколы Yjs и WebSocket, проверяет Origin и имя документа, ограничивает отдельное сообщение 2 МБ, проверяет соединения ping/pong. Источники: y-websocket, связка с CodeMirror, локальное хранение. Версии зависимостей закреплены в package-lock.json; в итоговом дереве одна версия Yjs, без предварительной Yjs 14 и LevelDB.

Основной документ сохраняется в проекте:

  • collaboration-data/notes/project.md — читаемый Markdown.
  • collaboration-data/notes/project.ydoc — полное состояние для продолжения совместной работы.
  • collaboration-data/notes/project.json — время и SHA-256 текста для подтверждения сохранения.

Запись выполняется после каждого изменения через временный файл, fsync и переименование. Состояние сохраняется до рассылки правки другим участникам. Если запись не удалась, статус сообщает об ошибке и не подтверждает сохранение; локальная копия и скачивание Markdown доступны отдельно. Редактор показывает «Сохранено» только после сверки хеша текущего текста с сервером. Повреждённое сохранение не заменяется пустым документом.

Каталог исключён из Git и находится вне публичной папки сайта. Через публичный HTTP API доступны состояние сохранения и экспорт Markdown того же открытого документа — в соответствии с выбранным доступом по общей ссылке. Полноценная история версий, автоматические резервные копии на другой сервер, комментарии Google Docs и управление ролями пока не реализованы. Перезапуск контейнера сохраняет документ на диске. Браузерные локальные копии могут вернуть старые правки при переподключении: это часть объединения изменений, а не система отката версий.

Основной адрес открывает документ project. Параметр ?doc=<имя> задаёт независимый документ; он используется для изолированной публичной проверки, не затрагивающей заметки пользователей. Кнопка копирования сохраняет этот параметр в ссылке.

Сборка и публикация

Исходники страницы и отдельный package.json находятся в services/shared-notes/. Команды: npm ci и npm run build из этой папки. Сборка записывает HTML, CSS и самостоятельный JS в dist/notes/; браузер не загружает зависимости с внешних CDN. URL ресурсов содержат хеш содержимого. Исходники и готовая сборка сохраняются в репозитории, node_modules — нет.

deployment/shared-notes.compose.yml запускает отдельный civilization-shared-notes из имеющегося node:22-alpine. Контейнер работает от UID 65534 с read-only кодом, без capabilities, с ограничениями ресурсов и записью только в постоянный каталог данных. Порт на хост не публикуется; Caddy проксирует /civilization/notes-api/*, включая WebSocket. Игра и загрузчик картинок используют прежние обработчики.

Публикация: python3 scripts/deploy-shared-notes.py с доступом к Docker и папке действующего сайта. Скрипт сверяет активную конфигурацию Caddy с файлом, сохраняет её в reference-original/shared-notes-deployment/, поднимает сервис, проверяет health, публикует только dist/notes/ и добавляет ссылку «Общие заметки» в меню. Новая ссылка вставляется под блокировкой общего скрипта публикации игры. Другие игровые файлы из параллельной разработки не выкладываются. Общая публикация игры исключает папки notes/upload; статические правки этих страниц публикуются отдельно.

Проверки

node --test server.test.mjs из services/shared-notes/ прошёл: два настоящих WebSocket-клиента, одновременная вставка, имена участников, личная отмена, слияние автономных и сетевых правок, совпадение Markdown и SHA-256, проверка Origin/путей, перезапуск сервера и загрузка новым клиентом, экспорт и удаление отключившегося участника.

PLAYWRIGHT_BROWSERS_PATH=/tmp/civ3-browser-check/browsers node tests/browser-shared-notes.cjs поднимает локальный сервер и два независимых Chromium. Для основного сайта задаётся CIV3_NOTES_URL=https://vm.ya-erm.ru/civilization/notes/. Проверяются оба удалённых курсора, разные цвета, одновременный ввод, галочки, личная отмена без удаления чужой правки, реальный обрыв WebSocket с продолжением ввода и последующим объединением, совпадение файла на диске, перезагрузка, ссылка, скачивание, блокировка Markdown-скрипта и javascript-ссылки, мобильный просмотр и выход друга.

Страница опубликована. Локальный и публичный браузерные прогоны прошли без ошибок страницы: browser-shared-notes-verification.json. Все проверки используют отдельный случайный документ и удаляют только его файлы; основной project не меняют.

Первое сравнение DOM включило подписи курсоров в текст; драйвер исправлен на извлечение только содержимого, виджеты курсоров исключены. Условия проверки реального файла и равенства документов сохранены. Первый публичный прогон остановился на ожидании поля имени с пределом 15 секунд; повтор с диагностикой и 45 секундами для браузерных действий прошёл полностью. Проверка одновременного изменения одного и того же маркера задачи отдельно не проводилась: состояние задач хранится в общем Markdown, отдельной структурной модели задач пока нет.

При первом запуске контейнера npm-зависимости имели ограниченные права чтения, из-за чего непривилегированный Node не находил ws. Скрипт публикации теперь нормализует права чтения и прохода по дереву зависимостей; код по-прежнему смонтирован read-only. Публичный маршрут включён после успешной проверки health исправленного сервиса. Файл THIRD-PARTY-LICENSES.txt содержит лицензии библиотек готовой сборки.

Изображения и новое выделение опубликованы и прошли локальный и публичный tests/browser-image-notes.cjs двумя независимыми браузерами: Markdown из загрузчика, публичный URL, прежний путь к файлу/папке, параллельная правка до вставки, одновременный показ картинок, мобильная ширина, запрет javascript URL, точные байты картинки и сохранение документа. Дополнительно проверены реальные цвета собственного и чужого выделения и сделаны снимки. Отчёт: browser-image-notes-verification.json. Первый прогон обнаружил старый JS-файл сборки без обработчика окна вставки; повторная сборка и проверка содержимого артефакта исправили несоответствие до публикации. Сервис заметок при этом обновлении не перезапускался.

Дополнение 2026-10-28 опубликовано и проверено тем же сценарием локально и на основном HTTPS-сайте: постоянная ссылка из загрузчика, заполнение высоты окна, «Обзор» с настоящим выбором файла, системный буфер в поле на мобильной ширине, обычная текстовая вставка, отказ при превышении лимита на байт без создания набора, потерянный POST-ответ без второго POST, закрытие во время передачи и повторное открытие. Обе новые картинки видны второму участнику и сохраняются в Markdown. Ошибок страницы нет; тестовые файлы и отдельный документ удалены, основной project не редактировался. Первый вариант проверки выявил минимальную высоту панели, мешавшую окну 320×700; она уменьшена до 160 px. В драйвере сравнение буфера исправлено на байты фактически вставленного File: браузер/ОС перекодирует исходный PNG при помещении в системный буфер. Перед вставкой новых тестовых картинок курсор явно возвращается в конец, чтобы не заменять текст, выделенный предыдущей проверкой контраста.