Skip to content

Add math (LaTeX) elements rendered via MathJax#57

Merged
boomzero merged 9 commits into
mainfrom
claude/refine-local-plan-t3nJ2
May 17, 2026
Merged

Add math (LaTeX) elements rendered via MathJax#57
boomzero merged 9 commits into
mainfrom
claude/refine-local-plan-t3nJ2

Conversation

@boomzero
Copy link
Copy Markdown
Owner

@boomzero boomzero commented May 16, 2026

Summary

  • Adds a first-class math element type. Click the new toolbar button (or double-click an existing math element) to open a modal with a CodeMirror LaTeX editor and live SVG preview. The rendered SVG is stored in src and the LaTeX source in a new latex column, so file loads and the presentation window pay no MathJax cost.
  • Splits prepareElementUpsert into two prepared statements — the existing path keeps the autosave-friendly "preserve src on UPDATE" optimization for images, and a new math path updates both src and latex so equation edits actually persist.
  • CURRENT_FORMAT_VERSION stays at 2 since v2 hasn't shipped; the new latex column is added through the existing idempotent ensureElementColumns migration, and CURRENT_COMPAT_NOTES is updated to mention math elements.

How it fits together

The math element rides the existing image-rendering path (a FabricImage of an SVG data URI), so most of the canvas/presentation code only needed its image-type predicate widened. lockUniScaling is set on math FabricImages so the equation can't be distorted by a corner drag. imageAssets / restoreSnapshot are extended so undo/redo can re-attach the SVG after the snapshot strips it for size.

MathJax (mathjax-full) and CodeMirror 6 are runtime dependencies but lazy-loaded via dynamic import() on first modal open — they're code-split into separate chunks and the presentation window never pulls them in.

Test plan

  • npm run typecheck (node + svelte-check) — clean
  • npm run lint — clean
  • npm run test — 133/133 pass (added 2 regression tests: math src+latex UPDATE roundtrip, and image-only src preservation on UPDATE)
  • npm run build — clean; MathJax chunks split out of main bundle
  • Manual: insert via toolbar, edit via double-click, broken LaTeX shows inline error, file save/reopen round-trip, presentation mode renders without loading MathJax, undo/redo across LaTeX edits

https://claude.ai/code/session_01PUc5sMRoJbrgNbjaJXvsoj


Generated by Claude Code

Users can now insert equations through a toolbar button or by
double-clicking an existing math element. A modal with a CodeMirror
LaTeX editor and live preview lets them iterate on the source; the
rendered SVG is stored in `src` and the LaTeX in a new `latex` column
so reopening the file is instant and the presentation window never
loads MathJax.

`prepareElementUpsert` was split into two prepared statements: images
keep the autosave-friendly behavior of preserving `src` on UPDATE, while
math elements update both `src` and `latex` so edits actually take effect.

The math element rides the existing image-render path (`FabricImage` of
an SVG data URI) with `lockUniScaling` so the equation can't be
distorted by a corner drag.

