13 KiB
LaTeX Rendering Implementation Plan
For Hermes: Use subagent-driven-development skill to implement this plan task-by-task.
Goal: Add reliable LaTeX/math rendering to Hermes Web UI chat Markdown messages so formulas render visually instead of appearing as raw TeX, including explicit fenced math blocks like ```latex.
Architecture: Keep the existing markdown-it renderer in MarkdownRenderer.vue and add a KaTeX-backed math plugin there, plus a small fence override for explicit LaTeX code fences. Load KaTeX styles globally once, cover inline, display, and fenced math in component tests, then verify with a production build and a local Web UI smoke test.
Tech Stack: Vue 3, TypeScript, Vite, markdown-it, KaTeX, Vitest, @vue/test-utils.
Acceptance Criteria
- Chat messages render inline math like
$x^2 + y^2 = z^2$as KaTeX HTML. - Chat messages render block math like
$$\n\\int_0^1 x^2 dx = \\frac{1}{3}\n$$as display KaTeX HTML. - Chat messages render explicit fenced math blocks like
, orlatex</code>, <code>tex```mathas display KaTeX HTML. - Existing Markdown features still work: code fences, Mermaid fences, headings, local file links, mentions.
- Ordinary fenced code blocks stay literal and are not rendered as math.
- Invalid/unsupported math syntax does not crash the chat renderer.
npm run test -- tests/client/markdown-rendering.test.tspasses.npm run buildpasses.- Local
hermes-web-ui.servicecan be restarted onto the built fork and displays formulas in the real panel.
Recommended Dependency Choice
Use markdown-it-katex + katex because the app already uses markdown-it, and this is the smallest change surface.
Install as runtime dependencies, not dev-only, because the renderer runs in the browser bundle:
npm install katex markdown-it-katex
npm install -D @types/katex
If TypeScript reports no module declaration for markdown-it-katex, add a local declaration file instead of weakening tsconfig.
Task 1: Add KaTeX dependencies
Objective: Make the required renderer and styles available to the client bundle.
Files:
- Modify:
package.json - Modify:
package-lock.jsonor lockfile generated by npm if present
Step 1: Install packages
Run from repo root:
cd /home/werserk/2-kira/hermes-web-ui
npm install katex markdown-it-katex
npm install -D @types/katex
Expected: package.json includes katex and markdown-it-katex; lockfile is updated.
Step 2: Verify dependency tree
npm ls katex markdown-it-katex
Expected: both packages resolve without UNMET DEPENDENCY.
Step 3: Commit
git add package.json package-lock.json
git commit -m "chore: add latex rendering dependencies"
Task 2: Add TypeScript module declaration if needed
Objective: Keep TypeScript strict and avoid any leaking into the renderer setup.
Files:
- Create if needed:
packages/client/src/types/markdown-it-katex.d.ts
Step 1: Run typecheck to discover whether declaration is needed
npm run build
Expected before implementation: build may still fail later because code is not changed yet. For this task, only check whether TypeScript knows the markdown-it-katex module after it is imported in Task 3.
Step 2: If TS2307/TS7016 appears, create declaration
Create packages/client/src/types/markdown-it-katex.d.ts:
declare module 'markdown-it-katex' {
import type MarkdownIt from 'markdown-it'
interface MarkdownItKatexOptions {
throwOnError?: boolean
errorColor?: string
strict?: boolean | string | ((errorCode: string) => boolean | string)
output?: 'html' | 'mathml' | 'htmlAndMathml'
trust?: boolean | ((context: unknown) => boolean)
macros?: Record<string, string>
}
const markdownItKatex: MarkdownIt.PluginWithOptions<MarkdownItKatexOptions>
export default markdownItKatex
}
Step 3: Commit only if file was needed
git add packages/client/src/types/markdown-it-katex.d.ts
git commit -m "chore: add markdown-it-katex types"
Task 3: Wire KaTeX into MarkdownRenderer
Objective: Render math in the same path that currently renders Markdown chat messages.
Files:
- Modify:
packages/client/src/components/hermes/chat/MarkdownRenderer.vue
Step 1: Import the plugin
Near existing Markdown imports:
import markdownItKatex from 'markdown-it-katex'
Step 2: Register the plugin after creating md
After the new MarkdownItConstructor(...) block and before custom fence renderer setup:
md.use(markdownItKatex, {
throwOnError: false,
errorColor: '#cc3344',
output: 'htmlAndMathml',
})
Step 3: Preserve existing Mermaid fence behavior
Do not move or remove this existing code:
const defaultFenceRenderer = md.renderer.rules.fence?.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx]
if (isMermaidFence(token.info)) {
return renderMermaidPlaceholder(token.content)
}
if (defaultFenceRenderer) {
return defaultFenceRenderer(tokens, idx, options, env, self)
}
return self.renderToken(tokens, idx, options)
}
Step 4: Run focused test suite
npm run test -- tests/client/markdown-rendering.test.ts
Expected: existing tests still pass or only fail because new math tests are not yet added.
Step 5: Commit
git add packages/client/src/components/hermes/chat/MarkdownRenderer.vue
git commit -m "feat: enable latex rendering in chat markdown"
Task 4: Load and tune KaTeX CSS
Objective: Ensure rendered math is visually correct in the Web UI.
Files:
- Modify:
packages/client/src/main.ts - Optionally modify:
packages/client/src/styles/global.scss
Step 1: Import KaTeX CSS once globally
In packages/client/src/main.ts, add after global styles import:
import 'katex/dist/katex.min.css'
Current import area should become:
import App from './App.vue'
import './styles/global.scss'
import 'katex/dist/katex.min.css'
Step 2: Add Hermes-specific layout tweaks only if needed
If visual smoke test shows cramped block equations, append to packages/client/src/styles/global.scss:
.markdown-body {
.katex-display {
overflow-x: auto;
overflow-y: hidden;
padding: 0.25rem 0;
}
}
Do not restyle KaTeX itself unless there is a concrete visual bug.
Step 3: Commit
git add packages/client/src/main.ts packages/client/src/styles/global.scss
git commit -m "style: load katex styles for markdown math"
Task 5: Add regression tests for math rendering
Objective: Prove formulas render, code fences stay literal, and malformed math is safe.
Files:
- Modify:
tests/client/markdown-rendering.test.ts
Step 1: Add inline math test
Inside describe('MarkdownRenderer', () => { ... }):
it('renders inline latex math with katex', () => {
const wrapper = mount(MarkdownRenderer, {
props: {
content: 'Pythagoras: $x^2 + y^2 = z^2$.',
},
})
const body = wrapper.find('.markdown-body')
expect(body.find('.katex').exists()).toBe(true)
expect(body.html()).toContain('x')
expect(body.html()).toContain('z')
expect(body.text()).not.toContain('$x^2 + y^2 = z^2$')
})
Step 2: Add block math test
it('renders display latex math with katex', () => {
const wrapper = mount(MarkdownRenderer, {
props: {
content: '$$\n\\int_0^1 x^2 dx = \\frac{1}{3}\n$$',
},
})
const body = wrapper.find('.markdown-body')
expect(body.find('.katex-display').exists()).toBe(true)
expect(body.find('.katex').exists()).toBe(true)
expect(body.text()).not.toContain('$$')
})
Step 3: Add fenced-code non-rendering test
it('does not render latex inside fenced code blocks', () => {
const wrapper = mount(MarkdownRenderer, {
props: {
content: '```md\n$x^2 + y^2 = z^2$\n```',
},
})
expect(wrapper.find('.markdown-body').find('.katex').exists()).toBe(false)
expect(wrapper.find('code.hljs').text()).toContain('$x^2 + y^2 = z^2$')
})
Step 4: Add invalid math safety test
it('keeps rendering when latex syntax is invalid', () => {
const wrapper = mount(MarkdownRenderer, {
props: {
content: 'Before $\\notacommand{ after',
},
})
expect(wrapper.find('.markdown-body').text()).toContain('Before')
})
Step 5: Run focused tests
npm run test -- tests/client/markdown-rendering.test.ts
Expected: all tests in the file pass.
Step 6: Commit
git add tests/client/markdown-rendering.test.ts
git commit -m "test: cover latex rendering in markdown"
Task 6: Run build and broader regression checks
Objective: Catch TypeScript, Vite, and existing client regressions before deploying locally.
Files:
- No source changes expected unless checks fail.
Step 1: Run focused tests
npm run test -- tests/client/markdown-rendering.test.ts tests/client/markdown-rendering-mermaid-import-timeout.test.ts
Expected: pass.
Step 2: Run all tests if practical
npm run test
Expected: pass. If unrelated pre-existing failures appear, record them in the PR body with evidence.
Step 3: Run production build
npm run build
Expected: pass and update dist/ only as build output. Do not commit dist/ unless the package/release workflow requires it.
Step 4: Commit fixes only if needed
git add <fixed-files>
git commit -m "fix: stabilize latex markdown rendering"
Task 7: Deploy to Maxim's local Hermes Web UI instance
Objective: Start using the feature on https://hermes.kiraproject.ru/ before upstream PR review completes.
Files:
- Local runtime install under npm/global prefix may change outside the repo.
- Do not commit generated deployment artifacts unless intentionally part of the repo.
Step 1: Build from the fork checkout
cd /home/werserk/2-kira/hermes-web-ui
npm run build
Step 2: Replace currently installed package using npm link or local global install
Preferred reversible approach:
cd /home/werserk/2-kira/hermes-web-ui
npm install -g --prefix /home/werserk/.npm-global .
Step 3: Restart the Web UI service
systemctl --user restart hermes-web-ui.service
systemctl --user status hermes-web-ui.service --no-pager --lines=20
Step 4: Smoke test HTTP response
curl -sS -I --max-time 5 http://127.0.0.1:8648/ | head -20
Expected: HTTP 200/304 or another successful static response, not connection refused.
Step 5: Browser smoke test
Open the chat panel and send/render a message containing:
Inline: $x^2 + y^2 = z^2$
Block:
$$
\\int_0^1 x^2 dx = \\frac{1}{3}
$$
Code fence should stay literal:
```md
$x^2 + y^2 = z^2$
**Expected:** first two formulas render visually; fenced formula stays literal.
---
## Task 8: Prepare upstream PR
**Objective:** Make the contribution easy for upstream maintainers to review.
**Files:**
- No code changes required unless PR polish finds an issue.
**Step 1: Push branch**
```bash
git push origin feat/latex-rendering
Step 2: Create PR draft
gh pr create \
--repo EKKOLearnAI/hermes-web-ui \
--head kira-project-lab:feat/latex-rendering \
--base main \
--title "feat(chat): render LaTeX math in Markdown messages" \
--body "$(cat <<'EOF'
## Summary
- Adds KaTeX-backed LaTeX rendering to chat Markdown messages.
- Supports inline `$...$` and display `$$...$$` math.
- Keeps math inside fenced code blocks literal.
## Test Plan
- [ ] npm run test -- tests/client/markdown-rendering.test.ts
- [ ] npm run test -- tests/client/markdown-rendering-mermaid-import-timeout.test.ts
- [ ] npm run build
- [ ] Manual browser smoke test with inline, block, and fenced math
## Notes
This uses the existing markdown-it rendering path and keeps HTML disabled.
EOF
)" \
--draft
Step 3: Attach visual proof
Add before/after screenshots in the PR body or a PR comment:
- Before: raw
$...$/$$...$$text. - After: rendered KaTeX inline and display equations.
Risks and Guardrails
- Delimiter ambiguity:
$is common in shell snippets and prices. The fenced-code test protects code blocks, but inline text likecost is $5 and $6may be parsed depending on plugin behavior. If this is noisy, switch tomarkdown-it-texmathwith explicit delimiter options or configure delimiters to prioritize\(...\)/\[...\]. - Security: keep
html: falseinmarkdown-it; do not enable arbitrary HTML. KaTeX should run withtrust: falseunless a clear need appears. - Bundle size: KaTeX adds client bundle weight. Acceptable for this feature, but mention it in PR if maintainers ask.
- CSS scope: KaTeX CSS is global. Import once in
main.ts; avoid duplicating it inside component styles. - Invalid math:
throwOnError: falseshould prevent chat crashes. Regression test required.
Definition of Done
- Branch
feat/latex-renderingcontains committed implementation. - Local Web UI renders inline and block formulas.
- Focused Markdown tests pass.
- Production build passes.
- PR draft exists against
EKKOLearnAI/hermes-web-ui:mainor is ready to create.