Add math (LaTeX) elements rendered via MathJax#57
Conversation
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
There was a problem hiding this comment.
💡 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".
- 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>
|
@codex review |
There was a problem hiding this comment.
💡 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".
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>
|
@codex review |
There was a problem hiding this comment.
💡 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".
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>
Summary
srcand the LaTeX source in a newlatexcolumn, so file loads and the presentation window pay no MathJax cost.prepareElementUpsertinto two prepared statements — the existing path keeps the autosave-friendly "preservesrcon UPDATE" optimization for images, and a new math path updates bothsrcandlatexso equation edits actually persist.CURRENT_FORMAT_VERSIONstays at 2 since v2 hasn't shipped; the newlatexcolumn is added through the existing idempotentensureElementColumnsmigration, andCURRENT_COMPAT_NOTESis updated to mention math elements.How it fits together
The math element rides the existing image-rendering path (a
FabricImageof an SVG data URI), so most of the canvas/presentation code only needed its image-type predicate widened.lockUniScalingis set on math FabricImages so the equation can't be distorted by a corner drag.imageAssets/restoreSnapshotare 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 dynamicimport()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) — cleannpm run lint— cleannpm run test— 133/133 pass (added 2 regression tests: mathsrc+latexUPDATE roundtrip, and image-onlysrcpreservation on UPDATE)npm run build— clean; MathJax chunks split out of main bundlehttps://claude.ai/code/session_01PUc5sMRoJbrgNbjaJXvsoj
Generated by Claude Code