Ворклог по задаче "Добавить локальный Markdown compatibility-компонент"

9 сент. 2026 г., 09:12:29

Legacy HTML normalization: замена <p> на <div> при импорте

При переносе старого контента обнаружился ещё один конфликт между legacy WYSIWYG-разметкой и новым React/SSR-рендерингом. Старый редактор оборачивал большие фрагменты HTML в <p>. В новой версии такой импортированный HTML может оказаться внутри уже существующего параграфа, из-за чего React получает невалидную HTML-структуру и предупреждает:

In HTML, <p> cannot be a descendant of <p>. This will cause a hydration error.

Причина

Проблема не в конкретном тексте, а в семантике тега <p>: HTML не допускает вложенные параграфы. Legacy WYSIWYG использовал <p> как универсальную блочную обёртку, хотя для произвольного вложенного содержимого безопаснее нейтральный контейнер вроде <div>.

При SSR это особенно неприятно: браузер может самостоятельно исправить невалидную HTML-структуру не так, как её ожидает React, и итоговая DOM-структура после парсинга отличается от серверной разметки. Это приводит к hydration mismatch/error.

Принятое решение

В импортер добавлен helper на cheerio, который до сохранения legacy HTML заменяет все <p> на <div>, сохраняя внутренний HTML и атрибуты:

import * as cheerio from 'cheerio'

export function replacePWithDiv(html: string): string {
  const $ = cheerio.load(html, { xml: false }, false)

  $('p').each((_, el) => {
    const $el = $(el)
    const div = $('<div></div>')
    div.html($el.html() || '')

    for (const attr of el.attributes) {
      div.attr(attr.name, attr.value)
    }

    $el.replaceWith(div)
  })

  return $.html()
}

Почему нормализация выполняется в импортере

Это legacy-специфичная проблема исходных данных, поэтому безопаснее исправлять её на входе в новую систему, а не заставлять общий Markdown/React renderer постоянно компенсировать старую WYSIWYG-разметку.

Импортер здесь выполняет роль normalization layer: сохраняет смысл старого содержимого, но убирает структуру, которая заведомо конфликтует с валидным HTML и React hydration.

Значение для общей Markdown-задачи

Этот случай дополняет проблему с whitespace/indentation: новый pipeline должен учитывать не только Markdown-синтаксис, но и качество embedded HTML. Даже если Markdown формально один и тот же, legacy HTML внутри него может иметь структуру, которую разные parser/renderer-цепочки обрабатывают по-разному.

Локальное решение остаётся частью BiznesHelper compatibility-слоя до появления более общего normalization/validation pipeline в haih-agent.

09.09.2026

До переработки Markdown в ядре haih-agent добавить в BiznesHelper локальный renderer, совместимый с фактическим Markdown, который сохраняет MDXEditor.