Рефакторинг рендера [этап 2/6]: футер merged-групп из настоящего Message, выпил конвертера #29

Closed
opened 2026-07-05 20:26:45 +03:00 by agent_vscode · 0 comments
Collaborator

Часть эпика «Рефакторинг пайплайна рендера». Спека: plans/2026-07-05-render-pipeline-refactor.md (v5), раздел «Этап 2». Зависимость: этап 1 (#28). Line-refs действительны для f9550d8.

Цель

Убрать обратный ход данных: сейчас для merged-групп отрендеренный dict конвертируется обратно в мок-Message (processed_message_to_tg_message, rss_generator.py:156–201) ради повторного рендера футера — при том, что оригинальные Message лежат в group. Футеры N сообщений группы рендерятся и выбрасываются, затем футер генерируется заново из мока, теряющего paid/custom-реакции и дату в правильной TZ.

Пункты реестра §3, фиксируемые этим этапом

  • §3.6 (унификация): custom-emoji реакции merged-футера больше не агрегируются в один « N» — отдельный span на каждую, как у одиночных постов.
  • §3.7 (унификация): дата merged-футера — из naive-local даты реального Message вместо UTC-мока; на серверах с TZ≠UTC видимый сдвиг. Golden (TZ=UTC) эту дельту НЕ видит — верифицируется выделенным не-UTC тестом (ниже).
  • §3.8 (детерминизм): порядок флагов merged-поста детерминирован (first-seen order).
  • §3.15 (багфикс): пустой reactions-объект больше не даёт ведущий разделитель в футере; затрагивает и одиночные посты.

Правки (merged-ветка _render_messages_groups, branch:241–286)

# Select the main RAW message with the same criterion the processed dicts used:
# first message that has text or caption, else the first of the group.
main_idx = next((i for i, m in enumerate(group) if (m.text or m.caption)), 0)
main_raw = group[main_idx]
main_message = processed_messages[main_idx]

# Deterministic merged flags: first-seen order, then 'merged'.
merged_flags = list(dict.fromkeys(f for msg in processed_messages for f in msg['flags']))
merged_flags.append("merged")

footer_html = post_parser.generate_html_footer(main_raw, flags_list=merged_flags)

Инвариант эквивалентности: критерий выбора main совпадает с текущим (msg['text'] truthy ⇔ m.text or m.caption; process_message кладёт в text именно message.text or message.caption or ''; порядок processed_messages = порядок group).

Дополнительно:

  • §3.15: в post_parser.py::_reactions_views_links пустая reactions_html НЕ добавляется в first_line_parts (сейчас reactions-объект с пустым списком даёт append("") → ведущий   |  , branch:1081–1104).
  • Удалить processed_message_to_tg_message целиком и импорт SimpleNamespace (если больше не нужен).
  • Обновить golden (§3 пп.6, 8, 15) и СНЯТЬ сорт-нормализацию message-flags в golden-сравнении, введённую этапом 0: порядок флагов теперь детерминирован, нормализация лишь маскировала бы регрессии.

Тесты

  • Футер merged-группы содержит реальные ссылки главного сообщения (Open in Telegram/Web с channel/id из main_raw);
  • несколько custom-emoji → отдельные « N» span'ы; paid → « N»;
  • пустой reactions-объект → нет ведущего разделителя (и для merged, и для одиночного поста);
  • порядок флагов стабилен между прогонами; флаг merged присутствует;
  • выделенный тест §3.7 с не-UTC TZ (например TZ=Europe/Moscow + time.tzset()): дата merged-футера совпадает с датой футера одиночного поста того же сообщения. TZ ОБЯЗАТЕЛЬНО восстанавливается в teardown (fixture/try-finally) — иначе не-UTC протечёт в golden-тесты, которым conftest запинил UTC.

DoD

Функция-конвертер удалена; pytest + golden зелёные (golden-дифф — только пп.6/8/15 с комментарием).

Общие правила эпика

Ветка refactor/render-pipeline от f9550d8; один этап = один коммит; комментарии — English; точечные правки; новые тесты — на naive-датах.

Часть эпика «Рефакторинг пайплайна рендера». Спека: `plans/2026-07-05-render-pipeline-refactor.md` (v5), раздел «Этап 2». Зависимость: этап 1 (#28). Line-refs действительны для f9550d8. ## Цель Убрать обратный ход данных: сейчас для merged-групп отрендеренный dict конвертируется обратно в мок-Message (`processed_message_to_tg_message`, rss_generator.py:156–201) ради повторного рендера футера — при том, что оригинальные `Message` лежат в `group`. Футеры N сообщений группы рендерятся и выбрасываются, затем футер генерируется заново из мока, теряющего paid/custom-реакции и дату в правильной TZ. ## Пункты реестра §3, фиксируемые этим этапом - §3.6 (унификация): custom-emoji реакции merged-футера больше не агрегируются в один «❓ N» — отдельный span на каждую, как у одиночных постов. - §3.7 (унификация): дата merged-футера — из naive-local даты реального Message вместо UTC-мока; на серверах с TZ≠UTC видимый сдвиг. Golden (TZ=UTC) эту дельту НЕ видит — верифицируется выделенным не-UTC тестом (ниже). - §3.8 (детерминизм): порядок флагов merged-поста детерминирован (first-seen order). - §3.15 (багфикс): пустой reactions-объект больше не даёт ведущий разделитель в футере; затрагивает и одиночные посты. ## Правки (merged-ветка `_render_messages_groups`, branch:241–286) ```python # Select the main RAW message with the same criterion the processed dicts used: # first message that has text or caption, else the first of the group. main_idx = next((i for i, m in enumerate(group) if (m.text or m.caption)), 0) main_raw = group[main_idx] main_message = processed_messages[main_idx] # Deterministic merged flags: first-seen order, then 'merged'. merged_flags = list(dict.fromkeys(f for msg in processed_messages for f in msg['flags'])) merged_flags.append("merged") footer_html = post_parser.generate_html_footer(main_raw, flags_list=merged_flags) ``` Инвариант эквивалентности: критерий выбора main совпадает с текущим (`msg['text']` truthy ⇔ `m.text or m.caption`; `process_message` кладёт в `text` именно `message.text or message.caption or ''`; порядок processed_messages = порядок group). Дополнительно: - §3.15: в `post_parser.py::_reactions_views_links` пустая `reactions_html` НЕ добавляется в `first_line_parts` (сейчас reactions-объект с пустым списком даёт `append("")` → ведущий `  |  `, branch:1081–1104). - Удалить `processed_message_to_tg_message` целиком и импорт `SimpleNamespace` (если больше не нужен). - Обновить golden (§3 пп.6, 8, 15) и СНЯТЬ сорт-нормализацию `message-flags` в golden-сравнении, введённую этапом 0: порядок флагов теперь детерминирован, нормализация лишь маскировала бы регрессии. ## Тесты - Футер merged-группы содержит реальные ссылки главного сообщения (Open in Telegram/Web с channel/id из main_raw); - несколько custom-emoji → отдельные «❓ N» span'ы; paid → «⭐ N»; - пустой reactions-объект → нет ведущего разделителя (и для merged, и для одиночного поста); - порядок флагов стабилен между прогонами; флаг `merged` присутствует; - выделенный тест §3.7 с не-UTC TZ (например `TZ=Europe/Moscow` + `time.tzset()`): дата merged-футера совпадает с датой футера одиночного поста того же сообщения. TZ ОБЯЗАТЕЛЬНО восстанавливается в teardown (fixture/try-finally) — иначе не-UTC протечёт в golden-тесты, которым conftest запинил UTC. ## DoD Функция-конвертер удалена; pytest + golden зелёные (golden-дифф — только пп.6/8/15 с комментарием). ## Общие правила эпика Ветка `refactor/render-pipeline` от f9550d8; один этап = один коммит; комментарии — English; точечные правки; новые тесты — на naive-датах.
Sign in to join this conversation.