# userscripts Fixes for the sites integrand reads from. Install with Violentmonkey or Tampermonkey. ## webassign-better-entry.user.js WebAssign's answer entry has two problems. Both were diagnosed on a live assignment; the notes are here because the causes are not guessable from the symptoms. ### Answer boxes that vanish Each box is hidden while it typesets, by a transient class: ```css .mathtype-wrapper .mathtype-rendering { visibility: hidden; } ``` Compare a broken box with a working one — same element, same everything: ``` stuck: class="mathtype mtClosed mathtype-rendering" working: class="mathtype mtClosed " ← note the leftover space ``` So boxes get **stuck wearing the flag**. They are not removed: they are full size, 120×36, and permanently invisible, which is why the symptom is a gap where a box should be rather than a missing question. The cause is further up. **MathJax 2.7.9 is loaded once per question** — 20 times on an 18-question page — along with 19 copies each of `mathtype.js`, `autoformat.js` and `overlay.js`. 154 script tags for 60 distinct files. MathJax 2.x keeps a global hub and queue; re-initialising it repeatedly drops callbacks registered against earlier instances, so some boxes never get the callback that removes the class. It is a race, and it worsens as the page settles: **3 of 19 stuck on load, 10 of 19 once it had.** ### Answers erased on the way out Blank boxes are not, mostly, a rendering fault. **The stored answer is erased, and the blank box is that erasure drawn faithfully.** One editor instance is shared by every box of a type. Closing a box resets it to a sentinel meaning "not holding anything" (`overlay.js:1037`): ```js MATHTYPE_DEFAULT = '' setMathTypeValue(editorType, window.mathTypeEditor.MATHTYPE_DEFAULT, resolve); ``` `syncAnswerValue` writes the answer field from whatever the live editor reports, and reads that sentinel as an empty answer: ```js return liveMathML && liveMathML !== MATHTYPE_DEFAULT ? liveMathML : ""; ... if (mathML) answerField.value = mathML; else answerField.value = ""; ``` So a sync landing between the reset and the box losing ownership wipes the answer. The close button syncs on **both `focus` and `pointerdown`**, which is exactly when the reset is running — the two are racing over one shared editor. There is a second, worse turn. The field is written *before* `warnInvalidMathTypeCharacters` runs, and that throws when a box lacks its warning element. Measured on a live page: ``` without guard: ERASED (and threw: Cannot set properties of null …) with guard: PRESERVED ``` The throw leaves the answer erased *and* abandons the rest of the close. **Why correcting it is safe.** A box the student really emptied reads back `` — neither falsy nor the sentinel — so it is stored normally. An answer field that held something and is empty immediately after a sync has therefore always come through the sentinel path, and keeping what was there is always right. The script wraps `window.mathTypeOverlay.syncAnswerValue` in a `try`/`finally` that restores the previous value in that one case, so it holds even when the sync throws. ### Answers drawn too small to see, and why that is not fixed here A closed box is not the editor's rendering. It holds the raw MathML, drawn natively by the browser, and it inherits the page's 13px. At that size the bar over a square root is thinner than a pixel and is never painted — the surd appears and the bar does not, which reads as a browser rendering bug. The same markup at four sizes says otherwise: ``` 13px surd drawn, bar invisible 20px bar faint but present 32px flawless 48px flawless ``` So nothing is wrong with the markup, the font, or MathML support. It is only too small, and `.mathtype .mtAnswer math { font-size: 1.45em }` restores the bar. **It is not shipped.** That rule resizes every answer on the page, and with it in place boxes appeared to blank more often. A missing hairline is a cosmetic complaint; a blank answer is not, so the trade is the wrong way round. It is worth revisiting once the rendering is stable enough to attribute a blank box to one cause with confidence. ### Typing that stopped existing WebAssign used to run its own MathQuill fork — still on their GitHub, last touched December 2015 — which paired brackets and built templates as you typed. They have since moved to MathType (WIRIS; the `wrs_` prefixes give it away) and reimplemented a sliver of the old behaviour in `pads/mathtype/autoformat.js`. Its entire feature set: ```js window.mathTypeEditor.replacements = ["sin","cos","tan","sec","csc","cot","log","ln"]; ``` Parentheses after eight function names, and nothing else. Their own comment concedes `sinh`/`cosh`/`tanh` cannot work, because `sin` fires before you can type the `h`. Measured on a live assignment, typing straight into a box: ``` 1/2 -> 1/2 a literal slash, no x^2 -> x^2 a literal caret, no ``` Every template needs a trip to the toolbar. That is the whole reason it stopped feeling like a nice place to type. ### Three layers, and the one that belongs here **Version 2** clicked toolbar buttons and called `execCommand`: | | | |---|---| | toolbar button `.click()` | works | | `execCommand("insertText", …)` | works, unicode included | | `execCommand("delete")` | does nothing | | synthetic `KeyboardEvent`s | do nothing | Nothing already typed could be selected or removed, so `1/` could not lift the `1` into a numerator. Worse, pressing a structural button from script with content already in the box was measured **emptying it to ``**. **Version 3** found the API WebAssign's own save path uses: ```js const mathML = window.mathTypeEditor[editorType].getMathML(); window.mathTypeEditor[editorType].setMathMLWithCallback(value, resolve); ``` That works, and it is enough to build any structure by hand. But **a write resets the caret to the end of the expression** — after building `12/□` the next character landed beside the fraction, not inside it. The workaround was to intercept every keystroke and place it in the tree directly, which typed correctly while leaving the visible caret somewhere else entirely. Correct, and too clever. **Version 4** uses the editor properly. Underneath both of the above: ```js editor.action(name) // insert a structure at the caret editor.getEditorModel().setCaret(p, n) // place the caret, or select n model.insertText(text) // type, unicode included ``` Action names, found by trying them on a live box — the ones that do not exist fail silently, so they are listed rather than guessed at: ``` fraction superscript subscript squareRoot nRoot parenthesis squareBracket curlyBracket verticalBar ``` `superscript` and `subscript` already take the preceding term as their base and leave the caret in the script, which is exactly what MathQuill's `^` did. `left` and `caretLeft` do not exist, so caret movement goes through `setCaret`. `fraction` inserts an empty one rather than wrapping, **unless something is selected**. So `/` selects the term you just typed and then asks for a fraction. Two measured rules make that work: - **`setCaret` takes a position and a length.** A bare `setCaret(3)` throws `Cannot read properties of null` and leaves typing dead; `setCaret(3, 2)` selects cleanly. It must be wrapped in `beginEventTransaction`/`endEventTransaction` **with a try/finally** — a throw that skips the release deadlocks the renderer semaphore and freezes the page. That is not hypothetical; it froze this one. - **How far back the term runs is found by asking, not by counting.** The first attempt counted caret positions from the MathML and got it wrong: `mfrac` gives both slots one position more than their contents while `msup` gives its base exactly as many, and an `mfenced` holding several children is a comma-separated argument list rather than one group. A model needing a special case per node type will be wrong about the next node type. So the selection grows one position at a time and `getSelectionMathML()` says what is actually selected. It stops on its own at an operator, and **text is the test for everything else**: a probe that gains some has taken more of the term, one that gains none has escaped into the structure around it. Growing past `12` inside a square root returns the whole `√12`, same text, and the root is not part of the term. Measured: ``` len 1 2 len 2 12 len 3 12 ← same text, escaped: keep 2 ``` **An empty slot is what tells the two escapes apart.** A selection holding one is never the term and never the end of the search either. Measured on a live box, caret after the `2` in each: ``` x^2 len 2 2 a hole: the base len 3 x2 reached it, take it tan(12 len 3 12 no hole: the fence closed len 4 n… the function itself tan( every length holds the same empty , so nothing is taken ``` A hole is a part of a structure the search has not reached yet, so it keeps going and `x^2/` takes the whole power. No hole and no new text is a container that closed around the caret, so it stops and a `/` inside `tan(…)` stays inside. Standing in an empty slot there is nothing behind the caret at all, and every selection from there holds that same hole — which is why `tan(` followed by `\frac` builds the fraction in the parentheses rather than around the tangent. An operator still stops the search outright, and a range the editor would not give says nothing about the next one; the three are kept apart. - **Where the denominator is, also by asking.** `S + L + 2` held only for a flat run of characters. A numerator holding a structure takes one position more than the selection it was made from — measured, `x^2` selects as 3 and sits in the numerator as 4 — so the arithmetic put the caret back in the numerator and the next digit landed beside the power. The empty slot is the signal. Growing a selection from the top of the numerator reaches the whole fraction eventually, and the fraction's own denominator is still empty at that moment, so the last selection with nothing empty in it is the numerator: ``` len 4 x2 ← still the numerator len 5 … ← empty slot: stop at 4 ``` Which is the same reading as the term search: a hole means this is not content anyone typed. Given a selection starting at S of length L, the new numerator begins at `S+1` and the denominator at `S+L+2`. With nothing selected the fraction is empty and the caret belongs in the numerator instead. The result is that MathType keeps ownership of the formula *and* the caret. The script only says what to build and where to look, so the caret is finally in the same place as your typing — and the in-memory tree, the keystroke interception and the write queue that version 3 needed are all gone. ### What the script does - Unsticks the boxes, and stops the close race erasing answers. - **`/` builds a real fraction**, lifting the term you just typed into the numerator and leaving you in the denominator. - **`^` and `_`** script the preceding term. - **Pairs** `(`, `[`, `{`, `|`, with the caret inside. - **Deleting by term and by line.** ⌥⌫ takes the stretch `/` would have lifted — `2sin(x)` in one press, and a `+` stops it — and ⌘⌫ takes everything to the left of the caret, leaving anything after it. Away from a Mac, ctrl+⌫ and ctrl+shift+⌫; no platform sniffing, since neither combination means anything on the other. MathType binds none of them, so all four used to delete a single character. A press with nothing to take is left to the editor rather than swallowed, and plain ⌫ is untouched. - **`\` commands** with a completion menu: a lone backslash lists everything, letters filter it (prefix matches first, so `si` offers `sigma` before anything that merely contains those letters), arrows move, Enter/Tab/Space accept, Backspace unwinds, Escape cancels. Any other key commits what you literally typed, so `\pi+` is not silently turned into the highlighted row. Covers `\frac`, `\sqrt`, `\nroot`, `\abs` and the Greek alphabet. - **Fixes the hyperbolics.** The bug is not ordering, it is timing: a name that is the prefix of a longer one waits 140ms to see whether the rest arrives, so `sinh` works. - **And waits for the editor, not the clock.** Every name has a second wait under it, added in 4.6. MathType applies typed characters on its own schedule and has not applied the one that completed the name by the time the key is handled — measured on a live box, the model still read `` for *both* letters of `ln` at the moment the parentheses went in: ``` keydown l model: keydown n model: settle model: ← parentheses opened here result ln l(n) ``` So the `n` was typed inside parentheses that had opened where it was going to land. WebAssign's own version avoided this by running on `keyup`; this one polls the model until the name is really there, then opens the parentheses. If the typist has run past the name by then, it leaves the formula alone. Missing parentheses cost a keystroke; late ones wrap the wrong thing. ### Verified on a live box Typing into an empty, unsubmitted answer box: ``` 5+12 / 9 129 rendered stacked then 7 the 9 becomes 97 — the caret really is in the denominator 2(x+1 x+1 x^2 x2 sinh3t sinh(3t) — the parenthesis arrives on its own ``` Re-measured in 4.6, driving the shipped handler with real keystrokes: ``` ln ln was: l(n) lnx lnx ln(x) sin sin sinh sinh still waits for the h 2log 2log ``` And the deletions, real keystrokes into the live editor: ``` 2sin ⌥⌫ empty the whole term 5+12 ctrl+⌫ 5+ the operator stops it 5+12 ⌘⌫ empty 12+34 ⌘⌫ with the caret after the + 34 the tail stays 123 ⌫ 12 MathType's own, untouched ``` And a fraction built inside each kind of structure, which is what the position-counting version got wrong: ``` inside √12 123 inside √(5+12) 5+12… inside (5+12) 5+… inside x^12 x123 ``` Since 4.6 the last of those is deliberately no longer the case: a `/` typed straight after `x^2` takes the whole power as the numerator, because that is what it looks like it should do. A fraction *inside* an exponent is built with `\frac`. Re-measured on the same live editor, driving the shipped functions: ``` x^2/9 x29 tan(/9 tan9 tan(12/9 tan129 x^12/97 x1297 x_2/9 x29 1/2/3 123 5+12/97 5+1297 inside √12 129 unchanged ``` **MathType still generates the MathML that gets submitted**, so nothing here authors answer data and grading cannot be affected by a formatting difference. ### What is deliberately not done Replacing MathType with a different editor. The contract is only `textarea#latex-source-` for the working LaTeX and `input[name=]` for the submitted value, so it is feasible — but their MathML uses ``, which is deprecated and which a modern editor will not emit. If the grader is at all literal about the dialect, correct answers would be marked wrong with no visible reason. Building on their own editor's API avoids that question entirely: the dialect stays theirs.