https://claude.ai/code/session_01PUc5sMRoJbrgNbjaJXvsoj
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f5e620b15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/renderer/src/components/MathEditorModal.svelte Outdated
Comment thread src/renderer/src/App.svelte
boomzero and others added 3 commits May 17, 2026 17:33
- Switch math.ts to MathJax's prebuilt es5/tex-svg.js bundle. The deep
  js/* CJS sources triggered "require is not defined" in the renderer
  because their transitive imports bypassed Vite's CJS pre-bundling.
- Convert MathJax's ex-unit SVG dimensions to pixels before serializing
  so equations insert at a usable scale instead of viewBox-based
  ~10000px monsters.
- Use fontCache: 'none' so glyph paths are inlined per SVG; with 'local'
  the <use href="#MJX-..."> refs broke once the SVG was lifted into a
  data URL.
- Detect MathJax parse errors via the inner [data-mjx-error] element
  and surface the message as text — the error SVG has a full-viewBox
  black <rect> that otherwise renders as a solid block.
- Add 'unsafe-eval' to the renderer CSP so MathJax's TeX parser and
  startup loader can run (they use new Function() internally).
- Enable syntaxHighlighting(defaultHighlightStyle) on the CodeMirror
  editor so the stex StreamLanguage tokens actually get colored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- clipboard.ts: include 'math' in the recognized element type set,
  validate latex + src on round-trip, register the rendered SVG via
  registerImageSrc on paste (math shares the imageAssets cache with
  images), and pull src through imageSrcOf on serialize. Without this,
  copy-paste of a math element either failed validation or dropped its
  rendered SVG.

- MathEditorModal: pair lastResult with the latex value it rendered.
  If commit() runs while a 200ms debounced render is still pending
  (e.g. fast Cmd+Enter after a keystroke), flush the render
  synchronously and only proceed when result.latex matches the current
  latex. Insert button is also disabled when the two diverge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the placeholder math icon with the Phosphor `Function` glyph
for visual consistency with the rest of the toolbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@boomzero
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44a42f60b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/renderer/src/App.svelte
Comment thread src/renderer/src/App.svelte Outdated
boomzero and others added 3 commits May 17, 2026 18:38
P1: imageAssets is a stable id→src cache for image elements (bytes never
change after import) and snapshots therefore strip src to keep history
small. Math elements re-render their SVG on every LaTeX edit, so the
cache is unstable: after edit A→B, undo restored latex=A but pulled
src=B from imageAssets, and the next autosave persisted the mismatched
pair — reopening showed the wrong equation for the stored LaTeX.

  stripSrcForSnapshot() now only drops src for type==='image'. Math
  snapshots carry their own src, and restoreSnapshot re-seeds
  imageAssets from the snapshot so later renders/autosave agree with
  the restored revision.

P2: lockUniScaling doesn't reliably prevent distortion on fabric@7 —
side handles still allow non-uniform stretching, which squashes the
rendered SVG's glyphs.

  applyMathAspectLock() hides the four mid-edge controls
  (mt/mb/ml/mr), sets lockScalingFlip, and listens for the scaling
  event to clamp scaleX === scaleY on every corner drag. Applied at
  both renderCanvasFromState image sites (cached + async).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reopening the math editor and saving overwrote the element's width and
height with `rendered.{width,height}`, erasing whatever resize the user
had done. Keep the user's chosen visual height (the natural proxy for
equation font-size) and rescale width to the new aspect ratio so the
glyphs stay the same size as before the edit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Spec updates (TWIG_SPEC.md):
- §1: add `latex TEXT` column to the elements table
- §3: add `math` row to the element-types table — `latex` is the only
  required field; `src`, `width`, and `height` are optional. Document
  twig's behavior of rendering on first open and writing the values
  back so AI generators don't need a MathJax-capable toolchain.
- §5: clipboard validation requires `latex`; `src` is optional and
  re-rendered on paste if absent.
- §6: add the `math_` ID prefix.
- §9: include `latex` in the Python schema example.
- §10: relax the math checklist item accordingly.

Implementation (App.svelte):
- hydrateMissingMathSrc(): on every render, find math elements with
  `latex` but no `src`, run MathJax, and write src/width/height back
  to state (autosave persists them on the next debounce). Fire-and-
  forget — the state mutation re-triggers renderCanvasFromState.
- makeMathErrorSvg(): when MathJax rejects the stored latex, render
  a visible red error bar with the message and (truncated) source so
  the slide isn't blank and the user can locate the bad equation.
- renderResultToMath() helper bridges the RenderResult union and the
  shared write path.

Manual test: qa/math/make_latex_only.py builds a .tb with three math
elements (two valid, one with `\sqrt{x`); opening it in twig renders
all three and autosaves src/width/height.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@boomzero
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4d32e81ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/renderer/src/lib/clipboard.ts Outdated
Comment thread src/renderer/src/App.svelte
boomzero and others added 2 commits May 17, 2026 19:37
The §12 v2 bullet list omitted the math element addition and the
expanded compat_notes payload. Add bullets covering the new `math`
element type, the `latex` column, clipboard handling, and the v2
writer's compat_notes contract (already implemented as
CURRENT_COMPAT_NOTES in db.ts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
P1: isTwigElement was rejecting any math clipboard payload that lacked
`src`, even though the spec promises latex-only math elements work
end-to-end (paste re-renders via MathJax). Require only `latex` on the
clipboard; accept `src` when present, fall through to hydration when
not.

P1: handleMathCommit's edit branch mutated `el.latex`, `el.src`, and
`el.width` in place. The $effect driving renderCanvasFromState only
tracks the elements array reference (see handlePropertyChange's note
on the same gotcha), so the stale FabricImage stayed on canvas until
the next slide switch. Trigger renderCanvasFromState() explicitly
after the mutation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@boomzero boomzero merged commit a544f3e into main May 17, 2026
3 checks passed
@boomzero boomzero deleted the claude/refine-local-plan-t3nJ2 branch May 17, 2026 14:15
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.

2 participants