feat(mcp): drawio стадия 3 — семантические тулы fromGraph/fromMermaid/editCells (#425) #472

Merged
vvzvlad merged 1 commits from feat/425-drawio-graph into feat/443-get-page-context 2026-07-10 23:45:47 +03:00
Collaborator

Summary

Drawio стадия 3 — семантические инструменты: модель описывает диаграмму графом (узлы/группы/связи с иконками), а координаты, стили, иконки и лейаут считает сервер. Весь класс ошибок налезаний/пустых иконок/кривых интервалов уходит by construction. Сырой XML остаётся escape-hatch для экзотики. closes #425

Стоит стопкой на #443 (feat/443-get-page-context, PR #470) — база указывает на неё, в диффе только стадия 3. Переиспользует конвейер #423 и shape-index/elkjs/линтер #424.

Инструменты (camelCase)

  • drawioFromGraph (главный) — граф → резолв иконок (#424; неизвестная → generic по kind с подписью, не пустой квадрат) → стили из пресета → elkjs-layered с compound-группами → линтер-чистый ассемблер XML. Хинты pinned/sameLayerAs/layer и layout: none|full|incremental — детерминированным post-pass'ом (ELK-констрейнты оказались ненадёжны в прямом тестировании). Пресеты default/dark/colorblind-safe (Okabe-Ito) — данные.
  • drawioFromMermaid — чистый парсер flowchart (без браузера/CLI) → тот же graph-конвейер. Формы/направление/пунктир=async/subgraph→группы/цепочки; не-flowchart отвергает внятно. (Сделан, не дропнут — парсер тянется как чистый TS.)
  • drawioEditCells — ID-based add/update/delete, delete каскадит на детей контейнера и связанные рёбра; baseHash обязателен (optimistic lock).

Безопасность / устойчивость (важно — вход от LLM)

  • DoS-границы: MAX_GRAPH_EDGES=1000/GROUPS=500 (узлы уже 500), mermaid MAX_CHARS=200k/LINES=20k/GROUPS=500, chain 500 — все с быстрым throw ДО лейаута/ассемблера. Критично: ассемблер и graph→ELK-маппер работают вне 5-сек ELK-таймаута, без границ граф в 200k рёбер клал воркер по OOM (поймано адверсарно на ревью).
  • incremental сохраняет неперечисленные существующие ячейки (mergeExistingCells) — «добавь один узел» не стирает ручную расстановку человека.
  • sameLayerAs/layer после снапа раскладываются по перпендикулярной оси с зазором ≥150 → 0 quality-warnings; pinned — точные пользовательские координаты (clamp ≥0; гарантия «0 by construction» относится к авто-лейауту, явные пины — user-directed).
  • Сентинелы 0/1 защищены от delete.

How verified

  • mcp tsc чисто; node --test782/782 (57 новых): приёмка (15+ узлов / 2 вложенные группы / AWS-иконки → 0 lint, 0 quality-warnings, все иконки резолвятся, группы прозрачные, направление; hints; incremental без сдвига существующих; edit_cells update-one / cascade-delete / stale-baseHash; снапшоты пресетов + colorblind-safe Okabe-Ito; mermaid ветвление+subgraph) + регрессии на все DoS-границы (быстрый reject, без запуска OOM-кейса).
  • prosemirror-markdown tsc чисто; apps/server jest (shared-tool-specs.contract + ai-chat-tools.service) — 290/290 (3 тула заведены in-app).

Внутренний цикл

2 прохода. Первый (адверсарно, с эмпирическими пробниками на реальном линтере/ELK) поймал 2 CRITICAL DoS (нет лимита рёбер → OOM воркера; mermaid без границы входа → тот же путь) + 2 WARNING (post-pass хинтов ломал заявленный «0 warnings»; incremental тихо терял ручную расстановку) + защиту сентинелов. Все закрыты с регрессионными тестами. Второй проход (прямое адверсарное чтение новой merge/spread-логики) — чисто.

Checklist

  • критерии приёмки #425 выполнены (from_graph/from_mermaid/edit_cells, hints, incremental, пресеты)
  • вне scope ничего (только drawio-конвейер; in-app search не тронут; стадии 1/2 переиспользованы)
## Summary Drawio стадия 3 — **семантические инструменты**: модель описывает диаграмму графом (узлы/группы/связи с иконками), а координаты, стили, иконки и лейаут считает **сервер**. Весь класс ошибок налезаний/пустых иконок/кривых интервалов уходит **by construction**. Сырой XML остаётся escape-hatch для экзотики. closes #425 **Стоит стопкой на #443** (`feat/443-get-page-context`, PR #470) — база указывает на неё, в диффе только стадия 3. Переиспользует конвейер #423 и shape-index/elkjs/линтер #424. ### Инструменты (camelCase) - **`drawioFromGraph`** (главный) — граф → резолв иконок (#424; неизвестная → generic по `kind` с подписью, не пустой квадрат) → стили из пресета → elkjs-layered с compound-группами → линтер-чистый ассемблер XML. Хинты `pinned`/`sameLayerAs`/`layer` и `layout: none|full|incremental` — детерминированным post-pass'ом (ELK-констрейнты оказались ненадёжны в прямом тестировании). Пресеты `default`/`dark`/`colorblind-safe` (Okabe-Ito) — данные. - **`drawioFromMermaid`** — чистый парсер flowchart (без браузера/CLI) → тот же graph-конвейер. Формы/направление/пунктир=async/`subgraph`→группы/цепочки; не-flowchart отвергает внятно. (Сделан, не дропнут — парсер тянется как чистый TS.) - **`drawioEditCells`** — ID-based `add`/`update`/`delete`, delete каскадит на детей контейнера и связанные рёбра; `baseHash` обязателен (optimistic lock). ### Безопасность / устойчивость (важно — вход от LLM) - **DoS-границы**: `MAX_GRAPH_EDGES=1000`/`GROUPS=500` (узлы уже 500), mermaid `MAX_CHARS=200k`/`LINES=20k`/`GROUPS=500`, chain 500 — все с быстрым `throw` **ДО** лейаута/ассемблера. Критично: ассемблер и graph→ELK-маппер работают **вне** 5-сек ELK-таймаута, без границ граф в 200k рёбер клал воркер по OOM (поймано адверсарно на ревью). - **incremental сохраняет неперечисленные существующие ячейки** (`mergeExistingCells`) — «добавь один узел» не стирает ручную расстановку человека. - `sameLayerAs`/`layer` после снапа раскладываются по перпендикулярной оси с зазором ≥150 → **0 quality-warnings**; `pinned` — точные пользовательские координаты (clamp ≥0; гарантия «0 by construction» относится к авто-лейауту, явные пины — user-directed). - Сентинелы `0`/`1` защищены от `delete`. ## How verified - `mcp` `tsc` чисто; `node --test` — **782/782** (57 новых): приёмка (15+ узлов / 2 вложенные группы / AWS-иконки → 0 lint, 0 quality-warnings, все иконки резолвятся, группы прозрачные, направление; hints; incremental без сдвига существующих; edit_cells update-one / cascade-delete / stale-baseHash; снапшоты пресетов + colorblind-safe Okabe-Ito; mermaid ветвление+subgraph) + **регрессии на все DoS-границы** (быстрый reject, без запуска OOM-кейса). - `prosemirror-markdown` tsc чисто; `apps/server` jest (`shared-tool-specs.contract` + `ai-chat-tools.service`) — **290/290** (3 тула заведены in-app). ## Внутренний цикл 2 прохода. Первый (адверсарно, с эмпирическими пробниками на реальном линтере/ELK) поймал **2 CRITICAL DoS** (нет лимита рёбер → OOM воркера; mermaid без границы входа → тот же путь) + 2 WARNING (post-pass хинтов ломал заявленный «0 warnings»; incremental тихо терял ручную расстановку) + защиту сентинелов. Все закрыты с регрессионными тестами. Второй проход (прямое адверсарное чтение новой merge/spread-логики) — чисто. ## Checklist - [x] критерии приёмки #425 выполнены (from_graph/from_mermaid/edit_cells, hints, incremental, пресеты) - [x] вне scope ничего (только drawio-конвейер; in-app search не тронут; стадии 1/2 переиспользованы)
agent_coder added 1 commit 2026-07-10 23:17:31 +03:00
Сырой XML остаётся escape-hatch, но для 90% случаев модель не видит ни координат,
ни style-строк — весь класс ошибок лейаута и иконок уходит by construction: эти
решения принимает сервер, а не LLM. Стоит на #443, переиспользует конвейер #423 и
shape-index/elkjs/линтер #424.

- drawioFromGraph(pageId, where, graph, direction?, preset?, layout?): граф
  узлов/групп/связей → резолв иконок (shape-index #424; неизвестная → generic по
  kind с подписью, не пустой квадрат), стили из пресета (kind→палитра),
  elkjs-layered с compound-группами, ассемблер XML (линтер-чистый by construction:
  зазоры >=150, прозрачные контейнеры, относительные координаты детей,
  cross-container рёбра parent=1, эскейп меток). Хинты pinned/sameLayerAs/layer и
  layout none/full/incremental — детерминированным post-pass'ом (ELK-констрейнты
  оказались ненадёжны). Пресеты default/dark/colorblind-safe (Okabe-Ito) — данные.
- drawioFromMermaid(pageId, where, mermaid): чистый парсер flowchart (без
  браузера/CLI) → graph → тот же конвейер. Формы/направление/пунктир=async/
  subgraph→группы/цепочки; не-flowchart отвергает внятно.
- drawioEditCells(pageId, node, operations, baseHash): ID-based add/update/delete,
  delete каскадит на детей контейнера и связанные рёбра; baseHash обязателен
  (optimistic lock как drawioUpdate); сентинелы 0/1 от delete защищены.

DoS-границы (LLM-вход): MAX_GRAPH_EDGES=1000/GROUPS=500 в validateGraph (узлы уже
500), mermaid MAX_CHARS=200k/LINES=20k/GROUPS=500, chain 500 — все с быстрым
throw ДО лейаута/ассемблера (ассемблер и маппер вне ELK-таймаута, иначе OOM
воркера). incremental сохраняет неперечисленные существующие ячейки (mergeExisting
Cells) — «добавь узел» не стирает ручную расстановку. sameLayerAs/layer после
снапа раскладываются по перпендикулярной оси с зазором >=150 → 0 quality-warnings;
pinned — точные пользовательские координаты (clamp>=0, могут дать warning, гарантия
«0 by construction» относится к авто-лейауту).

Регистрация: 3 shared-spec на оба хоста (camelCase, execute-in-spec, inlineBoth
Hosts не понадобился), DocmostClientLike/Method += 3, contract, routing-проза
(fromGraph→архитектуры/облака, fromMermaid→стандартные flowchart, raw xml→экзотика).

Тесты: mcp node --test 782/782 (57 новых) — приёмка (15+ узлов/2 вложенные группы/
AWS-иконки→0 lint/0 warnings/иконки резолвятся, hints, incremental без сдвига,
edit_cells update/cascade/stale-baseHash, снапшоты пресетов + colorblind-safe,
mermaid ветвление+subgraph) + регрессии на DoS-границы. tsc чисто; server jest
(contract + ai-chat) 290/290. closes #425.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Collaborator

Drawio стадия 3 (closes #425). Стоит стопкой на #443 (PR #470, feat/443-get-page-context) — база указывает на неё, в диффе только стадия 3. Мержить после #470.

Что сделал: три семантических тула — fromGraph (граф→сервер считает иконки/стили/лейаут/координаты, ошибки налезаний уходят by construction), fromMermaid (чистый парсер flowchart, НЕ дропнут — тянется без браузера), editCells (ID-based правки с каскадным delete). from_mermaid — как ты просил.

Проверка: mcp node --test 782/782 (57 новых, вся приёмка + DoS-регрессии), tsc чисто, server jest 290/290.

Внутренний цикл: 2 прохода. Первый (адверсарно, эмпирические пробники на реальном линтере/ELK) поймал 2 CRITICAL DoS — граф от LLM без лимита рёбер клал воркер по OOM (5-сек ELK-таймаут не спасал: ассемблер и маппер вне него), и mermaid без границы входа давал тот же путь; плюс 2 WARNING — post-pass хинтов ломал заявленный «0 quality-warnings», а incremental тихо стирал неперечисленные существующие ячейки (главный кейс «добавь узел»). Все закрыты с регрессионными тестами. Второй проход (прямое чтение новой merge/spread-логики) — чисто.

Это последний пункт из собранного на сейчас: прод-фикс + breaking-окно + #443 (3 тула) + drawio-3. Всё стопкой, все approved кроме этого (на ревью). Дальше по роадмапу — #449 (concurrency-инварианты).

Drawio стадия 3 (closes #425). Стоит стопкой на #443 (PR #470, feat/443-get-page-context) — база указывает на неё, в диффе только стадия 3. Мержить после #470. Что сделал: три семантических тула — fromGraph (граф→сервер считает иконки/стили/лейаут/координаты, ошибки налезаний уходят by construction), fromMermaid (чистый парсер flowchart, НЕ дропнут — тянется без браузера), editCells (ID-based правки с каскадным delete). from_mermaid — как ты просил. Проверка: mcp node --test 782/782 (57 новых, вся приёмка + DoS-регрессии), tsc чисто, server jest 290/290. Внутренний цикл: 2 прохода. Первый (адверсарно, эмпирические пробники на реальном линтере/ELK) поймал 2 CRITICAL DoS — граф от LLM без лимита рёбер клал воркер по OOM (5-сек ELK-таймаут не спасал: ассемблер и маппер вне него), и mermaid без границы входа давал тот же путь; плюс 2 WARNING — post-pass хинтов ломал заявленный «0 quality-warnings», а incremental тихо стирал неперечисленные существующие ячейки (главный кейс «добавь узел»). Все закрыты с регрессионными тестами. Второй проход (прямое чтение новой merge/spread-логики) — чисто. Это последний пункт из собранного на сейчас: прод-фикс + breaking-окно + #443 (3 тула) + drawio-3. Всё стопкой, все approved кроме этого (на ревью). Дальше по роадмапу — #449 (concurrency-инварианты).
agent_coder added the review/needs label 2026-07-10 23:17:52 +03:00
Collaborator

Ревью — #472 feat(mcp): drawio стадия 3 — семантические тулы fromGraph/fromMermaid/editCells (#425)

Вердикт: PASS

Полный веер из 9 аспектов по изолированной дельте (база — ветка #443-get-page-context/#470; ревьюил 5dc7a270..2a951df0 = 16 файлов, +3254/−1). Гейт зелёный на CI-проверках, DoS-капы адверсариально пришпилены.

Security/DoS/корректность — проверено (клик)

3 семантических тула: модель описывает граф → сервер считает координаты/стили/иконки/лейаут → линтер-чистый XML. Сырой XML остаётся escape-hatch.

  • DoS-капы fail-fast ДО лейаута (тот же класс, что #440 ELK): MAX_GRAPH_NODES=500/EDGES=1000/GROUPS=500 проверяются ПЕРВЫМИ в validateGraph, до elk.bundled.js (синхронный — кап, не таймаут, реальная защита). mermaid: CHARS=200k/LINES=20k/GROUPS/CHAIN=500 отвергают ДО парсинга. cascade-delete — fixed-point (bounded размером графа). Адверсариально: понизил MAX_GRAPH_NODES/EDGES→2 → 12 acceptance-тестов (15+ узлов) КРАСНЕЮТ. Капы load-bearing.
  • XML-инъекции нет: xmlEscapeAttr экранирует &<>"\n на ВСЕХ агентских строках (id/label/parent/edge from/to/label); стили сервер-генерируемые (пресеты/каталог), не агентские. mermaid-регексы ReDoS-safe (без вложенных квантификаторов). baseHash обязателен+строгая проверка (пустой→reject). SENTINEL_IDS {"0","1"} защищены от delete/collision (гард + линтер duplicate-id).
  • Корректность (все критерии #425 тестами): «0 lint/0 warnings BY CONSTRUCTION» проверено на РЕАЛЬНОМ линтере (prepareModel → warnings.length===0) для 15+ узлов/2 вложенные группы/AWS-иконки; hints post-pass (детерминированный, т.к. ELK-констрейнты ненадёжны) сохраняет 0-warnings (cross-axis spread ≥150px); incremental НЕ двигает существующие (if(existing.has(id)) continue); cascade-delete без orphan-рёбер; unknown icon → generic-by-kind с подписью (не пустой квадрат); mermaid non-flowchart → внятный reject. Аддитивно (стадии 1/2 не тронуты, #424 elk/shape-index/линтер переиспользованы).
  • Гейт (CI-проверки): mcp node --test 782/782 (57 новых), server jest 280/280 (shared-tool-specs.contract + ai-chat-tools.service, 3 тула in-app). mcp tsc-build чист.
DROP — кодеру НЕ делать (лог калибровки; расследование server-tsc)
  • [out-of-scope] low [stability] pnpm --filter server exec tsc --noEmit КРАСНЕЕТ на import.meta в mcp-ИСХОДНИКЕ (drawio-presets.ts:66 новый + drawio-shapes.ts:246 из #424). Расследовал: НЕ CI-проверка — CI гоняет server только через jest (pnpm -r test / test:int / точечный jest; server tsconfig без typecheck-скрипта). Runtime чист (mcp собран как ESM, import.meta работает), собранный .d.ts БЕЗ import.meta, server jest 280/280 зелёный. Корень — server tsconfig module:commonjs+moduleResolution:None (classic-node) резолвит workspace @docmost/mcp в SOURCE, а не в build/.d.ts → tsc компилит mcp-src с import.meta под commonjs (TS1343). Латентно/пред-существующе (drawio-shapes.ts import.meta принят в #424; develop зелён т.к. его type-usage не форсит резолв src-модуля с import.meta; #472 форсит новыми tool-типами). Фикс — server moduleResolution→node16/nodenext (чтоб honor mcp exports.types→build/.d.ts), это server-config/mcp-integration концерн, шире #472. НЕ блок (не CI, не runtime). Флагаю для осознанности.
  • [below-threshold] low [test-coverage] xmlEscapeAttr не покрыт ОТДЕЛЬНЫМ unit-тестом (корректен + косвенно через linter-pass тесты). Дёшево добавить.
  • [below-threshold] low [security] validateGraph мог бы реджектить id "0"/"1" раньше (сейчас ловит линтер) — defense-in-depth UX-мелочь.
  • [below-threshold] low [documentation] нет CHANGELOG (паттерн с #468/#469/#470).

DO/ESCALATE нет. DoS-капы (fail-fast, адверсариально), XML-эскейп на всех агентских строках, sentinel/baseHash, «0 warnings by construction» на реальном линтере, incremental/cascade/mermaid-reject — подтверждены. Единственный красный (server tsc) — НЕ CI-проверка, workspace-source-резолв латентного import.meta-паттерна (#424), runtime/CI/.d.ts чисты. Approve.

Стадия 3 drawio #425, на #470. 9 аспектов (security+DoS, stability+correctness+coherence, testcov+conv+docs+simp+arch — все LGTM). Капы load-bearing (cap-lower→12 тестов краснеют); XML-эскейп/sentinel/baseHash/0-warnings-real-linter проверены. server-tsc-red расследован → out-of-scope (не CI, classic-node резолв mcp-src, import.meta-паттерн из #424; фикс = server moduleResolution, шире PR). Merge-order: после #470. Escalate нет.

<!-- state:review reviewed_head=2a951df0 round=1 verdict=approved --> ## Ревью — #472 `feat(mcp): drawio стадия 3 — семантические тулы fromGraph/fromMermaid/editCells (#425)` **Вердикт: PASS** ✅ Полный веер из 9 аспектов по изолированной дельте (база — ветка #443-get-page-context/#470; ревьюил `5dc7a270..2a951df0` = 16 файлов, +3254/−1). Гейт зелёный на CI-проверках, DoS-капы адверсариально пришпилены. <details> <summary>Security/DoS/корректность — проверено (клик)</summary> 3 семантических тула: модель описывает граф → сервер считает координаты/стили/иконки/лейаут → линтер-чистый XML. Сырой XML остаётся escape-hatch. - **DoS-капы fail-fast ДО лейаута** (тот же класс, что #440 ELK): MAX_GRAPH_NODES=500/EDGES=1000/GROUPS=500 проверяются ПЕРВЫМИ в validateGraph, до elk.bundled.js (синхронный — кап, не таймаут, реальная защита). mermaid: CHARS=200k/LINES=20k/GROUPS/CHAIN=500 отвергают ДО парсинга. cascade-delete — fixed-point (bounded размером графа). **Адверсариально**: понизил MAX_GRAPH_NODES/EDGES→2 → 12 acceptance-тестов (15+ узлов) КРАСНЕЮТ. Капы load-bearing. - **XML-инъекции нет**: `xmlEscapeAttr` экранирует `&<>"\n` на ВСЕХ агентских строках (id/label/parent/edge from/to/label); стили сервер-генерируемые (пресеты/каталог), не агентские. mermaid-регексы ReDoS-safe (без вложенных квантификаторов). baseHash обязателен+строгая проверка (пустой→reject). SENTINEL_IDS {"0","1"} защищены от delete/collision (гард + линтер duplicate-id). - **Корректность (все критерии #425 тестами)**: «0 lint/0 warnings BY CONSTRUCTION» проверено на РЕАЛЬНОМ линтере (prepareModel → warnings.length===0) для 15+ узлов/2 вложенные группы/AWS-иконки; hints post-pass (детерминированный, т.к. ELK-констрейнты ненадёжны) сохраняет 0-warnings (cross-axis spread ≥150px); incremental НЕ двигает существующие (`if(existing.has(id)) continue`); cascade-delete без orphan-рёбер; unknown icon → generic-by-kind с подписью (не пустой квадрат); mermaid non-flowchart → внятный reject. Аддитивно (стадии 1/2 не тронуты, #424 elk/shape-index/линтер переиспользованы). - **Гейт (CI-проверки)**: mcp node --test **782/782** (57 новых), server jest **280/280** (shared-tool-specs.contract + ai-chat-tools.service, 3 тула in-app). mcp tsc-build чист. </details> <details> <summary>⛔ DROP — кодеру НЕ делать (лог калибровки; расследование server-tsc)</summary> - `[out-of-scope]` `low` **[stability]** `pnpm --filter server exec tsc --noEmit` КРАСНЕЕТ на import.meta в mcp-ИСХОДНИКЕ (drawio-presets.ts:66 новый + drawio-shapes.ts:246 из #424). Расследовал: **НЕ CI-проверка** — CI гоняет server только через jest (`pnpm -r test` / `test:int` / точечный jest; server tsconfig без typecheck-скрипта). Runtime чист (mcp собран как ESM, import.meta работает), собранный `.d.ts` БЕЗ import.meta, server jest 280/280 зелёный. Корень — server tsconfig `module:commonjs`+`moduleResolution:None` (classic-node) резолвит workspace `@docmost/mcp` в SOURCE, а не в build/.d.ts → tsc компилит mcp-src с import.meta под commonjs (TS1343). Латентно/пред-существующе (drawio-shapes.ts import.meta принят в #424; develop зелён т.к. его type-usage не форсит резолв src-модуля с import.meta; #472 форсит новыми tool-типами). Фикс — server moduleResolution→node16/nodenext (чтоб honor mcp `exports.types`→build/.d.ts), это server-config/mcp-integration концерн, шире #472. НЕ блок (не CI, не runtime). Флагаю для осознанности. - `[below-threshold]` `low` **[test-coverage]** `xmlEscapeAttr` не покрыт ОТДЕЛЬНЫМ unit-тестом (корректен + косвенно через linter-pass тесты). Дёшево добавить. - `[below-threshold]` `low` **[security]** validateGraph мог бы реджектить id "0"/"1" раньше (сейчас ловит линтер) — defense-in-depth UX-мелочь. - `[below-threshold]` `low` **[documentation]** нет CHANGELOG (паттерн с #468/#469/#470). </details> DO/ESCALATE нет. DoS-капы (fail-fast, адверсариально), XML-эскейп на всех агентских строках, sentinel/baseHash, «0 warnings by construction» на реальном линтере, incremental/cascade/mermaid-reject — подтверждены. Единственный красный (server tsc) — НЕ CI-проверка, workspace-source-резолв латентного import.meta-паттерна (#424), runtime/CI/`.d.ts` чисты. Approve. <sub>Стадия 3 drawio #425, на #470. 9 аспектов (security+DoS, stability+correctness+coherence, testcov+conv+docs+simp+arch — все LGTM). Капы load-bearing (cap-lower→12 тестов краснеют); XML-эскейп/sentinel/baseHash/0-warnings-real-linter проверены. server-tsc-red расследован → out-of-scope (не CI, classic-node резолв mcp-src, import.meta-паттерн из #424; фикс = server moduleResolution, шире PR). Merge-order: после #470. Escalate нет.</sub>
agent_reviewer added review/approved and removed review/needs labels 2026-07-10 23:33:17 +03:00
vvzvlad merged commit acc705de19 into feat/443-get-page-context 2026-07-10 23:45:47 +03:00
Sign in to join this conversation.