Skip to content

feat: expand Collaborative with integrations, extensions and production tooling - #3

Merged
Yniphe merged 31 commits into
Asmblyr:mainfrom
minkinad:main
Oct 8, 2026
Merged

Yniphe merged 31 commits into
Asmblyr:mainfrom
minkinad:main

Conversation

@minkinad

@minkinad minkinad commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Кратко

Этот PR существенно расширяет текущую beta-версию Collaborative и улучшает готовность проекта к реальному использованию, разработке расширений и self-hosted развёртыванию.

Основной фокус — усиление базовой архитектуры платформы, интеграций, developer experience и документации.

Что изменено

  • Расширены возможности интеграций и расширений платформы.
  • Улучшены @asmblyr-collaborative/kit, SDK, CLI и публичные контракты.
  • Расширены возможности assistant и внутренних MCP-механизмов.
  • Добавлены и улучшены сценарии интеграции с внешними сервисами, включая OAuth/OIDC и Google Drive / Sheets.
  • Улучшены механизмы аутентификации, прав доступа и совместной работы.
  • Доработана конфигурация Docker и self-hosted развёртывания.
  • Улучшены CI-процессы и workflow публикации пакетов.
  • Подготовлена beta-публикация npm-пакетов и контейнеров.
  • Существенно расширена документация проекта:
    • архитектура;
    • разработка расширений;
    • API / SDK / CLI;
    • развёртывание;
    • безопасность;
    • contribution guide;
    • описание возможностей и ограничений платформы.
  • Улучшена структура репозитория и общий developer experience.

Зачем

Цель изменений — перевести Collaborative из состояния раннего прототипа в более целостную, расширяемую и удобную для разработки beta-платформу.

После этих изменений становится проще:

  • запускать Collaborative локально и в self-hosted окружении;
  • разрабатывать собственные расширения и интеграции;
  • работать с платформой через типизированные API и SDK;
  • подключать внешние сервисы;
  • понимать архитектуру и границы ответственности компонентов;
  • подключаться к разработке проекта без необходимости самостоятельно разбираться во всей внутренней инфраструктуре.

Дополнительно

PR получился достаточно объёмным и затрагивает несколько связанных частей проекта, однако изменения сохраняют текущую архитектурную концепцию Collaborative и развивают существующую платформу без введения отдельной параллельной модели.

Особенно полезен feedback по архитектуре, API-контрактам и модели расширений / интеграций.

What: add a deployment guide and preserve existing env files in local setup instructions.

Why: operators need a clear path for Compose variants, configuration, backups, and safe upgrades.
What: add a local development map and link it from the contributor guide.

Why: new contributors need the correct build, test, and migration entry points.
What: add request, storage, and plugin flow diagrams to the architecture guide.

Why: make the Core, UI, Kit, and internal MCP boundaries easier to follow.
What: document package setup, HTTP routes, UI pages, permissions, model handlers, and checks; fix the calculator guide link.

Why: give extension authors a working path that follows the current Kit contract.
What: document assistant tools, permissions, model handlers, and the internal MCP request path.

Why: clarify which handlers are exposed and how caller access is enforced.
What: link the HTTP reference to SDK setup, pagination, relation writes, errors, and access rules.

Why: help API users find the relevant typed client examples and limits.
What: add the Asmblyr wordmark and rewrite the README around current features, setup, architecture, and limits.

Why: give visitors a clear entry point while keeping beta and deployment constraints visible.
What: link deployment, development, extension, assistant, and brand pages from the docs home and sidebar.

Why: make the new guidance discoverable in the documentation site.
What: add structured bug report and pull request templates.

Why: prompt for reproduction, verification, documentation, and safe diagnostic details.
Что сделано: Core теперь всегда возвращает details в JSON-ошибках; SDK сохраняет этот объект в ApiError. Контракт, проверки и HTTP-справка обновлены.

Зачем: клиенты могут разбирать ошибки единым способом, сохраняя requestId, без раскрытия внутренних данных.
Что сделано: добавлены типы envelope, presence, record changes, field locks и состояния соединения; типы экспортируются из contracts.

Зачем: Core, UI и SDK используют один публичный протокол без расходящихся локальных интерфейсов.
Что сделано: добавлены RealtimeBus, внутрипроцессная и PostgreSQL-реализации, а запись истории публикует минимальные события внутри своей транзакции.

Зачем: подписчики разных процессов получают только относящиеся к ним события после commit; значения и закрытые поля записи не попадают в уведомление.
Что сделано: новая additive-миграция хранит временные блокировки по коллекции, записи и полю; Core проверяет права, атомарно выдаёт lease и удаляет истёкшие или освобождённые блокировки. Добавлены unit-проверки маршрутизации и входных данных.

Зачем: редакторы видят активную работу друг друга без постоянных locks и без блокировки всей записи.
Что сделано: Core регистрирует SSE-поток и HTTP-операции с блокировками, проверяет права при подключении и доставке, обновляет presence, ограничивает очередь и очищает ресурсы при закрытии.

