[refactor][converter] Серверный экспорт/импорт страниц на @docmost/prosemirror-markdown (шаги 1–2 ликвидации editor-ext md-слоя) #345

Open
opened 2026-07-04 19:51:48 +03:00 by agent_vscode · 0 comments
Collaborator

Контекст

После #293/#333 канонический PM↔MD конвертер существует в одном экземпляре (@docmost/prosemirror-markdown, под serializer-contract тестом), его используют mcp/ai-chat и git-sync. Но в packages/editor-ext/src/lib/markdown/ живёт вторая, независимая реализация на другом стеке (turndown HTML→MD + marked MD→HTML), и её потребители — пользовательские пути:

  • серверный экспорт/импорт страниц (apps/server/src/integrations/export|importhtmlToMarkdown/markdownToHtml из @docmost/editor-ext) — этот issue;
  • клиентская вставка markdown (apps/client/.../markdown-clipboard.ts) — вынесена в #347 (browser-entry пакета + удаление слоя).

Чем этот слой отличается от канона (проверено по коду правил)

Нода editor-ext md-слой Канон
callout пишет легаси :::type (turndown.utils.ts:302); читает оба формата > [!type]
сноски [^id] ссылочный стиль ^[текст] inline
картинка ![alt](src)width/height/align/size/attachmentId ТЕРЯЮТСЯ; caption через <div><img data-caption> ![alt](src) + <!--img {…}-->, lossless
pageBreak/subpages/transclusion сырые HTML-div'ы standalone-комментарии
math $…$ (совпадает) $…$

Следствия: (а) md из «Export page» ≠ md в vault git-sync ≠ md, который видит агент — два диалекта в одном продукте; (б) экспорт теряет layout картинок; (в) слой рукописный, контракт-тестом не покрыт, чинится руками — последний живой генератор дрейфа.

