Skip to content

Add markdown and LaTeX math rendering for chat messages#6

Open
tpaulshippy wants to merge 4 commits intomainfrom
feature/markdown-support
Open

Add markdown and LaTeX math rendering for chat messages#6
tpaulshippy wants to merge 4 commits intomainfrom
feature/markdown-support

Conversation

@tpaulshippy
Copy link
Copy Markdown
Owner

@tpaulshippy tpaulshippy commented Apr 12, 2026

Summary

  • Add markdown support for assistant chat messages using WebView with marked library
  • Add LaTeX math equation rendering (e.g., \(a=54.5\)) using KaTeX
  • Create new MarkdownRenderer component that renders styled HTML in a WebView
  • Update ChatMessage.tsx to use MarkdownRenderer for assistant messages while keeping plain text for user messages

Testing

Test with messages containing:

  • Markdown: bold, italic, code, lists, links, blockquotes
  • Math: \(2(3*6)+(82-(a+5))=a+4\) renders as \(a=54.5\)

Summary by CodeRabbit

  • New Features
    • Assistant messages now support full Markdown rendering for rich formatting
    • LaTeX mathematical expressions render correctly within messages
    • Code blocks, tables, blockquotes and other Markdown elements display with proper styling
    • Message appearance adapts to your app’s light/dark theme for consistent readability

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 642e2650-f368-46cc-8d1f-768d0b8ab1b8

📥 Commits

Reviewing files that changed from the base of the PR and between 730f558 and 733c80b.

📒 Files selected for processing (2)
  • front/components/MarkdownRenderer.tsx
  • front/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • front/package.json
  • front/components/MarkdownRenderer.tsx

📝 Walkthrough

Walkthrough

Assistant messages now render Markdown and LaTeX via a new MarkdownRenderer component (WebView-based); ChatMessage uses it for non-user roles. marked and katex were added to dependencies and Jest transform settings updated.

Changes

Cohort / File(s) Summary
Message Rendering
front/components/ChatMessage.tsx
Adjusted message rendering: user messages use plain ThemedText; assistant/non-user messages render via the new MarkdownRenderer and are wrapped in ThemedView with styles.assistantMessage(assistantColor).
Markdown & LaTeX Renderer
front/components/MarkdownRenderer.tsx
Added MarkdownRenderer component: converts markdown with marked, renders KaTeX for LaTeX segments, generates theme-aware HTML/CSS, memoizes output, and displays via a WebView (scrolling disabled, transparent background).
Dependencies & Test Config
front/package.json
Added runtime deps marked and katex; updated Jest transformIgnorePatterns to allow transforming marked and katex under node_modules.

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatMessage
    participant MarkdownRenderer
    participant marked
    participant katex
    participant WebView

    User->>ChatMessage: deliver message object
    alt message.role == "user"
        ChatMessage->>ChatMessage: render `ThemedText` (plain)
    else
        ChatMessage->>MarkdownRenderer: pass `message.text`
        MarkdownRenderer->>marked: parse markdown -> HTML
        marked-->>MarkdownRenderer: parsed HTML
        MarkdownRenderer->>katex: render LaTeX segments
        katex-->>MarkdownRenderer: rendered HTML snippets
        MarkdownRenderer->>MarkdownRenderer: assemble theme-aware HTML/CSS
        MarkdownRenderer->>WebView: load generated HTML
        WebView-->>User: display formatted content
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through text and code with glee,
I turn plain words to markdown spree,
KaTeX sparks equations bright,
WebView cradles themed delight,
A rabbit's render — tidy and free.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and directly summarizes the main change: adding markdown and LaTeX math rendering for chat messages, which aligns with all three files modified (ChatMessage integration, MarkdownRenderer component creation, and dependency additions).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/markdown-support

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/components/ChatMessage.tsx`:
- Line 9: The import for MarkdownRenderer in ChatMessage.tsx uses a default
import but the component is exported as a named export; change the import
statement to a named import (import { MarkdownRenderer } from
"@/components/MarkdownRenderer") so it matches the component's named export and
coding guidelines, updating any references in this file that assume the default
import to use the named symbol MarkdownRenderer.

In `@front/components/MarkdownRenderer.tsx`:
- Line 136: The file currently uses a default export for the React component;
change it to a named export by exporting the MarkdownRenderer symbol as a named
export (e.g., export const MarkdownRenderer = ...) or by replacing the final
default export with a named export (export { MarkdownRenderer }); then update
all import sites to use the named import (import { MarkdownRenderer } from
'...') and ensure the component file stays a .tsx TypeScript component.
- Line 39: Update the KaTeX CDN stylesheet URL in MarkdownRenderer.tsx so its
version matches the installed runtime (package.json's ^0.16.45); locate the
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"> in the
MarkdownRenderer component and change the version segment to 0.16.45 (or the
exact runtime version) so the CSS and KaTeX runtime are identical.
- Around line 33-34: The parsed HTML from marked.parse (htmlContent) is injected
into the WebView (component using originWhitelist) without sanitization creating
XSS risk; fix by sanitizing the parsed HTML output with a library like
sanitize-html or isomorphic-dompurify before assigning to htmlContent and use
that sanitized string for the WebView source, and harden the WebView props by
tightening originWhitelist (avoid ['*']), disabling/excluding JavaScript
execution (e.g., javaScriptEnabled=false), disabling DOM storage, and removing
permissive settings so only trusted origins are allowed; update references to
marked.parse, htmlContent, and the WebView component props accordingly.
- Around line 125-132: Extract the inline style object from the WebView into a
StyleSheet by creating e.g. const styles = StyleSheet.create({ webview: {
backgroundColor: 'transparent', minHeight: 50 } }) and apply it to the WebView
via style={styles.webview}; additionally add startInLoadingState={true} and a
renderError prop that returns a simple fallback component (e.g. a View/Text) to
the WebView element so the MarkdownRenderer component handles loading and error
states properly.
- Around line 18-24: Update the inline LaTeX regex in MarkdownRenderer.tsx that
currently uses the capturing group ([^)]+) which fails on expressions with
nested parentheses; change that group to a non-greedy [\s\S]+? (matching any
char including newlines) so the .replace call that renders inline math via
katex.renderToString(latex, { throwOnError: false, displayMode: false }) will
capture full expressions like \(2(3*6)+(82-(a+5))=a+4\); keep the existing
try/catch and do not alter the separate display-mode pattern.

In `@front/package.json`:
- Around line 49-50: Jest will fail to parse the new ESM-only dependency
"marked" (and potentially the ESM build of "katex"), so update your Jest config
(jest.config.js or the "jest" field) to ensure node_modules/marked and
node_modules/katex are transformed (i.e., modify transformIgnorePatterns to
exclude these packages from being ignored, for example by using a negative
lookahead like /(?!marked|katex)/ in the pattern); target the
transformIgnorePatterns setting and add "marked" and "katex" so Babel/jest can
transpile them during tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5f5ce6b-eba6-4f28-aa8f-a1a841653c63

📥 Commits

Reviewing files that changed from the base of the PR and between ffd09f3 and 730f558.

⛔ Files ignored due to path filters (1)
  • front/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • front/components/ChatMessage.tsx
  • front/components/MarkdownRenderer.tsx
  • front/package.json

import { ActivityIndicator } from "react-native";
import { useThemeColor } from "@/hooks/useThemeColor";
import { IconSymbol } from "@/components/ui/IconSymbol";
import MarkdownRenderer from "@/components/MarkdownRenderer";
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.

🛠️ Refactor suggestion | 🟠 Major

Use named import for MarkdownRenderer.

Line 9 should switch to a named import to match the component export convention.

Suggested change
-import MarkdownRenderer from "@/components/MarkdownRenderer";
+import { MarkdownRenderer } from "@/components/MarkdownRenderer";

As per coding guidelines, "Place React Native components in the components directory, use TypeScript for all component files, and export components as named exports".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import MarkdownRenderer from "@/components/MarkdownRenderer";
import { MarkdownRenderer } from "@/components/MarkdownRenderer";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/components/ChatMessage.tsx` at line 9, The import for MarkdownRenderer
in ChatMessage.tsx uses a default import but the component is exported as a
named export; change the import statement to a named import (import {
MarkdownRenderer } from "@/components/MarkdownRenderer") so it matches the
component's named export and coding guidelines, updating any references in this
file that assume the default import to use the named symbol MarkdownRenderer.

Comment on lines +33 to +34
const htmlContent = marked.parse(processedContent, { breaks: true, gfm: true });

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "MarkdownRenderer.tsx" -type f

Repository: tpaulshippy/bots

Length of output: 99


🏁 Script executed:

cat -n front/components/MarkdownRenderer.tsx

Repository: tpaulshippy/bots

Length of output: 4363


🏁 Script executed:

head -150 front/components/MarkdownRenderer.tsx | tail -120

Repository: tpaulshippy/bots

Length of output: 2888


Unsanitized HTML from marked output injected into WebView (XSS risk).

The markdown content is parsed by marked.parse() (line 33) which can render arbitrary HTML, then injected directly into the WebView (lines 125-127) with permissive originWhitelist={['*']}. An attacker can inject JavaScript through markdown syntax.

Fix by sanitizing the parsed HTML output (not the input) using a library like sanitize-html or isomorphic-dompurify, and harden the WebView:

Suggested hardening
  import { marked } from 'marked';
+ import sanitizeHtml from 'sanitize-html';

- const htmlContent = marked.parse(processedContent, { breaks: true, gfm: true });
+ const rawHtml = marked.parse(processedContent, { breaks: true, gfm: true });
+ const htmlContent = sanitizeHtml(rawHtml, {
+   allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'code', 'pre', 'blockquote', 'img', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'span'],
+   allowedAttributes: { 'a': ['href'], 'img': ['src', 'alt'] }
+ });

   return (
     <WebView
-      originWhitelist={['*']}
+      originWhitelist={['about:blank']}
       source={{ html }}
+      javaScriptEnabled={false}
+      onShouldStartLoadWithRequest={(req) => req.url === 'about:blank'}

Also applies to: 125-127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/components/MarkdownRenderer.tsx` around lines 33 - 34, The parsed HTML
from marked.parse (htmlContent) is injected into the WebView (component using
originWhitelist) without sanitization creating XSS risk; fix by sanitizing the
parsed HTML output with a library like sanitize-html or isomorphic-dompurify
before assigning to htmlContent and use that sanitized string for the WebView
source, and harden the WebView props by tightening originWhitelist (avoid
['*']), disabling/excluding JavaScript execution (e.g.,
javaScriptEnabled=false), disabling DOM storage, and removing permissive
settings so only trusted origins are allowed; update references to marked.parse,
htmlContent, and the WebView component props accordingly.

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Does KaTeX guarantee CSS compatibility across all 0.16.x patch versions, or should renderToString output and CSS always use the exact same version?

💡 Result:

KaTeX does not guarantee CSS compatibility across all 0.16.x patch versions. The renderToString output and CSS must use the exact same version to ensure correct rendering.

Citations:


🏁 Script executed:

cat front/package.json | grep -A 2 -B 2 '"katex"'

Repository: tpaulshippy/bots

Length of output: 201


🏁 Script executed:

cat -n front/components/MarkdownRenderer.tsx | sed -n '35,45p'

Repository: tpaulshippy/bots

Length of output: 543


Update CDN KaTeX CSS to match installed runtime version.

Line 39 pins CDN CSS to 0.16.9, but package.json specifies ^0.16.45. KaTeX does not guarantee CSS compatibility across patch versions—the CSS and runtime output must use the exact same version to avoid rendering and style mismatches. Update the CDN link to 0.16.45 or higher to align with the installed runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/components/MarkdownRenderer.tsx` at line 39, Update the KaTeX CDN
stylesheet URL in MarkdownRenderer.tsx so its version matches the installed
runtime (package.json's ^0.16.45); locate the <link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"> in the
MarkdownRenderer component and change the version segment to 0.16.45 (or the
exact runtime version) so the CSS and KaTeX runtime are identical.

Comment on lines +125 to +132
<WebView
originWhitelist={['*']}
source={{ html }}
style={{ backgroundColor: 'transparent', minHeight: 50 }}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
messagingEnabled={false}
/>
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n front/components/MarkdownRenderer.tsx | head -150

Repository: tpaulshippy/bots

Length of output: 4363


Move inline style to StyleSheet.create and add WebView loading/error handling.

Line 128 uses an inline style object that should be extracted to StyleSheet.create. Additionally, add startInLoadingState and renderError props to provide proper loading and error states per the coding guidelines for this component directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/components/MarkdownRenderer.tsx` around lines 125 - 132, Extract the
inline style object from the WebView into a StyleSheet by creating e.g. const
styles = StyleSheet.create({ webview: { backgroundColor: 'transparent',
minHeight: 50 } }) and apply it to the WebView via style={styles.webview};
additionally add startInLoadingState={true} and a renderError prop that returns
a simple fallback component (e.g. a View/Text) to the WebView element so the
MarkdownRenderer component handles loading and error states properly.

);
};

export default MarkdownRenderer; No newline at end of file
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.

🛠️ Refactor suggestion | 🟠 Major

Export this component as a named export.

Switch from default export to named export, then update import sites accordingly.

Suggested change
-const MarkdownRenderer = ({ content }: MarkdownRendererProps) => {
+export const MarkdownRenderer = ({ content }: MarkdownRendererProps) => {
   // ...
 };
-
-export default MarkdownRenderer;

As per coding guidelines, "Place React Native components in the components directory, use TypeScript for all component files, and export components as named exports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/components/MarkdownRenderer.tsx` at line 136, The file currently uses a
default export for the React component; change it to a named export by exporting
the MarkdownRenderer symbol as a named export (e.g., export const
MarkdownRenderer = ...) or by replacing the final default export with a named
export (export { MarkdownRenderer }); then update all import sites to use the
named import (import { MarkdownRenderer } from '...') and ensure the component
file stays a .tsx TypeScript component.

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