fix(prosemirror-markdown): литеральный <br> в тексте экранируется, не становится hardBreak (#554) #560
Reference in New Issue
Block a user
Delete Branch "fix/554-literal-br"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что (#554)
Литеральный
<br>(или<br/>,<br />), НАБРАННЫЙ пользователем как ТЕКСТ абзаца, молча превращался в hardBreak-ноду на round-trip pm→md→pm:case "text"не экранировал литеральные</>, поэтому<br>уходил в markdown как есть, а marked на реимпорте парсил его как inline-HTML-break. Пред-существующий баг (идентичен на develop), но #553 (#549) сделал<br>↔hardBreak транспортом, так что различать пользовательский<br>стало нужнее.Фикс
В
case "text"(в не-code ветке, рядом с существующими escape'ами==/$…$/^[) добавлено энтити-экранирование углов break-тега:marked пропускает
<…>как литеральный текст, импортёр декодит обратно в<br>— ран переживает round-trip как текст с нулём hardBreak'ов.Комплементарность с #553 (транспорт
<br>)Пути раздельны по типу узла: hardBreak-нода эмитит
<br>изcase "hardBreak"/inlineToHtml; литеральный текст экранируется вcase "text"→<br>. На импорте различаются: сырой<br>(транспорт→hardBreak) vs энтити (текст). #553 уже в develop — ветка отребейзена на него, конфликта нет, оба пути работают вместе (полный сьют 833/833).Известное ограничение (осознанное)
Экранируются только «голые» варианты
<br>/<br/>/<br />. Break-тег с атрибутами (<br class="x">) НЕ экранируется и на импорте всё ещё станет hardBreak — редкий литерал, за скоупом. Regex точечный:<abbr>/<b>/<hr>/<break>не задеваются.Как проверял
Round-trip через реальный сериализатор+импортёр:
a<br>b/a<br/>b/a<br />b→ md с<br→ реимпорт 0 hardBreak, текст<br>цел. Реальный hardBreak-узел (a \nb) и stray-углы (a < b > c) не тронуты. Идемпотентно (уже-энтити текст не двоится). prosemirror-markdown сьют 833/833 (после ребейза на develop с #553), tsc 0.внутренний цикл: 1 проход — APPROVE (верная ветка без порчи code-span, точечный regex, нет коллизии с #553, идемпотентно; мутация снятия escape краснит ровно 3 варианта литерального
<br>).closes #554
1c0c6fb9a5to2c4fc565b6Ревью — литеральный
<br>экранируется, не становится hardBreak (#554). Вердикт: approved ✅Чинит ровно ту потерю, что я сам завёл в #554 при ревью #553. Проверил своим round-trip'ом + агент (оба-направления, контейнеры, мутация). Нового бага НЕ вносит, #553 НЕ ломает. Эскалаций нет.
📋 Проверка + adversarial
Фикс: в case "text" энтити-энкодит только угловые скобки break-тега:
textContent.replace(/<(\s*br\s*\/?\s*)>/gi, "<$1>")→ в md уходит<br>, marked пропускает энтити, импортёр декодит обратно в литеральный<br>→ раннится как ТЕКСТ, не hardBreak. Строго scoped на<br…>(не любой</>). Только text-path — эмитнутый hardBreak-<br>(из своего case, #553) не трогается.Мой эмпирический round-trip (собранный модуль):
a<br>b→mda<br>b→back-texta<br>b(0 hb ✅);<br/>/<br />так же.<br>НЕ экранируется).a < b > c— не тронут ✅. Inline-code/code-block<br>— НЕ экранируется (литерал в коде) ✅.Агент (parent-baseline, оба направления): decode симметричен (byte-stable, triple round-trip); over-fire корректен (heading/blockquote/list/link-text/table-cell держат литерал; code — нет; link-title/image-alt через своё экранирование); эмитнутый break цел во ВСЕХ контейнерах; смешанный кейс (литерал
<br>+ реальный hardBreak в одной ячейке) различает верно (1 hb + текст). Мутация: снял.replace→ все 3 литерал-<br>теста краснеют, hardBreak-тест зелёный. Не вакуумно.Гейт: pm-markdown 833/833 (47 файлов, вкл. новый roundtrip.test). Дерево чисто.
Security/stability/regressions/coherence/conventions — LGTM. ⛔ ниже порога (кодеру НЕ делать):
<br class=x>(с атрибутами) всё ещё → hardBreak (regex ловит только bare-форму) — тот же класс, но вне scope bare-<br>#554, пред-существующее;<br\t>(TAB в теге) marked нормализует — контрайвед. Не блок.Готово к мержу. Луп замкнулся: завёл #554 при ревью #553 → кодер #560 → проверил → approve.