План — два шага, два отдельных PR, по порядку

  • Шаг 1 — серверный экспорт на пакет (первый: максимум ценности, минимум работы).
    В apps/server/src/integrations/export заменить цепочку content → HTML → htmlToMarkdown(turndown) на прямой convertProseMirrorToMarkdown(content) из пакета (PM JSON → MD, без HTML-промежутка). Экспортные goldens обновить осознанно, fixtures-first: канонические формы сносок/картинок/комментариев, layout картинок выживает. Пакет headless и уже копируется в Docker-образ (#333) — инфра-работы нет.
    Acceptance: экспортированный md байт-в-байт равен тому, что записал бы git-sync для той же страницы (общая фикстура «export == vault»).

  • Шаг 2 — серверный импорт на пакет + нормализатор чужого markdown.
    Заменить markdownToHtml(marked) на markdownToProseMirror пакета. ВАЖНО: канон-парсер намеренно строгий ([^id] и ::: не парсит — no-backward-compat), а импорт глотает чужие файлы (GitHub/Notion/старые выгрузки). Поэтому на границе импорта — тонкий текстовый pre-pass нормализации: :::type> [!type], [^id]+[^id]: def^[…] (+ по мелочи по фикстурам). Нормализатор живёт ТОЛЬКО в import-boundary сервера, НЕ в пакете — это не форк конвертера, это input-liberal/output-canonical адаптер в одном месте.
    Acceptance: корпус чужих md-файлов (gfm-сноски, :::‑callouts, таблицы) импортируется без литерального мусора; повторный экспорт даёт канон.

  • Финал этого issue: в apps/server не остаётся импортов md-слоя editor-ext (htmlToMarkdown/markdownToHtml) — grep чистый. Само удаление packages/editor-ext/src/lib/markdown/ — в #347, после перевода последнего потребителя (клиентская вставка).

Guardrails (уроки #293/#333)

  1. Никакой конвертер-логики заново в editor-ext — только вызовы пакета; нормализатор импорта — текстовый pre-pass на границе, не парсер.
  2. Fixtures-first: новые goldens экспорта/импорта пишутся до замены кода.
  3. Проверка в CI-условиях (чистые gitignored-артефакты, шаги CI), отчёты с приложенным выводом прогонов.

Связанные

  • #347 — шаг 3 (клиентская вставка, browser-entry пакета) + финальное удаление md-слоя editor-ext. Блокируется этим issue.
  • #293 (закрыт, #333) — консолидация mcp/git-sync; этот issue — её продолжение.
  • #326 — общий план; работа независима от шагов 6a/6b (замороженного вливания #119) и может идти параллельно.
  • Канон: #293 (comment)
## Контекст После #293/#333 канонический PM↔MD конвертер существует в одном экземпляре (`@docmost/prosemirror-markdown`, под serializer-contract тестом), его используют mcp/ai-chat и git-sync. Но в `packages/editor-ext/src/lib/markdown/` живёт **вторая, независимая реализация** на другом стеке (turndown HTML→MD + marked MD→HTML), и её потребители — пользовательские пути: - **серверный экспорт/импорт страниц** (`apps/server/src/integrations/export|import` → `htmlToMarkdown`/`markdownToHtml` из `@docmost/editor-ext`) — **этот issue**; - **клиентская вставка markdown** (`apps/client/.../markdown-clipboard.ts`) — вынесена в **#347** (browser-entry пакета + удаление слоя). ### Чем этот слой отличается от канона (проверено по коду правил) | Нода | editor-ext md-слой | Канон | |---|---|---| | callout | пишет легаси `:::type` (turndown.utils.ts:302); читает оба формата | `> [!type]` | | сноски | `[^id]` ссылочный стиль | `^[текст]` inline | | картинка | `![alt](src)` — **width/height/align/size/attachmentId ТЕРЯЮТСЯ**; caption через `<div><img data-caption>` | `![alt](src)` + `<!--img {…}-->`, lossless | | pageBreak/subpages/transclusion | сырые HTML-div'ы | standalone-комментарии | | math | `$…$` (совпадает) | `$…$` | Следствия: (а) md из «Export page» ≠ md в vault git-sync ≠ md, который видит агент — два диалекта в одном продукте; (б) экспорт теряет layout картинок; (в) слой рукописный, контракт-тестом не покрыт, чинится руками — последний живой генератор дрейфа. ## План — два шага, два отдельных PR, по порядку - [ ] **Шаг 1 — серверный экспорт на пакет (первый: максимум ценности, минимум работы).** В `apps/server/src/integrations/export` заменить цепочку `content → HTML → htmlToMarkdown(turndown)` на прямой `convertProseMirrorToMarkdown(content)` из пакета (PM JSON → MD, без HTML-промежутка). Экспортные goldens обновить осознанно, fixtures-first: канонические формы сносок/картинок/комментариев, layout картинок выживает. Пакет headless и уже копируется в Docker-образ (#333) — инфра-работы нет. **Acceptance**: экспортированный md байт-в-байт равен тому, что записал бы git-sync для той же страницы (общая фикстура «export == vault»). - [ ] **Шаг 2 — серверный импорт на пакет + нормализатор чужого markdown.** Заменить `markdownToHtml(marked)` на `markdownToProseMirror` пакета. ВАЖНО: канон-парсер намеренно строгий (`[^id]` и `:::` не парсит — no-backward-compat), а импорт глотает **чужие** файлы (GitHub/Notion/старые выгрузки). Поэтому на границе импорта — тонкий текстовый pre-pass нормализации: `:::type`→`> [!type]`, `[^id]`+`[^id]: def`→`^[…]` (+ по мелочи по фикстурам). Нормализатор живёт ТОЛЬКО в import-boundary сервера, НЕ в пакете — это не форк конвертера, это input-liberal/output-canonical адаптер в одном месте. **Acceptance**: корпус чужих md-файлов (gfm-сноски, :::‑callouts, таблицы) импортируется без литерального мусора; повторный экспорт даёт канон. - [ ] **Финал этого issue**: в `apps/server` не остаётся импортов md-слоя editor-ext (`htmlToMarkdown`/`markdownToHtml`) — grep чистый. Само удаление `packages/editor-ext/src/lib/markdown/` — в #347, после перевода последнего потребителя (клиентская вставка). ## Guardrails (уроки #293/#333) 1. Никакой конвертер-логики заново в editor-ext — только вызовы пакета; нормализатор импорта — текстовый pre-pass на границе, не парсер. 2. Fixtures-first: новые goldens экспорта/импорта пишутся до замены кода. 3. Проверка в CI-условиях (чистые gitignored-артефакты, шаги CI), отчёты с приложенным выводом прогонов. ## Связанные - **#347** — шаг 3 (клиентская вставка, browser-entry пакета) + финальное удаление md-слоя editor-ext. Блокируется этим issue. - #293 (закрыт, #333) — консолидация mcp/git-sync; этот issue — её продолжение. - #326 — общий план; работа независима от шагов 6a/6b (замороженного вливания #119) и может идти параллельно. - Канон: https://gitea.vvzvlad.xyz/vvzvlad/gitmost/issues/293#issuecomment-5076
agent_vscode changed title from [refactor][converter] Перевести editor-ext markdown-слой на @docmost/prosemirror-markdown: экспорт → импорт → вставка (последний диалектный шов) to [refactor][converter] Серверный экспорт/импорт страниц на @docmost/prosemirror-markdown (шаги 1–2 ликвидации editor-ext md-слоя) 2026-07-04 19:56:49 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#345