docs: add latex rendering implementation plan

This commit is contained in:
Maxim Kirilyuk
2026-05-23 17:45:51 +03:00
committed by ekko
parent dca8fc6d8a
commit 7577cc472e
+490
View File
@@ -0,0 +1,490 @@
# 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.
**Architecture:** Keep the existing `markdown-it` renderer in `MarkdownRenderer.vue` and add a KaTeX-backed math plugin there. Load KaTeX styles globally once, cover inline and block 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.
- Existing Markdown features still work: code fences, Mermaid fences, headings, local file links, mentions.
- Math inside fenced code blocks stays literal and is not rendered.
- Invalid/unsupported math syntax does not crash the chat renderer.
- `npm run test -- tests/client/markdown-rendering.test.ts` passes.
- `npm run build` passes.
- Local `hermes-web-ui.service` can 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:
```bash
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.json` or lockfile generated by npm if present
**Step 1: Install packages**
Run from repo root:
```bash
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**
```bash
npm ls katex markdown-it-katex
```
**Expected:** both packages resolve without `UNMET DEPENDENCY`.
**Step 3: Commit**
```bash
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**
```bash
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`:
```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**
```bash
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:
```ts
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:
```ts
md.use(markdownItKatex, {
throwOnError: false,
errorColor: '#cc3344',
output: 'htmlAndMathml',
})
```
**Step 3: Preserve existing Mermaid fence behavior**
Do not move or remove this existing code:
```ts
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**
```bash
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**
```bash
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:
```ts
import 'katex/dist/katex.min.css'
```
Current import area should become:
```ts
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`:
```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**
```bash
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', () => { ... })`:
```ts
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**
```ts
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**
```ts
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**
```ts
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**
```bash
npm run test -- tests/client/markdown-rendering.test.ts
```
**Expected:** all tests in the file pass.
**Step 6: Commit**
```bash
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**
```bash
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**
```bash
npm run test
```
**Expected:** pass. If unrelated pre-existing failures appear, record them in the PR body with evidence.
**Step 3: Run production build**
```bash
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**
```bash
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**
```bash
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:
```bash
cd /home/werserk/2-kira/hermes-web-ui
npm install -g --prefix /home/werserk/.npm-global .
```
**Step 3: Restart the Web UI service**
```bash
systemctl --user restart hermes-web-ui.service
systemctl --user status hermes-web-ui.service --no-pager --lines=20
```
**Step 4: Smoke test HTTP response**
```bash
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:
```md
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**
```bash
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 like `cost is $5 and $6` may be parsed depending on plugin behavior. If this is noisy, switch to `markdown-it-texmath` with explicit delimiter options or configure delimiters to prioritize `\(...\)` / `\[...\]`.
- **Security:** keep `html: false` in `markdown-it`; do not enable arbitrary HTML. KaTeX should run with `trust: false` unless 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: false` should prevent chat crashes. Regression test required.
## Definition of Done
- Branch `feat/latex-rendering` contains 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:main` or is ready to create.