Skip to content

fix(qqofficial): prevent display-math clipping in lists - #10477

Open
Linyesantan wants to merge 3 commits into
AstrBotDevs:masterfrom
Linyesantan:fix/qq-list-math-clipping
Open

Linyesantan wants to merge 3 commits into
AstrBotDevs:masterfrom
Linyesantan:fix/qq-list-math-clipping

Conversation

@Linyesantan

@Linyesantan Linyesantan commented Oct 9, 2026 •

Copy link
Copy Markdown

QQ's native Markdown renderer can clip the right edge of display equations inside list items even after horizontal scrolling reaches its limit. This change promotes those equations to top-level blocks so the complete formula remains reachable.

Modifications

  • Normalize standalone $$...$$ and \[...\] equations inside lists, preserving TeX content and protecting code examples. Adjust subsequent list content indentation using the original Markdown structure.

  • Preserve streaming deltas and apply the corrected layout as a final update to the same card. A replacement snapshot uses reset=true and restarts index at 1, as required by the QQ API.

  • Add parser and native sender regressions, including every split boundary, multiple streamed updates, an empty final delta, and consecutive responses.

  • Declare markdown-it-py in both runtime dependency files and mdit-py-plugins in the development dependencies for independent math parsing assertions.

  • This is not a breaking change.

Verification steps and test results

  1. Send a native QQ Markdown message containing a long equation inside a list, for example:

    1. Formula:
       $$S = a_1 + a_2 + a_3 + a_4 + a_5 + a_6 + a_7 + a_8 + a_9 + a_{10}$$
  2. For C2C streaming, allow several intermediate updates before completing the response.

  3. Scroll the equation fully to the right. The final term should be visible, and the reply should appear once.

Focused regressions on the upstream checkout:

python -m pytest -q -W error::pytest.PytestUnhandledThreadExceptionWarning \
  tests/test_qqofficial_markdown_math.py \
  tests/test_qqofficial_markdown_math_sender.py
236 passed, 1 warning

ruff check .
All checks passed!

ruff format --check .
967 files already formatted

The warning in the focused suite is the existing Python 3.12 audioop deprecation warning. A broader QQ test run with thread warnings treated as errors encountered an existing aiosqlite worker / closed-event-loop problem, also reproduced on the unmodified upstream commit 28a10c1.

The installed AstrBot 4.28.2 sender was also verified against the real QQ API with three deltas and a final replacement:

accepted_frames: 4
final_state: 10
one_message: true
content_matches: true
formulas_preserved: true
errors: []

The phone user confirmed that the formula ends are fully visible and the reply is not duplicated. The helper in this PR matches the implementation used for that live verification.

Checklist

  • Bug fix; discussion of a new feature is not applicable.
  • Verification steps and test results are provided above.
  • No WebUI navigation, page structure, or labels change, so WebUI documentation updates are not needed.
  • Runtime dependencies are declared in requirements.txt and pyproject.toml; the test dependency is in the development group.
  • No malicious code is introduced.

Summary by Sourcery

Prevent QQ Markdown display equations in lists from being clipped while keeping streaming responses intact and delivering corrected final cards.

Bug Fixes:

  • Prevent QQ native Markdown from clipping standalone display equations inside list items by lifting them into top-level blocks while preserving surrounding list structure and TeX content.

Enhancements:

  • Preserve streamed deltas while replacing the completed card with the corrected layout when necessary.
  • Split oversized QQ C2C streaming frames within the API byte limit and maintain valid stream cursors across replacement snapshots.
  • Add comprehensive parser, streaming-state, sender, and frame-splitting regression coverage.

Build:

  • Add markdown-it-py as a runtime dependency and mdit-py-plugins as a development dependency.

Tests:

  • Add regressions covering list markers, nested lists, blockquotes, code protection, delimiter variants, line endings, every streaming split boundary, repeated responses, empty final deltas, and oversized frames.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/qqofficial/qq_markdown_math.py" line_range="143" />
<code_context>
+        if index in starts:
+            line = line[starts[index].prefix :]
+        elif columns and index not in math_lines:
+            line = _dedent_container(line, columns)
+        if (index in starts or (columns and index in item_starts)) and (
+            output and output[-1].strip()
</code_context>
<issue_to_address>
**List structure is lost**

When a list item contains lifted display math followed by a paragraph, nested list, or sibling item, `_dedent_container` removes the full ancestor indentation from later lines, so content that belongs to the original list is rendered at the wrong level and the reply loses its list structure.

Preserve each later line’s relative list indentation when lifting the display math.
</issue_to_address>

### Comment 2
<location path="astrbot/core/platform/sources/qqofficial/qq_markdown_math.py" line_range="9" />
<code_context>
+
+from markdown_it import MarkdownIt
+
+_LIST_MARKER = re.compile(r"^[ \t]*(?:[-+*]|\d{1,9}[.)])[ \t]+")
+_MATH_START = re.compile(
+    r"^[ \t]*(?:(?:[-+*]|\d{1,9}[.)])[ \t]+)?(?P<delimiter>\$\$|\\\[)"
</code_context>
<issue_to_address>
**Quoted list equations remain clipped**

When a list containing display math is nested inside a blockquote, `_LIST_MARKER.match` and `_MATH_START.match` reject blockquote-prefixed lines, so the list item or math opening is not recognized and `normalize_qq_list_math` leaves the equation unchanged; QQ clips it.

Recognize blockquote prefixes when identifying list markers and display-math openings.

Also at `astrbot/core/platform/sources/qqofficial/qq_markdown_math.py:77-78`.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: astrbot/core/platform/sources/qqofficial/qq_markdown_math.py:143, astrbot/core/platform/sources/qqofficial/qq_markdown_math.py:9


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread astrbot/core/platform/sources/qqofficial/qq_markdown_math.py Outdated
Comment thread astrbot/core/platform/sources/qqofficial/qq_markdown_math.py Outdated
QQ rejects a C2C stream frame above ~4 KB of UTF-8 (流式消息分片过长,请拆分发送). The final list-math relayout resends the whole answer as one reset frame, so long answers were dropped after the first frames. Split stream frames on line boundaries under a 3000-byte budget, keep the cursor in step, and make sure the final frame still ends with a newline after relayout. Verified live: a 9.7 KB answer streamed as 10 accepted frames into one card.
…ay math

Reopen the list items a lifted equation closes so later paragraphs, nested lists and sibling items keep their depth, and recognize blockquote-prefixed list math.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant