Skip to content

fix(native-chat): render math and center standalone equations - #31546

Merged
brennanb2025 merged 16 commits into
stablyai:mainfrom
xinzhoulvxinda:codex/native-chat-math-rendering
Oct 26, 2026
Merged

brennanb2025 merged 16 commits into
stablyai:mainfrom
xinzhoulvxinda:codex/native-chat-math-rendering

Conversation

@xinzhoulvxinda

@xinzhoulvxinda xinzhoulvxinda commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Native chat currently shows formulas as TeX source. This contributor change adds desktop rendering while retaining authored messages for copying. The initial implementation rewrote source before Markdown parsing, which could move equations out of quotes and lists, drop text on equation fence lines, and repeatedly parse settled streaming content.

Contribution by @xinzhoulvxinda, with additive maintainer review fixes. This keeps the contributor's opt-in integration and replaces source rewriting with chat-scoped parser grammar shared by incremental splitting and rendering. Inline and display formulas keep their Markdown containers and original positions. Unfinished and invalid formulas remain readable source; complete equations use sanitized, untrusted KaTeX with bounded expansion and size. Display equations scroll horizontally, and selecting or quoting an equation returns its TeX source once. Whole-message copy continues to use authored Markdown.

Tracked in #10910; this delivers the narrower STA-11362 desktop formula integration. GitHub's backtick-wrapped inline form remains unsupported, so this does not complete the broader issue. Phone and editor preview remain unchanged. Ordinary code, links and price forms stay literal; explicit math code fences typeset. Unquoted shell-variable prose such as $HOME/$USER is ambiguous under the common math grammar; use code formatting for literal shell text. Sole display formulas inside quote/list paragraphs center intentionally; equations mixed with prose or inside headings/tables stay inline.

Review and validation

Full review path: 759 changed source lines, plus the English label catalog entry, above the 150-line light-path limit. This is local desktop presentation with no persisted format, process, settings, transport, server capability or mobile-code changes.

The deferred-loading correction is e863afda12972a4685cee2485d830a951bc2ebb1. Its focused runs pass 129 tests across six explicitly named renderer/parser and consumer suites through the test queue. One held-import behavior test verifies that ordinary prose and opt-out never request the typesetter, while parser-recognized math retains source until the module resolves and then rerenders typeset. Earlier review runs cover the unchanged context-menu, cross-message Copy, quote and copy-on-select consumers. Coverage includes container exits, original source positions, invalid/incomplete formulas, authored code-fence closure, code/prices/security, and final/growing agreement.

The growing-renderer contract counts 2,106 versus 23,433 parsed characters at 10 versus 100 appended blocks. Malformed dollar-run contracts retain 66/606 display and 149/1,409 inline consumes at 20/200 markers. New exhausted-search contracts cover both alias families, mixed families, barriers and distinct dollar lengths, including actual rollback and map operations. Repeated parenthesis aliases use 404/3,824 total counted operations on 82/802 characters; each tested population remains within its declared character-based bound. Long unfinished blocks still reparse as they grow; these checks do not claim constant-time total streaming or measured browser latency.

The independent review accepts the parser, container, fence and source-copy corrections. The settled loading disposition is now implemented: the math opt-in and parser recognition precede the first dynamic typesetter import. Plain Markdown remains visible until resolution, with no loading notice. The same reviewer accepts that delta and the carried whole-mechanism reference comparison. Main was merged once at 67333479a4c07d38b24b328a3ba2088ccff7e734; 148 focused integration tests across five named files and the direct merge hook pass. Main’s CJK/autolink and native image/visual/host-link composition are preserved. Both review threads are resolved. CI reported declaration and localization-boundary errors; additive corrections f5040c47e1d4 and ec841203df72 preserve runtime behavior, pass the normal hook, and register the English label. The final test-only correction 38e17a0812543e2e68866326f3b7e5d0780016c6 preserves real i18n exports in the quote-suite mock; all 12 named tests pass. Earlier CI is green: all five Node shards, static/type/localization checks, Linux and Windows packages and selection evidence pass. All four review threads are resolved. The branch-only actual-app screenshot is complete on 38e17a0812543e2e68866326f3b7e5d0780016c6. It shows inline subscripts and a display fraction in the real native-chat transcript. This is a seeded saved transcript with no agents, matching the chat-screen row: renderer tests plus one screenshot. Owned processes stopped gracefully, no recorded process or configured-port listeners survived, and real config hashes stayed unchanged. A newer-main conflict required additive merge 0f33e47c8c3b250dcac6547fad7c74203d68c54a. It preserved main’s pane menus and the reviewed math-selection behavior; 30 targeted tests and the direct merge check pass. The parser, typesetting and native-chat rendering files remain byte-identical to the screenshot head. Test-only commit 980b5b46bba90e23c227ea83278a2a071d0e51da corrects two inherited-main scaling expectations to require cached display reuse; both named tests and the normal hook pass. The screenshot evidence carries across this main merge and test-only commit. Earlier-head CI on d8383f578d6251ec722ed673162ce1dba9fdd216 is green: all five test shards, static/type/localization checks, Linux and Windows packaging, selection evidence and the aggregate gate pass. All 33 checks are successful or skipped, the PR is out of draft, and all review threads are resolved.

The next blocking conflict was resolved by additive main merge 3a686d0f39412846b39203bba9f27f772386a3be (main 4c2a2ac3d93f3c542fb339a03db82d3cf9499f52). It retains main’s unknown-HTML rendering and unresolved-row handling alongside the accepted math and source-copy behavior. Both-parent composition checks, 82 targeted tests and node_modules/.bin/lint-staged --diff=MERGE_HEAD --concurrent false passed before the merge commit. The core math modules remain unchanged; no flow regression was found, so the existing screenshot evidence carries without another QA run.

Main’s repository-wide comment cleanup caused another blocking conflict. Additive merge d8383f578d6251ec722ed673162ce1dba9fdd216 brings in main 7d18e19907adcc2fe039f2eafb4aa40dca0c707c, retaining the cleanup and the accepted math-source serializer. Executable syntax across all 18 prior TypeScript files matches the reviewed head, and all upstream comment edits are retained. The required direct merge check passed before committing with --no-verify. CI on this merged head passed; no tests or QA flows were rerun for the comment-only resolution.

Later on October 25, another blocking main conflict required additive merge 90bc56a8a28ee3b3d7a5504c9c4a691807492cd4 (main 47ed6e1154755985d8671dd2625eed41d0a483da). The only conflict joined two English catalog groups: main’s browser-agent label and the accepted math scroll label are both retained. All 18 previously accepted source and test files remain byte-identical, preserving deferred KaTeX loading and the exhausted-search bound. The required direct merge check passed before the merge commit with --no-verify; both-parent checks found no flow regression. Existing renderer tests and the actual-app screenshot carry across this merge without a QA rerun. Merged-head CI is green: all five Node shards, static analysis and typecheck, Linux and Windows packaging, selection evidence and the aggregate gate pass. All 34 checks are successful or skipped; no retry was needed.

Actual-app screenshot

Windows branch build at 38e17a081254, seeded saved native-chat transcript; no live-agent run. The full window shows inline math and a display fraction together.

pr31546-native-chat-math-clean-full-window.png

Contributor visual evidence

The contributor supplied Linux source-component previews, separate from the actual-app branch screenshot above:

Before After
Before After

AI disclosure

The contributor prepared this with OpenAI Codex (GPT-6), including implementation and independent review assistance. Maintainer review and fixes also use coding agents; the concrete validation is listed above.

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dd4c64a8-8a71-4ffd-8ddf-6a1310658c1a
Commits

Reviewing files that changed from the base of the PR and between 1890c5b and e863afd.

Files selected for processing: 5
  • src/renderer/src/components/sidebar/CommentMarkdown.math-loading.test.tsx
  • src/renderer/src/components/sidebar/CommentMarkdown.math.test.tsx
  • src/renderer/src/components/sidebar/CommentMarkdown.tsx
  • src/renderer/src/components/sidebar/chat-markdown-math-rendering.ts
  • src/renderer/src/components/sidebar/chat-markdown-math-typeset.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Walkthrough

CommentMarkdown now supports optional math parsing and KaTeX rendering. Math remains disabled by default, while NativeChatMarkdown enables it. Streaming logic preserves incomplete formulas as readable source. Selection and copy operations can return formula source. Tests cover rendering, Markdown contexts, delimiter edge cases, sanitization, streaming, selection, and parser work.

Priority: Normal

Merge Risk: Minimal · up to e863a

No actionable merge-blocking issue is established. Complete the planned integration checks before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 20.45% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 44 functions across 17 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the primary changes: native chat math rendering and centered standalone equations.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description is mostly complete. It explains the user-facing change, implementation approach, rationale, issue reference, visual evidence, testing, AI usage, compatibility considerations, performan…
Full details: Description check

Explanation

The description is mostly complete. It explains the user-facing change, implementation approach, rationale, issue reference, visual evidence, testing, AI usage, compatibility considerations, performance limits, and CI results. It does not reproduce every template heading or checkbox, and it uses “Tracked in #10910” instead of a “Fixes #” entry, but the required information is substantially present.

Finishing Touches: 💡 1
⚔️ Resolve merge conflicts: 💡
  • Resolve merge conflict in branch codex/native-chat-math-rendering
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0c3bec85-ab20-493c-9754-cb4d968d7b54
📥 Commits

Reviewing files that changed from the base of the PR and between fd8b2dc and 6352059.

📒 Files selected for processing: 6
  • src/renderer/src/components/native-chat/NativeChatMarkdown.tsx
  • src/renderer/src/components/sidebar/CommentMarkdown.math.test.tsx
  • src/renderer/src/components/sidebar/CommentMarkdown.tsx
  • src/renderer/src/components/sidebar/chat-markdown-math.ts
  • src/renderer/src/components/sidebar/comment-markdown-word-fade.ts
  • src/renderer/src/components/sidebar/markdown-top-level-blocks.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 04b055b0-4693-41e8-9f9c-0ad105756d7a
📥 Commits

Reviewing files that changed from the base of the PR and between 6352059 and 879a173.

📒 Files selected for processing: 7
  • src/renderer/src/components/native-chat/native-chat-quote-selection.ts
  • src/renderer/src/components/sidebar/CommentMarkdown.math.test.tsx
  • src/renderer/src/components/sidebar/CommentMarkdown.tsx
  • src/renderer/src/components/sidebar/chat-markdown-math-rendering.ts
  • src/renderer/src/components/sidebar/chat-markdown-math-selection.ts
  • src/renderer/src/components/sidebar/chat-markdown-math.ts
  • src/renderer/src/components/sidebar/markdown-top-level-blocks.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments: 2
src/renderer/src/components/sidebar/CommentMarkdown.math.test.tsx: 2

450-454: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the debug process.stdout.write calls from the tests.

The block at Lines 450-454 is debug output. It runs only for the \r\r and x\r> fixtures. The calls at Lines 553-555 and 685-687 print measurements that the tests already assert on. Each run therefore adds output to the CI log with no assertion value. If you need the measurements for review, put them in the assertion failure message, for example as the second argument to expect(...).

♻️ Proposed removal for the fixture-specific debug block
-      if (content === source && (source.endsWith('\r\r') || source.includes('x\r> '))) {
-        process.stdout.write(
-          `Authored fence parse ${JSON.stringify({ source, value: chat.value, position: chat.position })}\n`
-        )
-      }

Also applies to: 553-555, 685-687


658-667: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the codeFencedFence spy in a finally block.

vi.spyOn(enter, 'codeFencedFence') patches the shared chatMathFromMarkdown.enter object. observations.mockRestore() at Line 696 runs only if every earlier assertion passes. If an assertion fails, the spy stays installed. The next it.each case then wraps an already wrapped handler, and later tests in the file see the patched fence handler. A failing case can therefore cause unrelated failures. Wrap the body after spyOn in try { ... } finally { observations.mockRestore() }. Alternatively, call vi.restoreAllMocks() in afterEach.

Also applies to: 696-696


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b79714eb-20ac-4b06-a24d-cc0ce1935710
📥 Commits

Reviewing files that changed from the base of the PR and between cd121a3 and 1890c5b.

📒 Files selected for processing: 3
  • src/renderer/src/components/sidebar/CommentMarkdown.math.test.tsx
  • src/renderer/src/components/sidebar/chat-markdown-math-nodes.ts
  • src/renderer/src/components/sidebar/chat-markdown-math.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@brennanb2025

brennanb2025 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Native chat showed formulas as raw TeX. Thank you @xinzhoulvxinda for the desktop formula contribution. Additive review fixes preserve Markdown containers and authored text, keep unfinished/invalid formulas readable, retain source for Copy/quote, and bound repeated failed delimiter searches.

The optional typesetter loads only after opted-in, parser-recognized complete math. Ordinary messages never request it. While loading, the original Markdown remains visible; formulas typeset when it resolves, without a placeholder or notice. Six review rounds and the single authorized loading-delta check are complete, with no open in-scope correctness or reference findings. All four review threads are resolved, including courteous replies to the two requested threads.

Validation: focused parser/renderer/consumer tests cover math containers, authored code-fence closure, invalid/incomplete input, source copying, held-import resolution and operation bounds. The final quote mock correction also passes all 12 named tests. Earlier-head CI is green on d8383f578d6251ec722ed673162ce1dba9fdd216: all five shards, static/type checks, Linux and Windows packaging, selection evidence and aggregate gate pass; all 33 checks are successful or skipped. The planned main merge was 67333479a4c0; a newly detected main conflict required additive merge 0f33e47c8c3b250dcac6547fad7c74203d68c54a. Its narrow integration check is recorded separately. A deterministic inherited-main test expectation was corrected in test-only commit 980b5b46bba90e23c227ea83278a2a071d0e51da: the cache now avoids the second legacy display traversal; both named tests and the normal hook pass. No shipped code changed in that correction. The screenshot below is from 38e17a0 and carries across the subsequent main merges and the test-only correction; no changed flow was rerun.

The next blocking conflict was resolved by additive main merge 3a686d0f39412846b39203bba9f27f772386a3be (main 4c2a2ac3d93f3c542fb339a03db82d3cf9499f52). It retains main’s unknown-HTML rendering and unresolved-row handling alongside the accepted math and source-copy behavior. Both-parent composition checks, 82 targeted tests and node_modules/.bin/lint-staged --diff=MERGE_HEAD --concurrent false passed before the merge commit. The core math modules remain unchanged; no flow regression was found, so the existing screenshot evidence carries without another QA run.

Main’s repository-wide comment cleanup caused another blocking conflict. Additive merge d8383f578d6251ec722ed673162ce1dba9fdd216 brings in main 7d18e19907adcc2fe039f2eafb4aa40dca0c707c, retaining the cleanup and the accepted math-source serializer. Executable syntax across all 18 prior TypeScript files matches the reviewed head, and all upstream comment edits are retained. The required direct merge check passed before committing with --no-verify. CI on this merged head passed; no tests or QA flows were rerun for the comment-only resolution.

Later on October 25, another blocking main conflict required additive merge 90bc56a8a28ee3b3d7a5504c9c4a691807492cd4 (main 47ed6e1154755985d8671dd2625eed41d0a483da). The only conflict joined two English catalog groups: main’s browser-agent label and the accepted math scroll label are both retained. All 18 previously accepted source and test files remain byte-identical, preserving deferred KaTeX loading and the exhausted-search bound. The required direct merge check passed before the merge commit with --no-verify; both-parent checks found no flow regression. Existing renderer tests and the actual-app screenshot carry across this merge without a QA rerun. Merged-head CI is green: all five Node shards, static analysis and typecheck, Linux and Windows packaging, selection evidence and the aggregate gate pass. All 34 checks are successful or skipped; no retry was needed.

Scope remains desktop native chat. Phone and editor preview are unchanged. GitHub backtick-wrapped inline math remains unsupported, tracked in #10910. Long unfinished blocks still reparse while growing; operation tests certify the stated bounds, not constant-time total streaming or browser latency.

For Brennan's review

Actual branch app on Windows: seeded saved native-chat transcript, no live agents. Both inline subscripts and the display fraction render with readable surrounding prose. This chat-screen flow uses renderer tests plus one screenshot (qa=tests). The app stopped gracefully; all 18 recorded processes and configured-port listeners were gone, and monitored real config hashes were unchanged.

pr31546-native-chat-math-clean-full-window.png

Two code spots for judgment: optional loading trigger and held-import behavior test.

@YangNuoCheng

Copy link
Copy Markdown

I checked this branch (3a686d0) against Chinese chat text, which is most of what my agents write, by running remarkChatMath with the chat's remark-gfm and remark-cjk-friendly plugins. All 12 cases parsed as expected:

Input Math found
信道容量 $C = B\log_2(1+\mathrm{SNR})$ 随带宽增长。 C = B\log_2(1+\mathrm{SNR})
信道容量$C$随带宽增长。 (no spaces around $) C
其中$\lambda_i \ge 0$,且$\sum_i \lambda_i = 1$。 (full-width punctuation) both formulas
令$x_t$表示第$t$轮的状态。 x_t, t
($k$为常数) k
成本为$5,收益为$10。 none
价格 $5 和 $10 都可以。 none
设 $\alpha=0.5$;当$N=10$时成立 both formulas
速率\(R_i\)和功率\[P_i\]。 R_i, P_i
**结论**:$\mathbf{w}^\star$ 收敛。 \mathbf{w}^\star
list items - 第一项 $a$ / - 第二项 $b$ a, b
> 引用:$E=mc^2$ E=mc^2

The CJK price heuristic keeps $5,…$10 literal, and formulas directly adjacent to Han characters still typeset. I had prototyped the same feature locally and am not opening a separate PR, since this one covers it. Thanks for working on it; looking forward to it landing.

@brennanb2025
brennanb2025 merged commit 5381113 into stablyai:main Oct 26, 2026
34 checks passed
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.

3 participants