[bug][mcp] hardBreak непредставим в markdown-каноне: сериализатор молча пропускает узел, импортер не создаёт его из br — поддержать в обе стороны #503

Open
opened 2026-07-11 05:09:59 +03:00 by agent_vscode · 0 comments
Collaborator

Суть

Из агентского фидбека: hardBreak, отправленный через updatePageJson, «тихо вырезался — без ошибки, перенос не появился». Агент был вынужден раскладывать строки по отдельным параграфам.

Диагноз (полный, по коду)

Трассировка пути update_page_json:

  1. Схема узел ЗНАЕТ: docmostExtensions (docmost-schema.ts) включает StarterKit v3 без отключения HardBreak → PMNode.fromJSON(docmostSchema, …) в collaboration.ts#L181 принимает его; неизвестные типы там бросают labelled-ошибку (unstorableYjsError), т.е. «тихость» — не отсюда.
  2. Markdown-слой узла НЕ ЗНАЕТ в обе стороны (проверено grep'ом):
    • сериализатор pm→md (markdown-converter.ts) — ни одного упоминания hardBreak → при выводе в markdown узел молча пропускается (PM-сериализаторы не ошибаются на необработанных кейсах);
    • импортер md→pm (markdown-to-prosemirror.ts) — нет обработки br-токена marked → из markdown hardBreak не создать вообще.

Итог: узел легален в редакторе (Shift+Enter) и схеме, но непредставим в markdown-каноне MCP. Потеря у агента произошла либо при markdown-канонизации записи, либо как «записан, но невидим при markdown-чтении» — фикс обязан закрыть обе стороны, тогда точная сторона не важна (первый шаг реализации — 5-минутный репро, чтобы знать, что пишем в CHANGELOG).

Решение

  1. Сериализатор: эмитить hardBreak как CommonMark hard break — обратный слэш + перевод строки (\\\n) внутри параграфа/listItem. Snapshot-тесты вывода.
  2. Импортер: br-токен marked (двойной пробел+\n, \\\n, <br>) → узел hardBreak.
  3. Round-trip тест (ключевой): документ с hardBreak → md → pm → идентичен; update_page_json c hardBreak → getPage показывает \\-перенос → get_page_json возвращает узел. Это закрывает и «no second canon»-инвариант: узел больше не теряется на канонизации.
  4. Вторичное укрепление: убедиться, что labelled-ошибка unstorableYjsError для реально неизвестных нод доходит до агента как понятное сообщение со списком допустимых типов (опыт «тихо» из фидбека намекает, что мог не дойти) — тест на {type:"blink"}.

Крайние случаи

  • Существующие доки с hardBreak (создан людьми через Shift+Enter): после фикса их markdown-экспорт изменится (появится \\) — это исправление потери данных, не регрессия; git-sync/export дифф разовый, отметить в CHANGELOG.
  • \\ в конце строки в СУЩЕСТВУЮЩЕМ markdown начнёт парситься как hardBreak — это CommonMark-семантика, корректно; text-режим чтения (#502) hardBreak отдаёт как \n.
  • jsonToText / tsvector: hardBreak в текст-экстракции — пробел или \n, проверить, что поиск не склеивает слова.

Тесты / приёмка

  • Репро из фидбека как тест: hardBreak через updatePageJson → чтение markdown показывает перенос, JSON — узел, UI — перенос.
  • Round-trip no-op на документе со всеми поддерживаемыми нодами (расширить существующий генератор round-trip тестов из #351, если применимо).
  • {type:"blink"} → понятная ошибка, не тишина.

Связанное

Фидбек-пакет: #501 (токен), #502 (литеральные режимы). Пересборка packages/mcp/build/* и packages/prosemirror-markdown/build/* — оба пакета затронуты.

# Суть Из агентского фидбека: `hardBreak`, отправленный через `updatePageJson`, «тихо вырезался — без ошибки, перенос не появился». Агент был вынужден раскладывать строки по отдельным параграфам. # Диагноз (полный, по коду) Трассировка пути `update_page_json`: 1. **Схема узел ЗНАЕТ**: `docmostExtensions` ([docmost-schema.ts](packages/prosemirror-markdown/src/lib/docmost-schema.ts)) включает StarterKit v3 без отключения HardBreak → `PMNode.fromJSON(docmostSchema, …)` в [collaboration.ts#L181](packages/mcp/src/lib/collaboration.ts#L181) принимает его; неизвестные типы там бросают labelled-ошибку (`unstorableYjsError`), т.е. «тихость» — не отсюда. 2. **Markdown-слой узла НЕ ЗНАЕТ в обе стороны** (проверено grep'ом): - сериализатор pm→md ([markdown-converter.ts](packages/mcp/src/lib/markdown-converter.ts)) — ни одного упоминания hardBreak → при выводе в markdown узел **молча пропускается** (PM-сериализаторы не ошибаются на необработанных кейсах); - импортер md→pm ([markdown-to-prosemirror.ts](packages/prosemirror-markdown/src/lib/markdown-to-prosemirror.ts)) — нет обработки `br`-токена marked → из markdown hardBreak не создать вообще. Итог: узел легален в редакторе (Shift+Enter) и схеме, но **непредставим в markdown-каноне MCP**. Потеря у агента произошла либо при markdown-канонизации записи, либо как «записан, но невидим при markdown-чтении» — фикс обязан закрыть обе стороны, тогда точная сторона не важна (первый шаг реализации — 5-минутный репро, чтобы знать, что пишем в CHANGELOG). # Решение 1. **Сериализатор**: эмитить hardBreak как CommonMark hard break — обратный слэш + перевод строки (`\\\n`) внутри параграфа/listItem. Snapshot-тесты вывода. 2. **Импортер**: `br`-токен marked (`двойной пробел+\n`, `\\\n`, `<br>`) → узел `hardBreak`. 3. **Round-trip тест** (ключевой): документ с hardBreak → md → pm → идентичен; `update_page_json` c hardBreak → `getPage` показывает `\\`-перенос → `get_page_json` возвращает узел. Это закрывает и «no second canon»-инвариант: узел больше не теряется на канонизации. 4. **Вторичное укрепление**: убедиться, что labelled-ошибка `unstorableYjsError` для реально неизвестных нод доходит до агента как понятное сообщение со списком допустимых типов (опыт «тихо» из фидбека намекает, что мог не дойти) — тест на `{type:"blink"}`. # Крайние случаи - **Существующие доки с hardBreak** (создан людьми через Shift+Enter): после фикса их markdown-экспорт изменится (появится `\\`) — это исправление потери данных, не регрессия; git-sync/export дифф разовый, отметить в CHANGELOG. - **`\\` в конце строки в СУЩЕСТВУЮЩЕМ markdown** начнёт парситься как hardBreak — это CommonMark-семантика, корректно; text-режим чтения (#502) hardBreak отдаёт как `\n`. - **jsonToText / tsvector**: hardBreak в текст-экстракции — пробел или `\n`, проверить, что поиск не склеивает слова. # Тесты / приёмка - Репро из фидбека как тест: hardBreak через updatePageJson → чтение markdown показывает перенос, JSON — узел, UI — перенос. - Round-trip no-op на документе со всеми поддерживаемыми нодами (расширить существующий генератор round-trip тестов из #351, если применимо). - `{type:"blink"}` → понятная ошибка, не тишина. # Связанное Фидбек-пакет: #501 (токен), #502 (литеральные режимы). Пересборка `packages/mcp/build/*` и `packages/prosemirror-markdown/build/*` — оба пакета затронуты.
agent_vscode changed title from [bug][mcp] updatePageJson молча выкидывает hardBreak: тихая нормализация вместо ошибки — агент думает, что записал, а там не то to [bug][mcp] hardBreak непредставим в markdown-каноне: сериализатор молча пропускает узел, импортер не создаёт его из br — поддержать в обе стороны 2026-07-11 05:21:55 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#503