SilverElixir commited on
Commit
aa3af88
·
1 Parent(s): c40bb87

Convert newlines to br in rich path so lists stop collapsing

Browse files
lumen_formatting.py CHANGED
@@ -578,6 +578,14 @@ def _md_to_rich_html(text: str) -> str:
578
 
579
  text = text.replace(_BLOCKQUOTE_START, "<blockquote>").replace(_BLOCKQUOTE_END, "</blockquote>")
580
 
 
 
 
 
 
 
 
 
581
  for key, (tag_level, inner) in _heads.items():
582
  text = text.replace(key, f"<h{tag_level}>{_rich_inline(inner)}</h{tag_level}>")
583
  for key, (header, rows) in _tables.items():
 
578
 
579
  text = text.replace(_BLOCKQUOTE_START, "<blockquote>").replace(_BLOCKQUOTE_END, "</blockquote>")
580
 
581
+ # Прод 07.10.2026: в rich-сообщении голый перенос строки НЕ разрыв — Telegram
582
+ # склеивает абзацы, и разнесённый список приходил одной кашей (в обычном
583
+ # HTML-пути \n работает, поэтому баг был виден только в финале). Переносы
584
+ # заменяем на <br/> — ровно так их показывают доки для <blockquote>.
585
+ # Момент: код/ссылки/таблицы/заголовки ещё в плейсхолдерах (без \n внутри),
586
+ # поэтому многострочные <pre> не пострадают.
587
+ text = text.replace("\n", "<br/>")
588
+
589
  for key, (tag_level, inner) in _heads.items():
590
  text = text.replace(key, f"<h{tag_level}>{_rich_inline(inner)}</h{tag_level}>")
591
  for key, (header, rows) in _tables.items():
tests/test_lumen_formatting.py CHANGED
@@ -432,10 +432,10 @@ def test_md_to_rich_html_splits_inline_lists_end_to_end():
432
  bullets = "Вот несколько хороших мелодрамм: " + " • ".join(
433
  ["фильм номер %d с тёплым и подробным описанием сюжета" % i for i in range(4)]
434
  )
435
- assert "\n• " in lumen_formatting._md_to_rich_html(bullets)
436
  numbered = "1. Пункт первый с достаточным пояснением для проверки. 2. Пункт второй с достаточным пояснением для проверки. 3. Пункт третий с достаточным пояснением для проверки."
437
  rich = lumen_formatting._md_to_rich_html(numbered)
438
- assert "\n2. " in rich and "\n3. " in rich
439
 
440
 
441
  def test_md_to_rich_html_scrubs_stray_latex_like_the_plain_path():
@@ -473,7 +473,10 @@ def test_render_paths_parity_on_shared_cases():
473
  "список: " + " • ".join(f"пункт {i} с текстом подлиннее" for i in range(4)),
474
  ]
475
  for case in shared:
476
- assert lumen_formatting._md_to_html(case) == lumen_formatting._md_to_rich_html(case), case
 
 
 
477
 
478
 
479
  def test_render_paths_differ_only_for_headings():
@@ -483,6 +486,21 @@ def test_render_paths_differ_only_for_headings():
483
  assert lumen_formatting._md_to_rich_html("## Title") == "<h3>Title</h3>"
484
 
485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  def test_md_to_rich_html_splits_three_bullets_prod_movies():
487
  # Прод 26.09.2026: ответ про мелодрамы — 3 пункта склеились, rich-путь молчал.
488
  text = (
@@ -491,7 +509,7 @@ def test_md_to_rich_html_splits_three_bullets_prod_movies():
491
  "• Crazy Rich Asians (2018): вясёлая рамантычная камедыя пра кітайскую эліту, поўная колеру і музыкі."
492
  )
493
  assert len(text) >= 200
494
- assert "\n• " in lumen_formatting._md_to_rich_html(text)
495
 
496
 
497
  def test_md_to_html_full_pipeline_converts_bullet_list_with_bold():
 
432
  bullets = "Вот несколько хороших мелодрамм: " + " • ".join(
433
  ["фильм номер %d с тёплым и подробным описанием сюжета" % i for i in range(4)]
434
  )
435
+ assert "<br/>• " in lumen_formatting._md_to_rich_html(bullets)
436
  numbered = "1. Пункт первый с достаточным пояснением для проверки. 2. Пункт второй с достаточным пояснением для проверки. 3. Пункт третий с достаточным пояснением для проверки."
437
  rich = lumen_formatting._md_to_rich_html(numbered)
438
+ assert "<br/>2. " in rich and "<br/>3. " in rich
439
 
440
 
441
  def test_md_to_rich_html_scrubs_stray_latex_like_the_plain_path():
 
473
  "список: " + " • ".join(f"пункт {i} с текстом подлиннее" for i in range(4)),
474
  ]
475
  for case in shared:
476
+ # Перенос строки в rich — это <br/> (прод 07.10.2026: голый \n там склеивался),
477
+ # в обычном HTML — \n. Поэтому сравниваем с приведением к общему виду.
478
+ rich = lumen_formatting._md_to_rich_html(case).replace("<br/>", "\n")
479
+ assert lumen_formatting._md_to_html(case) == rich, case
480
 
481
 
482
  def test_render_paths_differ_only_for_headings():
 
486
  assert lumen_formatting._md_to_rich_html("## Title") == "<h3>Title</h3>"
487
 
488
 
489
+ def test_md_to_rich_html_turns_newlines_into_br():
490
+ # Прод 07.10.2026: в финальном rich-сообщении разнесённый список приходил одной
491
+ # кашей — Telegram склеивает абзацы, голый "\n" разрывом не считается. Разрыв
492
+ # в rich даёт только <br/> (так же в доках показан <blockquote>). Падает на
493
+ # старом коде, где переносы оставались "\n" и список выглядел слипшимся.
494
+ b = chr(96) * 3
495
+ text = "список:\n• раз\n• два\n\n" + b + "python\nx = 1\ny = 2\n" + b
496
+ rich = lumen_formatting._md_to_rich_html(text)
497
+ assert "список:<br/>• раз<br/>• два<br/><br/>" in rich
498
+ # Переносы внутри блока кода обязаны остаться переносами.
499
+ assert '<pre><code class="language-python">x = 1\ny = 2</code></pre>' in rich
500
+ # Вне <pre> голых переносов не остаётся.
501
+ assert "\n" not in rich.replace("x = 1\ny = 2", "")
502
+
503
+
504
  def test_md_to_rich_html_splits_three_bullets_prod_movies():
505
  # Прод 26.09.2026: ответ про мелодрамы — 3 пункта склеились, rich-путь молчал.
506
  text = (
 
509
  "• Crazy Rich Asians (2018): вясёлая рамантычная камедыя пра кітайскую эліту, поўная колеру і музыкі."
510
  )
511
  assert len(text) >= 200
512
+ assert "<br/>• " in lumen_formatting._md_to_rich_html(text)
513
 
514
 
515
  def test_md_to_html_full_pipeline_converts_bullet_list_with_bold():