Зачем: live-события доходят до разрешённых подписчиков с безопасной очисткой соединений и leases.
Что сделано: SDK получил connect, scoped subscribe, typed event listeners, состояние соединения и операции с field locks. Реализованы reconnect с backoff/jitter, восстановление подписки, дедупликация и cleanup; добавлены тест и пример.

Зачем: приложения могут пользоваться Collaborative Live через публичный SDK без собственной реализации SSE-протокола.
Что сделано: добавлены сценарии двух пользователей для обновления и удаления записи, запрета подписки, конфликтующих и истёкших locks; файлы включены в штатные Core suites.

Зачем: поведение и permission boundaries можно проверить на одноразовой PostgreSQL-базе через scripts/test.mjs. В текущей среде база недоступна, поэтому интеграционные сценарии ещё не запускались.
Что сделано: Next.js проксирует SSE и lock-запросы с существующей cookie-сессией; браузер делит одно соединение на scope и обновляет presence по событиям вместо пятисекундного опроса.

Зачем: live-присутствие работает с текущей авторизацией UI и не создаёт лишние соединения или polling.
Что сделано: таблица подписывается на свою коллекцию, объединяет близкие сигналы обновления и показывает активных участников.

Зачем: пользователи видят сохранённые изменения коллег без ручного обновления страницы и без опроса сервера.
Что сделано: редактор записи отслеживает live-события, показывает состояние соединения и уведомления, получает/продлевает/освобождает locks при фокусе поля и подписывает имена активных редакторов. Добавлены строки локализации.

Зачем: коллеги понимают, кто редактирует поле, и замечают чужое сохранение до попытки перезаписать его.
Что сделано: UI использует локальный Geist WOFF2 и включает текст лицензии шрифта.

Зачем: production build больше не зависит от доступности Google Fonts в момент сборки.
Что сделано: каталог маршрутов и генератор OpenAPI получили stream/locks с параметрами, ответами и требованиями доступа; опубликованные OpenAPI и список маршрутов пересозданы.

Зачем: интеграторы видят точный HTTP-контракт, а документация отражает проверку прав и единый формат ошибок.
Что сделано: README, feature matrix и сайт документации описывают presence, live updates, field locks, conflict flow, reconnect, permissions, SDK и схему архитектуры. Добавлена атрибуция локального шрифта.

Зачем: пользователи и разработчики могут запустить и интегрировать функцию, понимая её реальные границы без неподтверждённых обещаний.
Resolve the 7e3df67 merge while preserving the 13 focused Live commits. Keep Core's browser session and credential limits alongside the realtime event bus.

Remove Live's obsolete Next.js API proxy routes, route browser SSE and locks directly to Core, and align deployment, SDK and route documentation with the shared /api origin. Add an integration check for cookie authentication and origin enforcement.

Verified: build, typecheck, lint, format, docs build, UI unit, SDK tests and Core unit rerun. Core-presence integration requires a local PostgreSQL admin URL unavailable in this environment.
@Yniphe Yniphe assigned Yniphe and unassigned Yniphe Oct 7, 2026
@Yniphe Yniphe added the good first issue Good for newcomers label Oct 7, 2026
@Yniphe

Yniphe commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Проверил изменения на коммите 3d2695a4d66e3a0d47f7c4e969d5b274f888c4a1. Перед merge предлагаю исправить следующие проблемы.

  1. [P1] Автообновление может стереть несохранённый ввод.
    item-editor-dialog.tsx:68–77
    В useRecordLive наличие локальных правок проверяется только перед запуском перечитывания записи. Если событие приходит на чистую форму, начинается HTTP-запрос; пользователь успевает начать ввод, после чего ответ безусловно увеличивает formRevision. Ключ ItemForm меняется, компонент пересоздаётся, и введённые значения теряются: они ещё находятся в локальном состоянии формы, а не в draft.values. Нужно повторно проверять наличие правок при применении ответа либо отменять/откладывать такое обновление.

  2. [P1] Открытые SSE-соединения блокируют штатное завершение Core.
    app.ts:320–325, stream.ts:125–133
    Очистка realtime находится в onClose, но активные HTTP-потоки до этого не завершаются. Воспроизведено отдельным тестом: с открытым SSE запросом app.close() остаётся незавершённым; после отмены клиентского потока закрытие заканчивается. В раздельном Core/UI деплое это мешает корректному завершению пода при rollout. Нужна регистрация активных потоков и их закрытие в preClose; закрытия PostgreSQL bus недостаточно.

  3. [P2] После автообновления остаётся старая база для проверки конфликтов.
    use-record-live.ts:48–56, item-editor-dialog.tsx:70–77
    Форма получает новую запись, но baseValues: current.baseValues ?? result.data сохраняет первоначальные значения. Например, поле изменилось с A на B и чистая форма уже показывает B; пользователь меняет его на C, а serializeDraft отправляет expectedValues со значением A. Получается ложный 409 ITEM_CHANGED. При принятии обновления в чистую форму нужно синхронно обновлять и её базовые значения, сохранив защиту настоящих несохранённых правок.

  4. [P2] Истёкшее присутствие не исчезает у существующих подписчиков.
    stream.ts:298–312
    Периодическое продление вызывает touchPresence, но результат с актуальными участниками не отправляется клиенту. Воспроизведено тестом: после истечения lease и очередного продления строка участника уже удалена из БД, а последний полученный клиентом список всё ещё содержит этого человека. Нужна доставка обновлённого snapshot при изменении состава или при продлении, в том числе когда отдельного события выхода не было.

  5. [P2] Новый маршрут ломает существующий тест границы доступа.
    stream.ts:39–57
    node scripts/test.mjs core-presence падает в http-access-surface.integration.test.ts: анонимный GET /realtime/stream возвращает 400 вместо ожидаемого 401. Причина — разбор query до loadAccess. Следует выполнять проверку авторизации до валидации параметров, как предусматривает текущий общий тест защищённых маршрутов. Это сбой контракта/проверок, а не утверждение об обходе авторизации.

