Overcoming LaTeX Restrictions

Jan 14, 2026

OzMath (weblink) is a website I built to help students in high school revise for their final exam. Key features include a mock test feature, AI autograder and visual math editor.

For devtools, I used bolt.new for the first ~4 months of development, then pivoted to Cursor which has been my main ide till this day. I used a lot of models but mostly GPT 5.1 Codex Max.

For data persistance, I used to use Supabase (back when I tried to profit from ozmath). But now I just use caching so that users dont have to sign up. The concern with this is API costs but I'm not too concerned as I'm only using vision models on a select few problems with extended solutions, so costs are cheap (added $10aud a year ago and havent had to top up).

As for hosting, I use framer for the landing page and reroute to the Vue app to Netlify.

While developing the UI, I quickly found out that LaTeX does not wrap containers well at all. Since all my question descriptions are in LaTeX, when I implemented Shadcn resizable in the ProblemView UI, the LaTeX didnt wrap the container, and I couldnt work around it by manually creating new lines due to screen sizing variations. It took me ages but I eventually noticed that the latex operator \to (which is a right pointing arrow) actually had a text-wrapping property to it.

From this, I found a way to brute force a solution by seperating every character with \to, then making the element invisible with \phantom, and pushing the (now) invisible space back with \mkern:

/**
 * Adds proper spacing between words in LaTeX text mode
 * @param text The LaTeX text to process
 * @returns The text with proper spacing between words
 */
export function addLatexTextSpacing(text: string): string {
  // Early return if text is empty or not a string
  if (!text || typeof text !== 'string') return text;

  // Regular expression to match text mode content, but not inside array environments
  const textModeRegex = /\\text\{([^}]+)\}/g;
  let inArray = false;

  return text.replace(textModeRegex, (match, textContent) => {
    // Don't process text inside array environments
    if (textContent.includes('\\begin{array}') || textContent.includes('\\end{array}')) {
      return match;
    }

    // Split by spaces while preserving LaTeX commands
    const words = textContent.split(/\s+/).filter(word => word.length > 0);
    
    // Add phantom spacing between text words and \; after each text block
    // Use \mkern-6mu for precise negative space (equivalent to \!\!)
    return words
      .map(word => `\\text{\\sf ${word}}\\;`)
      .join('\\mathrel{\\vphantom{\\to}}\\mkern-8mu');
  });
}