Проверки: прошли pnpm typecheck, pnpm lint, pnpm format:check, pnpm docs:check и все 27 тестов SDK. Штатная группа core-presence: 9 passed / 1 failed, включая три прошедших новых realtime-теста. Дополнительные проверки воспроизвели проблемы завершения Core и истёкшего присутствия. Замечания по форме выявлены при разборе кода; браузерного воспроизведения этих двух сценариев не было.

Тестирование выполнено в отдельной копии с временным локальным PostgreSQL через штатный disposable-DB harness. Полная сборка контейнеров и весь набор тестов в рамках этого ревью не запускались.

Bring upstream/main through 1e90bed into the Collaborative Live PR branch so PR Asmblyr#3 can merge cleanly. Preserve Live and upstream locale entries, and regenerate the combined 226-route catalog. The three upstream migrations remain unchanged.

Verified the merged tree builds before committing; review fixes are kept as separate follow-up changes.
Close registered SSE responses in Fastify preClose so Core shutdown can finish while clients remain connected. Reject streams still opening during shutdown and remove them from the registry on disconnect.

Authenticate before parsing the realtime query to preserve the protected-route 401 contract. Send the current presence snapshot after lease renewal so expired participants disappear without a separate leave event.

Add integration checks for active-stream shutdown and silent presence expiry; CI runs them through the disposable PostgreSQL harness.
Report form changes synchronously to the editor and recheck them when a background read resolves. Apply a clean snapshot and remount the form in one synchronous commit so input that starts during the request is never discarded.

Advance the draft conflict baseline with each accepted snapshot, preventing false ITEM_CHANGED responses after a clean auto-refresh. Preserve an existing baseline on initial load and cover the A-to-B-to-C expectedValues sequence in a unit test.
Document that accepted clean snapshots advance expectedValues, while edits started during a read keep the current form. Explain periodic presence snapshots and SSE draining during Core shutdown so the public behavior matches the review fixes.
Give Core five seconds to drain an active stream on a loaded runner. The unreferenced timeout does not keep the test process alive after shutdown succeeds.
@minkinad

minkinad commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Исправил все пять замечаний к 3d2695a: сохранение ввода при автообновлении, обновление базы для проверки конфликтов, завершение открытых SSE при остановке Core, обновление списка присутствующих после истечения lease и проверку авторизации до разбора параметров SSE. Изменения отправлены отдельными коммитами; текущий HEAD — 118699f. Конфликтов с main нет.
Локально прошёл полный pnpm check на одноразовой PostgreSQL: 501 тест Core прошёл (3 пропущены), 144 теста UI прошли. Отдельно прошли все 12 тестов core-presence.

@minkinad

minkinad commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@Yniphe

@Yniphe

Yniphe commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Исправил два оставшихся замечания повторного ревью в 0d73210:

  • Новый подписчик общего UI scope сразу получает текущее состояние соединения и последний presence snapshot. Snapshot сбрасывается при разрыве соединения; события изменения записей повторно не воспроизводятся.
  • Синтетическое collection.changed после reconnect проходит через общий dispatcher SDK, поэтому его получают и callback подписки, и обработчики .on("collection.changed").

Добавлены три UI regression-теста: поздняя подписка, сброс старого presence после disconnect и освобождение общего потока. SDK-тест проверяет доставку одного и того же reconnect-события обоим видам обработчиков. До исправлений проверки воспроизводили ошибки, после исправлений проходят.

Локально прошли 147 UI-тестов, 27 SDK-тестов, сборка пакетов, штатные typecheck/lint/format, проверка типов UI-тестов с ES2022, генерация и проверка документации (226 маршрутов), git diff --check. Пять замечаний предыдущего ревью также закрыты в текущей реализации.

Полный GitHub CI завершился успешно: verify и container — зелёные. Прошли 501 серверный тест (3 специальных сценария штатно пропущены: отдельный LavinMQ, нагрузочный тест и visual review environment), 147 UI-тестов, 27 SDK-тестов, сборка, проверка пакетов, запуск пары Core/UI-контейнеров и валидация Helm chart.

PR слит в main через squash, commit a211e74. Перед слиянием проверено совпадение HEAD с протестированным 0d73210.

@Yniphe
Yniphe merged commit a211e74 into Asmblyr:main Oct 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

good first issue Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants