Merge pull request #692 from desmosinc/feature.substituteKeyboardEvents

`substituteKeyboardEvents` config option
diff --git a/Makefile b/Makefile
index dcb2497..8acc2a6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,23 @@
 #
+# -*- Prerequisites -*-
+#
+
+# the fact that 'I am Node.js' is unquoted here looks wrong to me but it
+# CAN'T be quoted, I tried. Apparently in GNU Makefiles, in the paren+comma
+# syntax for conditionals, quotes are literal; and because the $(shell...)
+# call has parentheses and single and double quotes, the quoted syntaxes
+# don't work (I tried), we HAVE to use the paren+comma syntax
+ifneq ($(shell node -e 'console.log("I am Node.js")'), I am Node.js)
+  ifeq ($(shell nodejs -e 'console.log("I am Node.js")' 2>/dev/null), I am Node.js)
+    $(error You have /usr/bin/nodejs but no /usr/bin/node, please 'sudo apt-get install nodejs-legacy' (see http://stackoverflow.com/a/21171188/362030 ))
+  endif
+
+  $(error Please install Node.js: https://nodejs.org/ )
+endif
+
+
+
+#
 # -*- Configuration -*-
 #
 
diff --git a/README.md b/README.md
index ff01a2d..c5202aa 100644
--- a/README.md
+++ b/README.md
@@ -1,502 +1,43 @@
-# [MathQuill](http://mathquill.github.com)
+# [MathQuill](http://mathquill.com)
 
-by [Han][], [Jeanine][], and [Mary][] (maintainers@mathquill.com)
+by [Han](http://github.com/laughinghan), [Jeanine](http://github.com/jneen), and [Mary](http://github.com/stufflebear) (<maintainers@mathquill.com>) [<img alt="slackin.mathquill.com" src="http://slackin.mathquill.com/badge.svg" align="top">](http://slackin.mathquill.com)
 
-[Han]: http://github.com/laughinghan
-[Jeanine]: http://github.com/jneen
-[Mary]: http://github.com/stufflebear
+MathQuill is a web formula editor designed to make typing math easy and beautiful.
 
-Good news! We've resumed active development and we're committed to getting
-things running smoothly.  
-Find a dusty corner? Let us know! <big>[<img alt="slackin.mathquill.com" src="http://slackin.mathquill.com/badge.svg" align="top">](http://slackin.mathquill.com)
-[<img alt="freenode irc: #mathquill" src="https://img.shields.io/badge/%20freenode%20irc%20-%20%23mathquill%20-brightgreen.svg" align="top">](http://webchat.freenode.net/?channels=mathquill)</big>
+[<img alt="homepage demo" src="https://cloud.githubusercontent.com/assets/225809/15163580/1bc048c4-16be-11e6-98a6-de467d00cff1.gif" width="260">](http://mathquill.com)
 
-## Usage
+The MathQuill project is supported by its [partners](http://mathquill.com/partners.html). We hold ourselves to a compassionate [Code of Conduct](http://docs.mathquill.com/en/latest/Code_of_Conduct/).
 
-Just load MathQuill and call our constructors on some HTML element DOM objects,
-for example:
+MathQuill is resuming active development and we're committed to getting things running smoothly. Find a dusty corner? [Let us know in Slack.](http://slackin.mathquill.com) (Prefer IRC? We're `#mathquill` on Freenode.)
 
-```html
-<link rel="stylesheet" href="/path/to/mathquill.css"/>
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
-<script src="/path/to/mathquill.js"></script>
+## Getting Started
 
-<p>
-  Solve <span id="problem">ax^2 + bx + c = 0</span>:
-  <span id="answer">x=</span>
-</p>
+MathQuill has a simple interface. This brief example creates a MathQuill element and renders, then reads a given input:
+```javascript
+var htmlElement = document.getElementById('some_id');
+var config = {
+  handlers: { edit: function(){ ... } },
+  restrictMismatchedBrackets: true
+};
+var mathField = MQ.MathField(htmlElement, config);
 
-<script>
-  var MQ = MathQuill.getInterface(2);
-  MQ.StaticMath($('#problem')[0]);
-  var answer = MQ.MathField($('#answer')[0], {
-    handlers: {
-      edit: function() {
-        checkAnswer(answer.latex());
-      }
-    }
-  });
-</script>
+mathField.latex('2^{\\frac{3}{2}}'); // Renders the given LaTeX in the MathQuill field
+mathField.latex(); // => '2^{\\frac{3}{2}}'
 ```
 
-To load MathQuill,
-- [jQuery 1.4.3+](http://jquery.com) has to be loaded before `mathquill.js`
-  ([Google CDN-hosted copy][] recommended)
-- the fonts should be served from the `font/` directory relative to
-  `mathquill.css` (unless you'd rather change where your copy of `mathquill.css`
-  includes them from), which is already the case if you just:
-- download and serve [the latest release][].
+Check out our [Getting Started Guide](http://docs.mathquill.com/en/latest/Getting_Started/) for setup instructions and basic MathQuill usage.
 
-[Google CDN-hosted copy]: http://code.google.com/apis/libraries/devguide.html#jquery
-[the latest release]: https://github.com/mathquill/mathquill/releases/latest
+## Docs
 
-To use the MathQuill API, first get the latest version of the interface:
+Most documentation for MathQuill is located on [ReadTheDocs](http://docs.mathquill.com/en/latest/).
 
-```js
-var MQ = MathQuill.getInterface(2);
-```
-
-Now you can call `MQ.StaticMath()` or `MQ.MathField()`, which MathQuill-ify
-an HTML element and return an API object. If the element had already been
-MathQuill-ified into the same kind, return that kind of API object (if
-different kind or not an HTML element, `null`). Note that it always returns
-either an instance of itself, or `null`.
-
-```js
-var staticMath = MQ.StaticMath(staticMathSpan);
-mathField instanceof MQ.StaticMath // => true
-mathField instanceof MQ // => true
-mathField instanceof MathQuill // => true
-
-var mathField = MQ.MathField(mathFieldSpan);
-mathField instanceof MQ.MathField // => true
-mathField instanceof MQ.EditableField // => true
-mathField instanceof MQ // => true
-mathField instanceof MathQuill // => true
-```
-
-`MQ` itself is a function that takes an HTML element and, if it's the root
-HTML element of a static math or math field, returns an API object for it
-(if not, `null`):
-
-```js
-MQ(mathFieldSpan) instanceof MQ.MathField // => true
-MQ(otherSpan) // => null
-```
-
-API objects for the same MathQuill instance have the same `.id`, which will
-always be a unique truthy primitive value that can be used as an object key
-(like an ad hoc [`Map`][] or [`Set`][]):
-
-```js
-MQ(mathFieldSpan).id === mathField.id // => true
-
-var setOfMathFields = {};
-setOfMathFields[mathField.id] = mathField;
-MQ(mathFieldSpan).id in setOfMathFields // => true
-staticMath.id in setOfMathFields // => false
-```
-
-[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
-[`Set`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
-
-Similarly, API objects for the same MathQuill instance share a `.data` object
-(which can be used like an ad hoc [`WeakMap`][] or [`WeakSet`][]):
-
-```js
-MQ(mathFieldSpan).data === mathField.data // => true
-mathField.data.foo = 'bar';
-MQ(mathFieldSpan).data.foo // => 'bar'
-```
-
-[`WeakMap`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
-[`WeakSet`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet
-
-Any element that has been MathQuill-ified can be reverted:
-
-```html
-<span id="revert-me" class="mathquill-static-math">
-  some <code>HTML</code>
-</span>
-```
-```js
-MQ($('#revert-me')[0]).revert().html(); // => 'some <code>HTML</code>'
-```
-
-MathQuill uses computed dimensions, so if they change (because an element was
-mathquill-ified before it was in the visible HTML DOM, or the font size
-changed), then you'll need to tell MathQuill to recompute:
-
-```js
-var mathFieldSpan = $('<span>\\sqrt{2}</span>');
-var mathField = MQ.MathField(mathFieldSpan[0]);
-mathFieldSpan.appendTo(document.body);
-mathField.reflow();
-```
-
-MathQuill API objects further expose the following public methods:
-
-* `.el()` returns the root HTML element
-* `.html()` returns the contents as static HTML
-* `.latex()` returns the contents as LaTeX
-* `.latex('a_n x^n')` will render the argument as LaTeX
-
-Additionally, descendants of `MQ.EditableField` (currently only `MQ.MathField`)
-expose:
-
-* `.focus()`, `.blur()` focuses or defocuses the editable field
-* `.write(' - 1')` will write some LaTeX at the current cursor position
-* `.cmd('\\sqrt')` will enter a LaTeX command at the current cursor position or
-  with the current selection
-* `.select()` selects the contents (just like [on `textarea`s][] and [on
-  `input`s][])
-* `.clearSelection()` clears the current selection
-* `.moveTo{Left,Right,Dir}End()` move the cursor to the left/right end of the
-  editable field, respectively. (The first two are implemented in terms of
-  `.moveToDirEnd(dir)` where `dir` is one of `MQ.L` or `MQ.R`, constants that
-  obey the contract that `MQ.L === -MQ.R` and vice versa.)
-* `.keystroke(keys)` simulates keystrokes given a string like `"Ctrl-Home Del"`,
-  a whitespace-delimited list of [key values][] with optional prefixes
-* `.typedText(text)` simulates typing text, one character at a time
-* `ᴇxᴘᴇʀɪᴍᴇɴᴛᴀʟ` `.dropEmbedded(pageX, pageY, options)` insert a custom
-  embedded element at the given coordinates, where `options` is an object like:
-
-  ```js
-  {
-    htmlString: '<span class="custom-embed"></span>',
-    text: function() { return 'custom_embed'; },
-    latex: function() { return '\customEmbed'; }
-  }
-  ```
-* `ᴇxᴘᴇʀɪᴍᴇɴᴛᴀʟ` `.registerEmbed('name', function(id){return options})` allows MathQuill to parse custom embedded objects from latex, where `options` is an object like the one defined above in `.dropEmbedded`. This will parse the following latex into the embedded object you defined: `\embed{name}[id]}`
-
-[on `textarea`s]: http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-48880622
-[on `input`s]: http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-34677168
-[key values]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
-
-MathQuill overwrites the global `MathQuill` variable when loaded. You can undo
-that with `.noConflict()` (similar to [`jQuery.noConflict()`]
-(http://api.jquery.com/jQuery.noConflict)):
-
-```html
-<script src="/path/to/first-mathquill.js"></script>
-<script src="/path/to/second-mathquill.js"></script>
-<script>
-var secondMQ = MathQuill.noConflict().getInterface(2);
-secondMQ.MathField(...);
-
-var firstMQ = MathQuill.getInterface(2);
-firstMQ.MathField(...);
-</script>
-```
-
-(Warning: This lets different copies of MathQuill each power their own
- math fields, but using different copies on the same DOM element won't
- work. Anyway, .noConflict() is primarily to help you reduce globals.)
-
-#### Configuration Options
-
-`MQ.MathField()` can also take an options object:
-
-```js
-var el = $('<span>x^2</span>').appendTo('body');
-var mathField = MQ.MathField(el[0], {
-  spaceBehavesLikeTab: true,
-  leftRightIntoCmdGoes: 'up',
-  restrictMismatchedBrackets: true,
-  sumStartsWithNEquals: true,
-  supSubsRequireOperand: true,
-  charsThatBreakOutOfSupSub: '+-=<>',
-  autoSubscriptNumerals: true,
-  autoCommands: 'pi theta sqrt sum',
-  autoOperatorNames: 'sin cos etc',
-  substituteTextarea: function() {
-    return document.createElement('textarea');
-  },
-  handlers: {
-    edit: function(mathField) { ... },
-    upOutOf: function(mathField) { ... },
-    moveOutOf: function(dir, mathField) { if (dir === MQ.L) ... else ... }
-  }
-});
-```
-
-To change `mathField`'s options, the `.config({ ... })` method takes an options
-object in the same format.
-
-Global defaults for a page may be set with `MQ.config({ ... })`.
-
-If `spaceBehavesLikeTab` is true the keystrokes {Shift-,}Spacebar will behave
-like {Shift-,}Tab escaping from the current block (as opposed to the default
-behavior of inserting a Space character).
-
-By default, the Left and Right keys move the cursor through all possible cursor
-positions in a particular order: right into a fraction puts the cursor at the
-left end of the numerator, right out of the numerator puts the cursor at the
-left end of the denominator, right out of the denominator puts the cursor to the
-right of the fraction; symmetrically, left into a fraction puts the cursor at
-the right end of the denominator, etc. Note that right out of the numerator to
-the left end of the denominator is actually leftwards (and downwards, it's
-basically wrapped). If instead you want right to always go right, and left to
-always go left, you can set `leftRightIntoCmdGoes` to `'up'` or `'down'` so that
-left and right go up or down (respectively) into commands, e.g. `'up'` means
-that left into a fraction goes up into the numerator, skipping the denominator;
-symmetrically, right out of the numerator skips the denominator and puts the
-cursor to the right of the fraction, which unlike the default behavior is
-actually rightwards (the drawback is the denominator is always skipped, you
-can't get to it with just Left and Right, you have to press Down); which is
-the same behavior as the Desmos calculator. `'down'` instead means it is the
-numerator that is always skipped, which is the same behavior as the Mac OS X
-built-in app Grapher.
-
-If `restrictMismatchedBrackets` is true then you can type [a,b) and [a,b), but
-if you try typing `[x}` or `\langle x|`, you'll get `[{x}]` or
-`\langle|x|\rangle` instead. This lets you type `(|x|+1)` normally; otherwise,
-you'd get `\left( \right| x \left| + 1 \right)`.
-
-If `sumStartsWithNEquals` is true then when you type `\sum`, `\prod`, or
-`\coprod`, the lower limit starts out with `n=`, e.g. you get the LaTeX
-`\sum_{n=}^{ }`, rather than empty by default.
-
-`supSubsRequireOperand` disables typing of superscripts and subscripts when
-there's nothing to the left of the cursor to be exponentiated or subscripted.
-Averts the especially confusing typo `x^^2`, which looks much like `x^2`.
-
-`charsThatBreakOutOfSupSub` sets the chars that when typed, "break out" of
-superscripts and subscripts: for example, typing `x^2n+y` normally results in
-the LaTeX `x^{2n+y}`, you have to hit Down or Tab (or Space if
-`spaceBehavesLikeTab` is true) to move the cursor out of the exponent and get
-the LaTeX `x^{2n}+y`; this option makes `+` "break out" of the exponent and
-type what you expect. Problem is, now you can't just type `x^n+m` to get the
-LaTeX `x^{n+m}`, you have to type `x^(n+m` and delete the paren or something.
-(Doesn't apply to the first character in a superscript or subscript, so typing
-`x^-6` still results in `x^{-6}`.)
-
-`autoCommands`, a space-delimited list of LaTeX control words (no backslash,
-letters only, min length 2), defines the (default empty) set of "auto-commands",
-commands automatically rendered by just typing the letters without typing a
-backslash first.
-
-`autoOperatorNames`, a list of the same form (space-delimited letters-only each
-length>=2), and overrides the set of operator names that automatically become
-non-italicized when typing the letters without typing a backslash first, like
-`sin`, `log`, etc. Defaults to the LaTeX built-in operator names ([Section
-3.17 of the Short Math Guide][3.17]) plus some missing trig operators like
-`sech`, `arcsec`, `arsinh`, etc.
-
-[3.17]: http://tinyurl.com/jm9okjc
-
-`substituteTextarea`, a function that creates a focusable DOM element, called
-when setting up a math field. It defaults to `<textarea autocorrect=off .../>`,
-but for example, Desmos substitutes `<span tabindex=0></span>` on iOS to
-suppress the built-in virtual keyboard in favor of a custom math keypad that
-calls the MathQuill API. Unfortunately there's no universal [check for a virtual
-keyboard][StackOverflow], you can't even [detect a touchscreen][stucox] (notably
-[Modernizr gave up][Modernizr]) and even if you could, Windows 8 and ChromeOS
-devices have both physical keyboards and touchscreens and you can connect
-physical keyboards to iOS and Android devices with Bluetooth, so touchscreen !=
-virtual keyboard. Desmos currently sniffs the user agent for iOS, so Bluetooth
-keyboards just don't work in Desmos on iOS, the tradeoffs are up to you.
-
-[StackOverflow]: http://stackoverflow.com/q/2593139/362030
-[stucox]: http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
-[Modernizr]: https://github.com/Modernizr/Modernizr/issues/548
-
-Supported handlers:
-- `moveOutOf`, `deleteOutOf`, and `selectOutOf` are called with `dir` and the
-  math field API object as arguments
-- `upOutOf`, `downOutOf`, `enter`, and `edit` are called with just the API
-  object as the argument
-
-The `*OutOf` handlers are called when Left/Right/Up/Down/Backspace/Del/
-Shift-Left/Shift-Right is pressed but the cursor is at the left/right/top/bottom
-edge and so nothing happens within the math field. For example, when the cursor
-is at the left edge, pressing the Left key causes the `moveOutOf` handler (if
-provided) to be called with `MQ.L` and the math field API object as arguments,
-and Backspace causes `deleteOutOf` (if provided) to be called with `MQ.L` and
-the API object as arguments, etc.
-
-The `enter` handler is called whenever Enter is pressed.
-
-The `edit` handler is called when the contents of the field might have been
-changed by stuff being typed, or deleted, or written with the API, etc.
-(Deprecated aliases: `edited`, `reflow`.)
-
-Handlers are always called directly on the `handlers` object passed in,
-preserving the `this` value, so you can do stuff like:
-```js
-var MathList = P(function(_) {
-  _.init = function() {
-    this.maths = [];
-    this.el = ...
-  };
-  _.add = function() {
-    var math = MQ.MathField($('<span/>')[0], { handlers: this });
-    $(math.el()).appendTo(this.el);
-    math.data.i = this.maths.length;
-    this.maths.push(math);
-  };
-  _.moveOutOf = function(dir, math) {
-    var adjacentI = (dir === MQ.L ? math.data.i - 1 : math.data.i + 1);
-    var adjacentMath = this.maths[adjacentI];
-    if (adjacentMath) adjacentMath.focus().moveToDirEnd(-dir);
-  };
-  ...
-});
-```
-Of course you can always ignore the last argument, like when the handlers close
-over the math field:
-```js
-var latex = '';
-var mathField = MQ.MathField($('#mathfield')[0], {
-  handlers: {
-    edit: function() { latex = mathField.latex(); },
-    enter: function() { submitLatex(latex); }
-  }
-});
-```
-
-**A Note On Changing Colors:**
-
-To change the foreground color, don't just set the `color`, also set
-the `border-color`, because the cursor, fraction bar, and square root
-overline are all borders, not text. (Example below.)
-
-Due to technical limitations of IE8, if you support it, and want to give
-a MathQuill editable a background color other than white, and support
-square roots, parentheses, square brackets, or curly braces, you will
-need to, in addition to of course setting the background color on the
-editable itself, set it on elements with class `mq-matrixed`, and then set
-a Chroma filter on elements with class `mq-matrixed-container`.
-
-For example, to style as white-on-black instead of black-on-white:
-
-    #my-math-input {
-      color: white;
-      border-color: white;
-      background: black;
-    }
-    #my-math-input .mq-matrixed {
-      background: black;
-    }
-    #my-math-input .mq-matrixed-container {
-      filter: progid:DXImageTransform.Microsoft.Chroma(color='black');
-    }
-
-(This is because almost all math rendered by MathQuill has a transparent
-background, so for them it's sufficient to set the background color on
-the editable itself. The exception is, IE8 doesn't support CSS
-transforms, so MathQuill uses a matrix filter to stretch parens etc,
-which [anti-aliases wrongly without an opaque background][Transforms],
-so MathQuill defaults to white.)
-
-[Transforms]: http://github.com/mathquill/mathquill/wiki/Transforms
-
-## Building and Testing
-
-To hack on MathQuill, you're gonna want to build and test the source files
-you edit. In addition to `make`, MathQuill uses some build tools written on
-[Node](http://nodejs.org/#download), so you will need to install that before
-running `make`. (Once it's installed, `make` automatically does `npm install`,
-installing the necessary build tools.)
-
-- `make` builds `build/mathquill.{css,js,min.js}`
-- `make dev` won't try to minify MathQuill (which can be annoyingly slow)
-- `make test` builds `mathquill.test.js` (used by `test/unit.html`) and also
-  doesn't minify
-- `make basic` builds `mathquill-basic.{js,min.js,css}` and
-  `font/Symbola-basic.{eot,ttf}`; serve and load them instead for a stripped-
-  down version of MathQuill for basic mathematics, without advanced LaTeX
-  commands. Specifically, it doesn't let you type LaTeX backslash commands
-  with `\` or text blocks with `$`, and also won't render any LaTeX commands
-  that can't by typed without `\`. The resulting JS is only somewhat smaller,
-  but the font is like 100x smaller. (TODO: reduce full MathQuill's font size.)
-
-## Understanding The Source Code
-
-All the CSS is in `src/css`. Most of it's pretty straightforward, the choice of
-font isn't settled, and fractions are somewhat arcane, see the Wiki pages
-["Fonts"](http://github.com/mathquill/mathquill/wiki/Fonts) and
-["Fractions"](http://github.com/mathquill/mathquill/wiki/Fractions).
-
-All the JavaScript that you actually want to read is in `src/`, `build/` is
-created by `make` to contain the same JS cat'ed and minified.
-
-There's a lot of JavaScript but the big picture isn't too complicated, there's 2
-thin layers sandwiching 2 broad but modularized layers:
-
-- At the highest level, the public API is a thin wrapper around calls to:
-- "services" on the "controller", which sets event listeners that call:
-- methods on "commands" in the "edit tree", which call:
-- tree- and cursor-manipulation methods, at the lowest level, to move the
-  cursor or edit the tree or whatever.
-
-More specifically:
-
-(In comments and internal documentation, `::` means `.prototype.`.)
-
-- At the lowest level, the **edit tree** of JS objects represents math and text
-  analogously to how [the HTML DOM][] represents a web page.
-    + (Old docs variously called this the "math tree", the "fake DOM", or some
-      combination thereof, like the "math DOM".)
-    + `tree.js` defines base classes of objects relating to the tree.
-    + `cursor.js` defines objects representing the cursor and a selection of
-      math or text, with associated HTML elements.
-- Interlude: a **feature** is a unit of publicly exposed functionality, either
-  by the API or interacted with by typists. Following are the 2 disjoint
-  categories of features.
-- A **command** is a thing you can type and edit like a fraction, square root,
-  or "for all" symbol, &forall;. They are implemented as a class of node objects
-  in the edit tree, like `Fraction`, `SquareRoot`, or `VanillaSymbol`.
-    + Each command has an associated **control sequence** (as termed by Knuth;
-      in the LaTeX community, commonly called a "macro" or "command"), a token
-      in TeX and LaTeX syntax consisting of a backslash then any single
-      character or string of letters, like `\frac` or <code>\ </code>. Unlike
-      loose usage in the LaTeX community, where `\ne` and `\neq` (which print
-      the same symbol, &ne;) might or might not be considered the same command,
-      in the context of MathQuill they are considered different "control
-      sequences" for the same "command".
-- A **service** is a feature that applies to all or many commands, like typing,
-  moving the cursor around, LaTeX exporting, LaTeX parsing. Note that each of
-  these varies by command (the cursor goes in a different place when moving into
-  a fraction vs into a square root, they export different LaTeX, etc), cue
-  polymorphism: services define methods on the controller that call methods on
-  nodes in the edit tree with certain contracts, such as a controller method
-  called on initialization to set listeners for keyboard events, that when the
-  Left key is pressed, calls `.moveTowards` on the node just left of the cursor,
-  dispatching on what kind of command the node is (`Fraction::moveTowards` and
-  `SquareRoot::moveTowards` can insert the cursor in different places).
-    + `controller.js` defines the base class for the **controller**, which each
-      math field or static math instance has one of, and to which each service
-      adds methods.
-- `publicapi.js` defines the global `MathQuill.getInterface()` function, the
-  `MQ.MathField()` etc. constructors, and the API objects returned by
-  them. The constructors, and the API methods on the objects they return, call
-  appropriate controller methods to initialize and manipulate math field and
-  static math instances.
-
-[the HTML DOM]: http://www.w3.org/TR/html5-author/introduction.html#a-quick-introduction-to-html
-
-Misc.:
-
-`intro.js` defines some simple sugar for the idiomatic JS classes used
-throughout MathQuill, plus some globals and opening boilerplate.
-
-Classes are defined using [Pjs][], and the variable `_` is used by convention as
-the prototype.
-
-[pjs]: https://github.com/jneen/pjs
-
-`services/*.util.js` files are unimportant to the overall architecture, you can
-ignore them until you have to deal with code that is using them.
+Some older documentation still exists on the [Wiki](https://github.com/mathquill/mathquill/wiki).
 
 ## Open-Source License
 
 The Source Code Form of MathQuill is subject to the terms of the Mozilla Public
-License, v. 2.0: http://mozilla.org/MPL/2.0/
+License, v. 2.0: [http://mozilla.org/MPL/2.0/](http://mozilla.org/MPL/2.0/)
 
 The quick-and-dirty is you can do whatever if modifications to MathQuill are in
 public GitHub forks. (Other ways to publicize modifications are also fine, as
-are private use modifications. See also: [MPL 2.0 FAQ][])
-
-[MPL 2.0 FAQ]: https://www.mozilla.org/en-US/MPL/2.0/FAQ/
+are private use modifications. See also: [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/))
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..f93227b
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,201 @@
+# Okay so maybe everyone else already knows all this, but it took some time
+# for Michael and I [Han] to really see how everything fits together.
+#
+# Basically, what we're doing here is automated browser testing, so CircleCI
+# handles the automation, and Sauce Labs handles the browser testing.
+# Specifically, Sauce Labs offers a REST API to run tests in browsers in VMs,
+# and CircleCI can be configured to listen for git pushes and run local
+# servers and call out to REST APIs to test against these local servers.
+#
+# The flow goes like this:
+#   - CircleCI notices/is notified of a git push
+#   - they pull and checkout and magically know to install dependencies and shit
+#       + https://circleci.com/docs/manually/
+#   - their magic works fine for MathQuill's dependencies but to run the tests,
+#     it foolishly runs `make test`, what an inconceivable mistake
+#   - that's where we come in: `circle.yml` lets us override the test script.
+#       + https://circleci.com/docs/configuration/
+#   - our `circle.yml` first installs and runs a tunnel to Sauce Labs
+#   - and runs `make server`
+#   - then it calls out to Sauce Labs' REST API to open a browser that reaches
+#     back through the tunnel to access the unit test page on the local server
+#       + > Sauce Connect allows you to run a test server within the CircleCI
+#         > build container and expose it it (using a URL like `localhost:8080`)
+#         > to Sauce Labs’ browsers.
+#
+#         https://circleci.com/docs/browser-testing-with-sauce-labs/
+#
+#   - boom testing boom
+
+
+# this file is based on https://github.com/circleci/sauce-connect/blob/a65e41c91e02550ce56c75740a422bebc4acbf6f/circle.yml
+# via https://circleci.com/docs/browser-testing-with-sauce-labs/
+
+dependencies:
+  cache_directories:
+    - ~/sauce-connect
+  pre:
+    - "test $SAUCE_USERNAME && test $SAUCE_ACCESS_KEY
+       # Sauce Labs credentials required. Sign up here: https://saucelabs.com/opensauce/"
+    - ? |-
+        {
+          mkdir -p ~/sauce-connect
+          cd ~/sauce-connect
+          if [ -x sc-*-linux/bin/sc ]; then
+            echo Using cached sc-*-linux/bin/sc
+          else
+            time wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
+            time tar -xzf sc-latest-linux.tar.gz
+          fi
+          # Sauce Connect randomly fails so try twice
+          time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY --readyfile ~/sauce_is_ready \
+            || time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY --readyfile ~/sauce_is_ready \
+            || echo ERROR > ~/sauce_is_ready
+        } >$CIRCLE_ARTIFACTS/sauce-connect.log 2>&1
+      :
+        background: true
+
+test:
+  override:
+    # Sauce can connect to Safari on ports 3000, 4000, 7000, and 8000. Edge needs port 7000 or 8000.
+    # https://david263a.wordpress.com/2015/04/18/fixing-safari-cant-connect-to-localhost-issue-when-using-sauce-labs-connect-tunnel/
+    # https://support.saucelabs.com/customer/portal/questions/14368823-requests-to-localhost-on-microsoft-edge-are-failing-over-sauce-connect
+    - PORT=8000 make server >$CIRCLE_ARTIFACTS/make_server.log 2>&1:
+        background: true
+
+    # CircleCI expects test results to be reported in an JUnit/xUnit-style XML
+    # file:
+    #   https://circleci.com/docs/test-metadata/
+    # Our unit tests are in a browser, so they can't write to a file, and Sauce
+    # apparently truncates custom data in their test result reports, so instead
+    # we POST to this trivial Node server on localhost:9000 that writes the
+    # body of any POST request to $CIRCLE_TEST_REPORTS/mocha/xunit.xml
+    - mkdir -p $CIRCLE_TEST_REPORTS/mocha
+    - ? |-
+        node << 'EOF' >$CIRCLE_TEST_REPORTS/mocha/xunit.xml \
+                      2>$CIRCLE_ARTIFACTS/mocha-test-report.log
+          require('http').createServer(function(req, res) {
+            res.setHeader('Access-Control-Allow-Origin', '*');
+            req.pipe(process.stdout);
+            req.on('end', res.end.bind(res));
+          })
+          .listen(9000);
+          console.error('listening on http://0.0.0.0:9000/');
+        EOF
+      :
+        background: true
+
+    # Wait for tunnel to be ready (`make server` and the trivial Node server
+    # are much faster, no need to wait for them)
+    - while [ ! -e ~/sauce_is_ready ]; do sleep 1; done; test "$(<~/sauce_is_ready)" != ERROR
+
+    # Start taking screenshots in the background while the unit tests are running
+    - ? |-
+        {
+          time { test -d node_modules/wd || npm install wd; }
+          time node script/screenshots.js http://localhost:8000/test/visual.html \
+            && touch ~/screenshots_are_ready || echo ERROR > ~/screenshots_are_ready:
+        } >$CIRCLE_ARTIFACTS/screenshots.log 2>&1
+      :
+        background: true
+
+    # Run in-browser unit tests, based on:
+    #   https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
+    # "build" and "tag" parameters from:
+    #   https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-TestAnnotation
+    - |-
+      curl -i https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests \
+           -X POST \
+           -u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
+           -H 'Content-Type: application/json' \
+           -d '{
+                 "build": "'$(git rev-parse HEAD)'",
+                 "tags": [
+                   "after-v'$(node -p 'require("./package.json").version')'",
+                   "circle-ci"
+                 ],
+                 "framework": "mocha",
+                 "url": "http://localhost:8000/test/unit.html?post_xunit_to=http://localhost:9000",
+                 "platforms": [["", "Chrome", ""]]
+      }' | tee js-tests
+
+    # Wait for tests to finish:
+    #
+    #   > Make the request multiple times as the tests run until the response
+    #   > contains `completed: true` to the get the final results.
+    #
+    #   https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
+    - |-
+      while true  # Bash has no do...while >:(
+      do
+        sleep 5
+        curl -i https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests/status \
+             -X POST \
+             -u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
+             -H 'Content-Type: application/json' \
+             -d "$(tail -1 js-tests)" \
+        | tee status
+        tail -1 status > status.json
+        # deliberately do `... != false` rather than `... == true`
+        # because unexpected values should break rather than infinite loop
+        [ "$(node -p 'require("./status.json").completed')" != false ] && break
+      done
+
+    # Wait for screenshots to be ready
+    - while [ ! -e ~/screenshots_are_ready ]; do sleep 1; done; test "$(<~/screenshots_are_ready)" != ERROR:
+        timeout: 300
+
+    # Stitch together images
+    # TODO: Split this into multiple yaml lines. I (Michael)
+    #       niavely tried to split this into mutiple yaml lines
+    #       but was unsucessful in doing do.
+    - |-
+      img_dir=$CIRCLE_ARTIFACTS/imgs/
+      for x in $(ls $img_dir)
+      do
+        convert $img_dir/$x/*.png -append $img_dir/$x.png
+      done
+
+      # Remove all directories in $CIRCLE_ARTIFACTS/img
+      # Currently the pieces aren't kept around. If it's
+      # desirable to keep them around, we should use
+      #    cp -r $dir $CIRCLE_ARTIFACTS/img_pieces
+      # The reason the image pieces aren't currently kept
+      # around is that it was leading to a problem. Specifically,
+      # when we get the previous images, we niavely grab any *.png,
+      # including the pieces images. This compounded so that each
+      # iteration of a test run would have all of the images from
+      # the previous test run plus whichever new images were generated.
+      rm -R -- $img_dir/*/
+
+      # Install utility we need
+      npm install -g json
+
+      # Download the latest mathquill artifacts.
+      curl $(curl https://circleci.com/api/v1/project/mathquill/mathquill/latest/artifacts \
+              | json -a url pretty_path -d '\n\t' \
+              | grep '\.png$' \
+              | grep -v 'PREV' \
+              | sed "s:\$CIRCLE_ARTIFACTS/imgs/:-o $img_dir/PREV_:")
+
+      # Generate image diffs.
+      cd $img_dir
+      for file in $(ls PINNED*); do
+        prev=PREV_$file
+        metric_diff=$(compare -metric AE -compose src $prev $file raw_diff.png)
+        composite -alpha on raw_diff.png $prev DIFF_$file
+      done
+
+      for file in $(ls EVERGREEN*); do
+        prev=$(ls PREV_$file*)
+        metric_diff=$(compare -metric AE -compose src $prev $file raw_diff.png)
+        composite -alpha on raw_diff.png $prev DIFF_$file
+      done
+
+      rm raw_diff.png
+
+    # finally, complain to Circle CI if there were nonzero test failures
+    - |-
+      [ "$(node -p 'require("./status.json")["js tests"][0].result.failures')" == 0 ]
+  post:
+    - killall --wait sc; true  # wait for Sauce Connect to close the tunnel; ignore errors since it's just cleanup
diff --git a/docs/Api_Methods.md b/docs/Api_Methods.md
new file mode 100644
index 0000000..b7db00c
--- /dev/null
+++ b/docs/Api_Methods.md
@@ -0,0 +1,245 @@
+# API Methods
+
+To use the MathQuill API, first get the latest version of the interface:
+
+```js
+var MQ = MathQuill.getInterface(2);
+```
+
+By default, MathQuill overwrites the global `MathQuill` variable when loaded. If you do not want this behavior, you can use `.noConflict()` ([similar to `jQuery.noConflict()`](http://api.jquery.com/jQuery.noConflict)):
+
+```html
+<script src="/path/to/first-mathquill.js"></script>
+<script src="/path/to/second-mathquill.js"></script>
+<script>
+var secondMQ = MathQuill.noConflict().getInterface(2);
+secondMQ.MathField(...);
+
+var firstMQ = MathQuill.getInterface(2);
+firstMQ.MathField(...);
+</script>
+```
+
+This lets different copies of MathQuill each power their own math fields, but using different copies on the same DOM element won't work. `.noConflict()` is primarily intended to help you reduce globals.
+
+
+
+# Constructors
+
+## MQ.StaticMath(html_element)
+
+Creates a non-editable MathQuill initialized with the contents of the HTML element and returns a [StaticMath object](#mathquill-base-methods).
+
+If the given element is already a static math instance, this will return a new StaticMath object with the same `.id`. If the element is a different type of MathQuill, this will return `null`.
+
+## MQ.MathField(html_element, [ config ])
+
+Creates an editable MathQuill initialized with the contents of the HTML element and returns a [MathField object](#editable-mathfield-methods).
+
+If the given element is already an editable math field, this will return a new editable MathField object with the same `.id`. If the element is a different type of MathQuill, this will return `null`.
+
+## \MathQuillMathField LaTeX command
+
+`\MathQuillMathField` can be used to embed editable math fields inside static math, like:
+
+```html
+<span id="fill-in-the-blank">\sqrt{ \MathQuillMathField{x}^2 + \MathQuillMathField{y}^2 }</span>
+<script>
+  var fillInTheBlank = MQ.StaticMath(document.getElementById('fill-in-the-blank'));
+  fillInTheBlank.innerFields[0].latex() // => 'x'
+  fillInTheBlank.innerFields[1].latex() // => 'y'
+</script>
+```
+
+As you can see, they can be accessed on the StaticMath object via `.innerFields`.
+
+## MQ(html_element)
+
+`MQ` itself is a function that takes an HTML element and, if it's the root
+HTML element of a static math or math field, returns an API object for it
+(if not, `null`):
+
+```js
+MQ(mathFieldSpan) instanceof MQ.MathField // => true
+MQ(otherSpan) // => null
+```
+
+## MQ.config(config)
+
+Updates the global [configuration options](Config.md) (which can be overridden on a per-field basis).
+
+
+
+# Comparing MathFields
+
+## Checking Type
+```js
+var staticMath = MQ.StaticMath(staticMathSpan);
+mathField instanceof MQ.StaticMath // => true
+mathField instanceof MQ // => true
+mathField instanceof MathQuill // => true
+
+var mathField = MQ.MathField(mathFieldSpan);
+mathField instanceof MQ.MathField // => true
+mathField instanceof MQ.EditableField // => true
+mathField instanceof MQ // => true
+mathField instanceof MathQuill // => true
+```
+
+## Comparing IDs
+API objects for the same MathQuill instance have the same `.id`, which will always be a unique truthy primitive value that can be used as an object key (like an ad hoc [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)):
+
+```js
+MQ(mathFieldSpan).id === mathField.id // => true
+
+var setOfMathFields = {};
+setOfMathFields[mathField.id] = mathField;
+MQ(mathFieldSpan).id in setOfMathFields // => true
+staticMath.id in setOfMathFields // => false
+```
+
+## Data Object
+Similarly, API objects for the same MathQuill instance share a `.data` object (which can be used like an ad hoc [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) or [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)):
+
+```js
+MQ(mathFieldSpan).data === mathField.data // => true
+mathField.data.foo = 'bar';
+MQ(mathFieldSpan).data.foo // => 'bar'
+```
+
+
+
+# MathQuill base methods
+
+The following are methods that every MathQuill object has. These are the only methods that static math instances have and a subset of the methods that editable fields have.
+
+## .revert()
+
+Any element that has been turned into a MathQuill instance can be reverted:
+```html
+<span id="revert-me" class="mathquill-static-math">
+  some <code>HTML</code>
+</span>
+```
+```js
+mathfield.revert().html(); // => 'some <code>HTML</code>'
+```
+
+## .reflow()
+
+MathQuill uses computed dimensions, so if they change (because an element was mathquill-ified before it was in the visible HTML DOM, or the font size changed), then you'll need to tell MathQuill to recompute:
+
+```js
+var mathFieldSpan = $('<span>\\sqrt{2}</span>');
+var mathField = MQ.MathField(mathFieldSpan[0]);
+mathFieldSpan.appendTo(document.body);
+mathField.reflow();
+```
+
+## .el()
+
+Returns the root HTML element.
+
+## .latex()
+
+Returns the contents as LaTeX.
+
+## .latex(latex_string)
+
+This will render the argument as LaTeX in the MathQuill instance.
+
+
+
+# Editable MathField methods
+
+Editable math fields have all of the [above](#mathquill-base-methods) methods in addition to the ones listed here.
+
+## .focus()
+
+Puts the focus on the editable field.
+
+## .blur()
+
+Removes focus from the editable field.
+
+## .write(latex_string)
+
+Write the given LaTeX at the current cursor position. If the cursor does not have focus, writes to last position the cursor occupied in the editable field.
+
+```javascript
+mathField.write(' - 1'); // writes ' - 1' to mathField at the cursor position
+```
+
+## .cmd(latex_string)
+
+Enter a LaTeX command at the current cursor position or with the current selection. If the cursor does not have focus, it writes it to last position the cursor occupied in the editable field.
+
+```javascript
+mathField.cmd('\\sqrt'); // writes a square root command at the cursor position
+```
+
+## .select()
+
+Selects the contents (just like [on `textarea`s](http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-48880622) and [on `input`s](http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-34677168)).
+
+## .clearSelection()
+
+Clears the selection.
+
+## .moveToLeftEnd(), .moveToRightEnd()
+
+Move the cursor to the left/right end of the editable field, respectively. These are shorthand for [`.moveToDirEnd(L/R)`](#movetodirenddirection), respectively.
+
+## .movetoDirEnd(direction)
+
+Moves the cursor to the end of the mathfield in the direction specified. The direction can be one of `MQ.L` or `MQ.R`. These are constants, where `MQ.L === -MQ.R` and vice versa. This function may be easier to use than [moveToLeftEnd or moveToRightEnd](#movetoleftend-movetorightend) if used in the [`moveOutOf` handler](Config.md#outof-handlers).
+
+```javascript
+var config = {
+  handlers: {
+    moveOutOf: function(direction) {
+      nextMathFieldOver.movetoDirEnd(-direction);
+    }
+  }
+});
+```
+
+## .keystroke(keys)
+
+Simulates keystrokes given a string like `"Ctrl-Home Del"`, a whitespace-delimited list of [key inputs](http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes) with optional prefixes.
+
+```javascript
+mathField.keystroke('Shift-Left'); // Selects character before the current cursor position
+```
+
+## .typedText(text)
+
+Simulates typing text, one character at a time from where the cursor currently is. This is supposed to be identical to what would happen if a user were typing the text in.
+
+```javascript
+// Types part of the demo from mathquill.com without delays between keystrokes
+mathField.typedText('x=-b\\pm \\sqrt b^2 -4ac');
+```
+
+## .config(new_config)
+
+Changes the [configuration](Config.md) of just this math field.
+
+## .dropEmbedded(pageX, pageY, options) **[ᴇxᴘᴇʀɪᴍᴇɴᴛᴀʟ](#note-on-experimental-features)**
+
+Insert a custom embedded element at the given coordinates, where `options` is an object like:
+```js
+{
+  htmlString: '<span class="custom-embed"></span>',
+  text: function() { return 'custom_embed'; },
+  latex: function() { return '\\customEmbed'; }
+}
+```
+
+## .registerEmbed('name', function(id){ return options; }) **[ᴇxᴘᴇʀɪᴍᴇɴᴛᴀʟ](#note-on-experimental-features)**
+
+Allows MathQuill to parse custom embedded objects from latex, where `options` is an object like the one defined above in `.dropEmbedded()`. This will parse the following latex into the embedded object you defined: `\embed{name}[id]}`.
+
+## Note on Experimental Features
+
+Methods marked as experimental may be altered drastically or removed in future versions. They may also receive less maintenance than other non-experimental features.
diff --git a/docs/Code_of_Conduct.md b/docs/Code_of_Conduct.md
new file mode 100644
index 0000000..7d01333
--- /dev/null
+++ b/docs/Code_of_Conduct.md
@@ -0,0 +1,21 @@
+## Code of Conduct
+
+### Quick Version
+
+The MathQuill project and its supporting communication channels, including GitHub, the MathQuill Slack, and the #mathquill IRC channel, are dedicated to providing a harassment-free experience for everyone, regardless of gender, gender identity and expression, sexual orientation, disability, physical appearance, age, body size, race, or religion. We do not tolerate harassment of participants in any form. Sexual language and imagery is not appropriate without discussion and pre-approval from a moderator. Participants violating these rules may be sanctioned or expelled from the group at the discretion of [@laughinghan], [@stufflebear], or another moderator.
+
+### Additional Details
+Harassment includes offensive verbal comments related to gender, gender identity and expression, sexual orientation, disability, physical appearance, age, body size, race, religion, sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of discussion, inappropriate contact, and unwelcome sexual attention. Disclosure of another person’s contact information including legal name or residence without their permission is also unacceptable behavior.
+ 
+Participants asked to stop any harassing or inappropriate behavior are expected to comply immediately.
+ 
+If a participant engages in harassing behavior, [@laughinghan], [@stufflebear], or another moderator may take any action they deem appropriate, including warning the offender or permanent expulsion from the channel.
+ 
+If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact us at <coc@mathquill.com> or any moderator you feel most comfortable with as soon as possible. They will address the issue as soon as they are able to.
+ 
+### Attribution
+
+CoC adopted from http://pastebin.com/UV8snBsV, which was adapted from http://tinyurl.com/lhc-c-of-c, which was further adapted from a private channel CoC written by @juliepagano.
+
+[@laughinghan]: https://github.com/laughinghan
+[@stufflebear]: https://github.com/stufflebear
diff --git a/docs/Config.md b/docs/Config.md
new file mode 100644
index 0000000..294248c
--- /dev/null
+++ b/docs/Config.md
@@ -0,0 +1,163 @@
+# Setting Configuration
+
+The configuration options object is of the following form:
+```js
+{
+  spaceBehavesLikeTab: true,
+  leftRightIntoCmdGoes: 'up',
+  restrictMismatchedBrackets: true,
+  sumStartsWithNEquals: true,
+  supSubsRequireOperand: true,
+  charsThatBreakOutOfSupSub: '+-=<>',
+  autoSubscriptNumerals: true,
+  autoCommands: 'pi theta sqrt sum',
+  autoOperatorNames: 'sin cos',
+  substituteTextarea: function() {
+    return document.createElement('textarea');
+  },
+  handlers: {
+    edit: function(mathField) { ... },
+    upOutOf: function(mathField) { ... },
+    moveOutOf: function(dir, mathField) { if (dir === MQ.L) ... else ... }
+  }
+}
+```
+
+You can configure an editable math field by passing an options argument as the second argument to [the constructor (`MQ.MathField(html_element, config)`)](Api_Methods.md#mqmathfieldhtml_element-config), or by [calling `.config()` on the math field (`mathField.config(new_config)`)](Api_Methods.md#confignew_config).
+
+Global defaults may be set with [`MQ.config(global_config)`](Api_Methods.md#mqconfigconfig).
+
+
+
+# Configuration Options
+
+## spacesBehavesLikeTab
+
+If `spaceBehavesLikeTab` is true the keystrokes `{Shift-,}Spacebar` will behave like `{Shift-,}Tab` escaping from the current block (as opposed to the default behavior of inserting a Space character).
+
+The animated demo on <mathquill.com> has this behavior.
+
+## leftRightIntoCmdGoes
+
+This allows you to change the way the left and right keys move the cursor when there are items of different height, like fractions.
+
+By default, the Left and Right keys move the cursor through all possible cursor positions in a particular order: right into a fraction puts the cursor at the left end of the numerator, right out of the numerator puts the cursor at the left end of the denominator, and right out of the denominator puts the cursor to the right of the fraction. Symmetrically, left into a fraction puts the cursor at the right end of the denominator, etc.
+
+If instead you want right to always visually go right, and left to always go visually left, you can set `leftRightIntoCmdGoes` to `'up'` or `'down'` so that left and right go up or down (respectively) into commands. For example, `'up'` means that left into a fraction goes up into the numerator and right out of the numerator skips the denominator and puts the cursor to the right of the fraction. This behavior can be seen in the [Desmos calculator](https://www.desmos.com/calculator). If this property is set to `'down'` instead, the numerator is harder to navigate to, like in the Mac OS X built-in app Grapher.
+
+## restrictMismatchedBrackets
+
+If `restrictMismatchedBrackets` is true then you can type `[a,b)` and `(a,b]`, but if you try typing `[x}` or `\langle x|`, you'll get `[{x}]` or `\langle|x|\rangle` instead. This lets you type `(|x|+1)` normally; otherwise, you'd get `\left( \right| x \left| + 1 \right)`.
+
+## sumStartsWithNEquals
+
+If `sumStartsWithNEquals` is true then when you type `\sum`, `\prod`, or `\coprod`, the lower limit starts out with `n=`, e.g. you get the LaTeX `\sum_{n=}^{ }`, rather than empty by default.
+
+## supSubsRequireOperand
+
+`supSubsRequireOperand` disables typing of superscripts and subscripts when there's nothing to the left of the cursor to be exponentiated or subscripted. Prevents the especially confusing typo `x^^2`, which looks much like `x^2`.
+
+## charsThatBreakOutOfSupSub
+
+`charsThatBreakOutOfSupSub` takes a string of the chars that when typed, "break out" of superscripts and subscripts.
+
+Normally, to get out of a superscript or subscript, a user has to navigate out of it with the directional keys, a mouse click, tab, or Space if [`spaceBehavesLikeTab`](#spacesbehavesliketab) is true. For example, typing `x^2n+y` normally results in the LaTeX `x^{2n+y}`. If you wanted to get the LaTeX `x^{2n}+y`, the user would have to manually move the cursor out of the exponent.
+
+If this option was set to `'+-'`, `+` and `-` would "break out" of the exponent. This doesn't apply to the first character in a superscript or subscript, so typing `x^-6` still results in `x^{-6}`. The downside to setting this option is that in order to type `x^{n+m}`, a workaround like typing `x^(n+m` and then deleting the `(` is required.
+
+## autoCommands
+
+`autoCommands` defines the set of commands automatically rendered by just typing the letters without typing a backslash first.
+
+This takes a string formatted as a space-delimited list of LaTeX commands. Each LaTeX command must be at least letters only with a minimum length of 2 characters.
+
+For example, with `autoCommands` set to `'pi theta'`, the word 'pi' automatically converts to the pi symbol and the word 'theta' automatically converts to the theta symbol.
+
+## autoOperatorNames
+
+`autoOperatorNames` overrides the set of operator names that automatically become non-italicized when typing the letters without typing a backslash first, like `sin`, `log`, etc.
+
+This defaults to the LaTeX built-in operator names ([Section 3.17 of the Short Math Guide](http://tinyurl.com/jm9okjc)) with additional trig operators like `sech`, `arcsec`, `arsinh`, etc. If you want some of these italicized after setting this property, you will have to add them to the list.
+
+Just like [`autoCommands`](#autocommands) above, this takes a string formatted as a space-delimited list of LaTeX commands.
+
+## substituteTextarea
+
+`substituteTextarea` is a function that creates a focusable DOM element that is called when setting up a math field. Overwriting this may be useful for hacks like suppressing built-in virtual keyboards. It defaults to `<textarea autocorrect=off .../>`.
+
+For example, [Desmos](https://www.desmos.com/calculator) substitutes `<span tabindex=0></span>` on iOS to suppress the built-in virtual keyboard in favor of a custom math keypad that calls the MathQuill API. Unfortunately there's no universal [check for a virtual keyboard](http://stackoverflow.com/q/2593139/362030) or [way to detect a touchscreen](http://www.stucox.com/blog/you-cant-detect-a-touchscreen/), and even if you could, a touchscreen ≠ virtual keyboard (Windows 8 and ChromeOS devices have both physical keyboards and touchscreens and iOS and Android devices can have Bluetooth keyboards). Desmos currently sniffs the user agent for iOS, so Bluetooth keyboards just don't work in Desmos on iOS. The tradeoffs are up to you.
+
+
+
+# Handlers
+
+Handlers are called after a specified event. They are called directly on the `handlers` object passed in, preserving the `this` value, so you can do stuff like:
+```js
+var MathList = P(function(_) {
+  _.init = function() {
+    this.maths = [];
+    this.el = ...
+  };
+  _.add = function() {
+    var math = MQ.MathField($('<span/>')[0], { handlers: this });
+    $(math.el()).appendTo(this.el);
+    math.data.i = this.maths.length;
+    this.maths.push(math);
+  };
+  _.moveOutOf = function(dir, math) {
+    var adjacentI = (dir === MQ.L ? math.data.i - 1 : math.data.i + 1);
+    var adjacentMath = this.maths[adjacentI];
+    if (adjacentMath) adjacentMath.focus().moveToDirEnd(-dir);
+  };
+  ...
+});
+```
+
+It's common to just ignore the last argument, like if the handlers close over the math field:
+```js
+var latex = '';
+var mathField = MQ.MathField($('#mathfield')[0], {
+  handlers: {
+    edit: function() { latex = mathField.latex(); },
+    enter: function() { submitLatex(latex); }
+  }
+});
+```
+
+## *OutOf handlers
+
+`.moveOutOf(direction, mathField)`, `.deleteOutOf(direction, mathField)`, `.selectOutOf(direction, mathField)`, `.upOutOf(mathField)`, `.downOutOf(mathField)`
+
+The `*OutOf` handlers are called when a cursor movement would cause the cursor to leave the MathQuill mathField. These let you integrate cursor movement seamlessly between your code and MathQuill. For example, when the cursor is at the right edge, pressing the Right key causes the `moveOutOf` handler to be called with `MQ.R` and the math field API object. Pressing Backspace causes `deleteOutOf` to be called with `MQ.L` and the API object.
+
+## enter(mathField)
+
+Called whenever Enter is pressed.
+
+## edit(mathField)
+
+This is called when the contents of the field might have been changed. This will be called with any edit, such as something being typed, deleted, or written with the API. Note that this may be called when nothing has actually changed.
+
+Deprecated aliases: `edited`, `reflow`.
+
+# Changing Colors
+
+To change the foreground color, set both `color` and the `border-color` because some MathQuill symbols are implemented with borders instead of pure text.
+
+For example, to style as white-on-black instead of black-on-white use:
+
+    #my-math-input {
+      color: white;
+      border-color: white;
+      background: black;
+    }
+    #my-math-input .mq-matrixed {
+      background: black;
+    }
+    #my-math-input .mq-matrixed-container {
+      filter: progid:DXImageTransform.Microsoft.Chroma(color='black');
+    }
+
+## Color Change Support on IE8
+
+To support a MathQuill editable background color other than white in IE8, set the background color on both the editable mathField and on elements with class `mq-matrixed`. Then set a Chroma filter on elements with class `mq-matrixed-container`.
diff --git a/docs/Contributing.md b/docs/Contributing.md
new file mode 100644
index 0000000..bcbd3ae
--- /dev/null
+++ b/docs/Contributing.md
@@ -0,0 +1,65 @@
+# Building from Source
+
+Install [Node](http://nodejs.org/#download) if needed. Then run `make`. This installs all of the needed dependencies and creates the full build folder.
+
+For developing on MathQuill, run `make dev`. This will skip minifying MathQuill, which can be annoyingly slow.
+
+## Building a Smaller MathQuill
+
+`make basic` builds a stripped-down version of MathQuill for basic mathematics, without advanced LaTeX commands. This version doesn't allow typed LaTeX backslash commands with `\` or text blocks with `$`, and also won't render any LaTeX commands that can't by typed without `\`. This version of MathQuill's JS is only somewhat smaller, but the font is like 100x smaller.
+
+To run this smaller version, serve and load `mathquill-basic.{js,min.js,css}` and `font/Symbola-basic.{eot,ttf}` instead.
+
+# Testing
+
+Run `make test`, which builds `mathquill.test.js`. Open `test/unit.html` in your browser to see the result of the unit tests. Open `test/visual.html` to see how a variety of expressions are rendering on your branch.
+
+# Understanding The Source Code
+
+All the JavaScript that you actually want to read is in `src/`. `build/` is created by `make` to contain the same JS concatenated and minified.
+
+All the CSS is in `src/css`. The choice of font isn't settled, and fractions are somewhat arcane, see the Wiki pages ["Fonts"](http://github.com/mathquill/mathquill/wiki/Fonts) and ["Fractions"](http://github.com/mathquill/mathquill/wiki/Fractions).
+
+## Architecture
+
+There's 2 thin layers sandwiching 2 broad, modularized layers. At the highest level, the public API is a thin wrapper around calls to [services](#service) on the controller. These set event listeners that call methods on [commands](#command) in the [edit tree](#edit-tree). Those commands call tree and cursor manipulation methods to do actions like move the cursor or edit the tree.
+
+## Edit Tree
+
+At the lowest level, the **edit tree** of JS objects represents math and text analogously to how [the HTML DOM](http://www.w3.org/TR/html5-author/introduction.html#a-quick-introduction-to-html) represents a web page.
+
+[`tree.js`](https://github.com/mathquill/mathquill/blob/master/src/tree.js) defines base classes of objects relating to the tree.
+
+[`cursor.js`](https://github.com/mathquill/mathquill/blob/master/src/cursor.js) defines objects representing the cursor and a selection of math or text, with associated HTML elements.
+
+Old docs called this the "math tree", the "fake DOM", or some combination thereof, like the "math DOM".
+
+## Command
+
+A **command** is a thing a user can type and edit like a fraction, square root, or "for all" symbol, &forall;. They are implemented as a class of node objects in the edit tree, like `Fraction`, `SquareRoot`, or `VanillaSymbol`.
+
+Each command has an associated **control sequence** (commonly called a "macro" or "command"). This is a token in TeX and LaTeX syntax consisting of a backslash then any single character or string of letters, like `\frac` or <code>\ </code>. Unlike loose usage in the LaTeX community, where `\ne` and `\neq` might or might not be considered the same command, in the context of MathQuill they are considered different "control sequences" for the same "command".
+
+## Service
+
+A **service** is a feature that applies to all or many actions, such as typing, moving the cursor around, LaTeX exporting, or LaTeX parsing. Each of these actions vary by command. For example, the cursor goes in a different place when moving into a fraction vs into a square root and they each export different LaTeX.
+
+Services define methods on the controller that call methods on nodes in the edit tree with certain contracts, such as a controller method called on initialization to set listeners for keyboard events, that when the Left key is pressed, calls `.moveTowards` on the node just left of the cursor, dispatching on what kind of command the node is (`Fraction::moveTowards` and `SquareRoot::moveTowards` can insert the cursor in different places).
+
+[`controller.js`](https://github.com/mathquill/mathquill/blob/master/src/controller.js) defines the base class for the **controller**, which each math field or static math instance has one of, and to which each service adds methods.
+
+## API
+
+[`publicapi.js`](https://github.com/mathquill/mathquill/blob/master/src/publicapi.js) defines the global `MathQuill.getInterface()` function, the mathField constructors, and the API objects returned by them. The constructors, and the API methods on the objects they return, call appropriate controller methods to initialize and manipulate math field and static math instances.
+
+## Other Components
+
+[`services/*.util.js`](https://github.com/mathquill/mathquill/tree/master/src/services) files are unimportant to the overall architecture. You can largely ignore them until you have to deal with code that is using them.
+
+[`intro.js`](https://github.com/mathquill/mathquill/blob/master/src/intro.js) defines some simple sugar for the idiomatic JS classes used throughout MathQuill, plus some globals and opening boilerplate.
+
+## Conventions
+
+Classes are defined using [Pjs](https://github.com/jneen/pjs), and the variable `_` is used by convention as the prototype.
+
+In comments and internal documentation, `::` means `.prototype.`.
diff --git a/docs/Getting_Started.md b/docs/Getting_Started.md
new file mode 100644
index 0000000..aace364
--- /dev/null
+++ b/docs/Getting_Started.md
@@ -0,0 +1,63 @@
+# Download and Load
+
+Download [the latest release](https://github.com/mathquill/mathquill/releases/latest) or [build from source](Contributing.md#building-and-testing).
+
+MathQuill depends on [jQuery 1.4.3+](http://jquery.com), we recommend the [Google CDN-hosted copy](http://code.google.com/apis/libraries/devguide.html#jquery).
+
+Load MathQuill with something like (order matters):
+```html
+<link rel="stylesheet" href="/path/to/mathquill.css"/>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+<script src="/path/to/mathquill.js"></script>
+<script>
+var MQ = MathQuill.getInterface(2);
+</script>
+```
+
+Now you can call our [API methods](Api_Methods.md) on `MQ`.
+
+# Basic Usage
+
+MathQuill instances are created from HTML elements. For the full list of constructors and API methods, see [API Methods](Api_Methods.md).
+
+## Static Math Rendering
+
+To statically render a formula, call [`MQ.StaticMath()`](Api_Methods.md#mqstaticmathhtml_element) on an HTML element:
+```html
+<p>Solve <span id="problem">ax^2 + bx + c = 0</span>.</p>
+
+<script>
+  var problemSpan = document.getElementById('problem');
+  MQ.StaticMath(problemSpan);
+</script>
+```
+
+## Editable Math Fields
+
+To create an editable math field, call [`MQ.MathField()`](Api_Methods.md#mqmathfieldhtml_element-config) on an HTML element and, optionally, a [config options object](Config.md). The following example features a math field with a handler to check the answer every time an edit may have occurred:
+```html
+<p><span id="answer">x=</span></p>
+
+<script>
+  var answerSpan = document.getElementById('answer');
+  var answerMathField = MQ.MathField(answerSpan, {
+    handlers: {
+      edit: function() {
+        var enteredMath = answerMathField.latex(); // Get entered math in LaTeX format
+        checkAnswer(enteredMath);
+      }
+    }
+  });
+</script>
+```
+
+## Get and Set Math
+
+To get and set the contents of a math field, use [`mathField.latex()`](Api_Methods.md#latex).
+
+Math fields are initialized with the text that was in the span, parsed as LaTeX. This can be updated by calling [`mathField.latex(latexString)`](Api_Methods.md#latexlatex_string). To programmatically type text into a math field, use [`.typedText(string)`](Api_Methods.md#typedtexttext),
+
+# Join the Community
+
+[<img alt="slackin.mathquill.com" src="http://slackin.mathquill.com/badge.svg" align="top">](http://slackin.mathquill.com)
+(Prefer IRC? We're `#mathquill` on Freenode.)
diff --git a/docs/README.md b/docs/README.md
new file mode 120000
index 0000000..32d46ee
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1 @@
+../README.md
\ No newline at end of file
diff --git a/docs/extra.css b/docs/extra.css
new file mode 100644
index 0000000..559adca
--- /dev/null
+++ b/docs/extra.css
@@ -0,0 +1,8 @@
+/* default ReadTheDocs MkDocs theme's <code> font-size
+   much too small at 75%, override */
+body code {
+  font-size: 90%;
+}
+body pre code {
+  font-size: 100%;
+}
diff --git a/docs/index.md b/docs/index.md
new file mode 120000
index 0000000..32d46ee
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1 @@
+../README.md
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..5351f6e
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,22 @@
+site_name: MathQuill
+site_url: http://docs.mathquill.com
+repo_url: https://github.com/mathquill/mathquill
+site_description: Easily type math in your webapp
+site_author: Han, Jeanine, Mary
+site_favicon: http://mathquill.com/favicon.ico
+google_analytics: ['UA-73742753-3', 'docs.mathquill.com']
+
+pages:
+  - index.md
+  - Getting_Started.md
+  - 'API Methods': Api_Methods.md
+  - 'Config Options': Config.md
+  - 'Under the Hood': Contributing.md
+  - Code_of_Conduct.md
+
+markdown_extensions:
+  - toc:
+      permalink: True
+
+extra_css:
+  - extra.css
diff --git a/package.json b/package.json
index cedaec8..9c8fb28 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,10 @@
     "url": "https://github.com/mathquill/mathquill.git"
   },
   "dependencies": {
-    "pjs": ">=3.1.0"
+    "pjs": ">=3.1.0 <5.0.0"
   },
   "devDependencies": {
-    "mocha": "*",
+    "mocha": ">=2.4.1",
     "uglify-js": "2.x",
     "less": ">=1.5.1"
   }
diff --git a/script/screenshots.js b/script/screenshots.js
new file mode 100644
index 0000000..a28f035
--- /dev/null
+++ b/script/screenshots.js
@@ -0,0 +1,199 @@
+// This script assumes the following:
+//   1. You've installed wd with `npm install wd'.
+//   2. You've set the environment variables $SAUCE_USERNAME and $SAUCE_ACCESS_KEY.
+//   3. If the environment variable $CIRCLE_ARTIFACTS is not set images will be saved in /tmp
+//
+// This scripts creates following files for each browser in browserVersions:
+//    $CIRCLE_ARTIFACTS/imgs/{browser_version_platform}/#.png
+//
+// The intention of this script is that it will be ran from CircleCI
+//
+// Example usage:
+//   node screenshots.js http://localhost:9292/test/visual.html
+//   node screenshots.js http://google.com
+
+var wd = require('wd');
+var fs = require('fs');
+var username = process.env['SAUCE_USERNAME'];
+var accessKey = process.env['SAUCE_ACCESS_KEY'];
+var baseDir = process.env['CIRCLE_ARTIFACTS'] || '/tmp';
+var url = process.argv[2];
+var allImgsDir = baseDir+'/imgs';
+fs.mkdirSync(allImgsDir);
+
+var browserVersions = [
+  {
+    'version': {
+      // Expecting IE 8
+      'browserName': 'Internet Explorer',
+      'platform': 'Windows XP'
+    },
+    'pinned': 'PINNED'
+  },
+  {
+    'version': {
+      // Expecting IE 11
+      'browserName': 'Internet Explorer',
+      'platform': 'Windows 7'
+    },
+    'pinned': 'PINNED'
+  },
+  {
+    'version': {
+      'browserName': 'MicrosoftEdge',
+      'platform': 'Windows 10'
+    },
+    'pinned': 'EVERGREEN'
+  },
+  {
+    'version': {
+      'browserName': 'Firefox',
+      'platform': 'OS X 10.11'
+    },
+    'pinned': 'EVERGREEN'
+  },
+  {
+    'version': {
+      'browserName': 'Safari',
+      'platform': 'OS X 10.11'
+    },
+    'pinned': 'EVERGREEN'
+  },
+  {
+    'version': {
+      'browserName': 'Chrome',
+      'platform': 'OS X 10.11'
+    },
+    'pinned': 'EVERGREEN'
+  },
+  {
+    'version': {
+      'browserName': 'Firefox',
+      'platform': 'Linux'
+    },
+    'pinned': 'EVERGREEN'
+  },
+];
+
+
+browserVersions.forEach(function(obj) {
+  var cfg = obj.version;
+  var browserDriver = wd.remote('ondemand.saucelabs.com', 80, username, accessKey);
+  // The following is in the style of
+  // https://github.com/admc/wd/blob/62f2b0060d36a402de5634477b26a5ed4c051967/examples/async/chrome.js#L25-L40
+  browserDriver.init(cfg, function(err, _, capabilities) {
+    if (err) console.log(err);
+    console.log(cfg.browserName,cfg.platform,'init')
+
+    var browser = cfg.browserName.replace(/\s/g, '_');
+    var platform = cfg.platform.replace(/\s/g, '_');
+    var piecesDir = allImgsDir+'/'+obj.pinned+'_'+platform+'_'+browser;
+    fs.mkdirSync(piecesDir);
+
+    browserDriver.get(url, function(err) {
+      if (err) console.log(err);
+      console.log(cfg.browserName,cfg.platform,'get')
+      browserDriver.safeExecute('document.documentElement.scrollHeight', function(err,scrollHeight) {
+        if (err) console.log(err);
+        console.log(cfg.browserName,cfg.platform,'get scrollHeight')
+        browserDriver.safeExecute('document.documentElement.clientHeight', function(err,viewportHeight) {
+          if (err) console.log(err);
+          console.log(cfg.browserName,cfg.platform,'get clientHeight')
+
+          // Firefox and Internet Explorer will take a screenshot of the entire webpage,
+          if (cfg.browserName != 'Safari' && cfg.browserName != 'Chrome' && cfg.browserName != 'MicrosoftEdge') {
+            // saves file in the file `piecesDir/browser_version_platform/*.png`
+            var filename = piecesDir+'/'+browser+'_'+platform+'.png';
+            browserDriver.saveScreenshot(filename, function(err) {
+              if (err) console.log(err);
+              console.log(cfg.browserName,cfg.platform,'saveScreenshot');
+
+              browserDriver.log('browser', function(err,logs) {
+                if (err) console.log(err);
+                console.log(cfg.browserName,cfg.platform,'log');
+
+                var logfile = baseDir+'/'+browser+'_'+platform+'.log'
+                logs = logs || [];
+                fs.writeFile(logfile,logs.join('\n'), function(err) {
+                  if (err) console.log(err);
+
+                  browserDriver.quit();
+                });
+              });
+
+            });
+          } else {
+            var scrollTop = 0;
+
+            // loop generates the images. Firefox and Internet Explorer will take
+            // a screenshot of the entire webpage, but Opera, Safari, and Chrome
+            // do not. For those browsers we scroll through the page and take
+            // incremental screenshots.
+            (function loop() {
+              var index = (scrollTop/viewportHeight) + 1;
+
+              // Use `window.scrollTo` because thats what jQuery does
+              // https://github.com/jquery/jquery/blob/1.12.3/src/offset.js#L186
+              // `window.scrollTo` was used instead of jQuery because jQuery was
+              // causing a stackoverflow in Safari.
+              browserDriver.safeEval('window.scrollTo(0,'+scrollTop+');', function(err) {
+                if (err) console.log(JSON.stringify(err));
+                console.log(cfg.browserName,cfg.platform,'safeEval 1');
+
+                // saves file in the file `piecesDir/browser_version_platform/#.png`
+                var filename = piecesDir+'/'+index+'.png';
+                browserDriver.saveScreenshot(filename, function(err) {
+                  if (err) console.log(err);
+                  console.log(cfg.browserName,cfg.platform,'saveScreenshot');
+
+                  scrollTop += viewportHeight;
+                  if (scrollTop + viewportHeight > scrollHeight) {
+                    browserDriver.getWindowSize(function(err,size) {
+                      if (err) console.log(err);
+                      console.log(cfg.browserName,cfg.platform,'getWindowSize');
+                      // account for the viewport offset
+                      var extra = size.height - viewportHeight;
+                      browserDriver.setWindowSize(size.width, (scrollHeight-scrollTop)+extra, function(err) {
+                        if (err) console.log(err);
+                        console.log(cfg.browserName,cfg.platform,'setWindowSize');
+
+                        browserDriver.safeEval('window.scrollTo(0,'+scrollHeight+');', function(err) {
+                          if (err) console.log(JSON.stringify(err));
+                          console.log(cfg.browserName,cfg.platform,'safeEval 2');
+
+                          index++;
+                          var filename = piecesDir+'/'+index+'.png';
+                          browserDriver.saveScreenshot(filename, function(err) {
+                            if (err) console.log(err);
+                            console.log(cfg.browserName,cfg.platform,'saveScreenshot Final');
+
+                            browserDriver.log('browser', function(err,logs) {
+                              if (err) console.log(err);
+                              console.log(cfg.browserName,cfg.platform,'log');
+
+                              var logfile = baseDir+'/'+browser+'_'+platform+'.log'
+                              logs = logs || [];
+                              fs.writeFile(logfile,logs.join('\n'), function(err) {
+                                if (err) console.log(err);
+                                console.log(cfg.browserName,cfg.platform,'writeFile');
+
+                                browserDriver.quit();
+                              });
+                            });
+                          });
+                        });
+
+                      });
+                    });
+                  } else {
+                    loop();
+                  }
+                });
+              });
+            })();
+          }
+        });
+      });
+    });
+  });
+});
diff --git a/script/test_server.js b/script/test_server.js
index a551bae..17a7420 100644
--- a/script/test_server.js
+++ b/script/test_server.js
@@ -35,6 +35,8 @@
         }
       }
       else {
+        var ext = filepath.match(/\.[^.]+$/);
+        if (ext) res.setHeader('Content-Type', 'text/' + ext[0].slice(1));
         res.end(data);
       }
 
diff --git a/src/commands/math/basicSymbols.js b/src/commands/math/basicSymbols.js
index a8578ae..7f44356 100644
--- a/src/commands/math/basicSymbols.js
+++ b/src/commands/math/basicSymbols.js
@@ -247,6 +247,7 @@
 LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', '&nbsp;');
 
 LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '&prime;');
+LatexCmds['″'] = LatexCmds.dprime = bind(VanillaSymbol, '″', '&Prime;');
 
 LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
 if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
@@ -421,12 +422,28 @@
   _.init = VanillaSymbol.prototype.init;
 
   _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
+    function determineOpClassType(node) {
+      if (node[L]) {
+        // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
+        // or an open bracket (open parenthesis, open square bracket)
+        // consider the operator to be unary
+        if (node[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(node[L].ctrlSeq)) {
+          return '';
+        }
+      } else if (node.parent && node.parent.parent && node.parent.parent.isStyleBlock()) {
+        //if we are in a style block at the leftmost edge, determine unary/binary based on
+        //the style block
+        //this allows style blocks to be transparent for unary/binary purposes
+        return determineOpClassType(node.parent.parent);
+      } else {
+        return '';
+      }
+
+      return 'mq-binary-operator';
+    };
+    
     if (dir === R) return; // ignore if sibling only changed on the right
-    // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
-    // or an open bracket (open parenthesis, open square bracket)
-    // consider the operator to be unary, otherwise binary
-    this.jQ[0].className =
-      (!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
+    this.jQ[0].className = determineOpClassType(this);
     return this;
   };
 });
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index f4e1381..fdd3cf2 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -114,6 +114,9 @@
       })
     ;
   };
+  _.isStyleBlock = function() {
+    return true;
+  };
 });
 
 // Very similar to the \textcolor command, but will add the given CSS class.
@@ -133,12 +136,15 @@
       })
     ;
   };
+  _.isStyleBlock = function() {
+    return true;
+  };
 });
 
 var SupSub = P(MathCommand, function(_, super_) {
   _.ctrlSeq = '_{...}^{...}';
   _.createLeftOf = function(cursor) {
-    if (!cursor[L] && cursor.options.supSubsRequireOperand) return;
+    if (!this.replacedFragment && !cursor[L] && cursor.options.supSubsRequireOperand) return;
     return super_.createLeftOf.apply(this, arguments);
   };
   _.contactWeld = function(cursor) {
@@ -474,15 +480,15 @@
   };
 });
 
-var Vec = LatexCmds.vec = P(MathCommand, function(_, super_) {
-  _.ctrlSeq = '\\vec';
+var Hat = LatexCmds.hat = P(MathCommand, function(_, super_) {
+  _.ctrlSeq = '\\hat';
   _.htmlTemplate =
       '<span class="mq-non-leaf">'
-    +   '<span class="mq-vector-prefix">&rarr;</span>'
-    +   '<span class="mq-vector-stem">&0</span>'
+    +   '<span class="mq-hat-prefix">^</span>'
+    +   '<span class="mq-hat-stem">&0</span>'
     + '</span>'
   ;
-  _.textTemplate = ['vec(', ')'];
+  _.textTemplate = ['hat(', ')'];
 });
 
 var NthRoot =
@@ -500,6 +506,21 @@
   };
 });
 
+var DiacriticAbove = P(MathCommand, function(_, super_) {
+  _.init = function(ctrlSeq, symbol, textTemplate) {
+    var htmlTemplate =
+      '<span class="mq-non-leaf">'
+      +   '<span class="mq-diacritic-above">'+symbol+'</span>'
+      +   '<span class="mq-diacritic-stem">&0</span>'
+      + '</span>'
+    ;
+
+    super_.init.call(this, ctrlSeq, htmlTemplate, textTemplate);
+  };
+});
+LatexCmds.vec = bind(DiacriticAbove, '\\vec', '&rarr;', ['vec(', ')']);
+LatexCmds.tilde = bind(DiacriticAbove, '\\tilde', '~', ['tilde(', ')']);
+
 function DelimsMixin(_, super_) {
   _.jQadd = function() {
     super_.jQadd.apply(this, arguments);
@@ -573,8 +594,8 @@
         Fragment(cursor[side], cursor.parent.ends[side], -side) // me and ghost outside
           .disown().withDirAdopt(-side, brack.parent, brack, brack[side])
           .jQ.insDirOf(side, brack.jQ);
-        brack.bubble('reflow');
       }
+      brack.bubble('reflow');
     }
     else {
       brack = this, side = brack.side;
@@ -673,7 +694,9 @@
   '&rang;': '&lang;',
   '\\langle ': '\\rangle ',
   '\\rangle ': '\\langle ',
-  '|': '|'
+  '|': '|',
+  '\\lVert ' : '\\rVert ',
+  '\\rVert ' : '\\lVert ',
 };
 
 function bindCharBracketPair(open, ctrlSeq) {
@@ -687,6 +710,8 @@
 LatexCmds.langle = bind(Bracket, L, '&lang;', '&rang;', '\\langle ', '\\rangle ');
 LatexCmds.rangle = bind(Bracket, R, '&lang;', '&rang;', '\\langle ', '\\rangle ');
 CharCmds['|'] = bind(Bracket, L, '|', '|', '|', '|');
+LatexCmds.lVert = bind(Bracket, L, '&#8741;', '&#8741;', '\\lVert ', '\\rVert ');
+LatexCmds.rVert = bind(Bracket, R, '&#8741;', '&#8741;', '\\lVert ', '\\rVert ');
 
 LatexCmds.left = P(MathCommand, function(_) {
   _.parser = function() {
@@ -695,13 +720,17 @@
     var succeed = Parser.succeed;
     var optWhitespace = Parser.optWhitespace;
 
-    return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
-      .then(function(ctrlSeq) { // TODO: \langle, \rangle
+    return optWhitespace.then(regex(/^(?:[([|]|\\\{|\\langle\b|\\lVert\b)/))
+      .then(function(ctrlSeq) {
         var open = (ctrlSeq.charAt(0) === '\\' ? ctrlSeq.slice(1) : ctrlSeq);
+	if (ctrlSeq=="\\langle") { open = '&lang;'; ctrlSeq = ctrlSeq + ' '; }
+	if (ctrlSeq=="\\lVert") { open = '&#8741;'; ctrlSeq = ctrlSeq + ' '; }
         return latexMathParser.then(function (block) {
           return string('\\right').skip(optWhitespace)
-            .then(regex(/^(?:[\])|]|\\\})/)).map(function(end) {
+            .then(regex(/^(?:[\])|]|\\\}|\\rangle\b|\\rVert\b)/)).map(function(end) {
               var close = (end.charAt(0) === '\\' ? end.slice(1) : end);
+	      if (end=="\\rangle") { close = '&rang;'; end = end + ' '; }
+	      if (end=="\\rVert") { close = '&#8741;'; end = end + ' '; }
               var cmd = Bracket(0, open, close, ctrlSeq, end);
               cmd.blocks = [ block ];
               block.adopt(cmd, 0, 0);
@@ -791,7 +820,7 @@
     return this;
   };
   _.parser = function() {
-    var self = this;
+    var self = this,
       string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
     return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}'))
       .then(function(name) {
diff --git a/src/commands/text.js b/src/commands/text.js
index d4352bd..59614ee 100644
--- a/src/commands/text.js
+++ b/src/commands/text.js
@@ -296,7 +296,6 @@
   };
 });
 
-CharCmds.$ =
 LatexCmds.text =
 LatexCmds.textnormal =
 LatexCmds.textrm =
diff --git a/src/css/math.less b/src/css/math.less
index aae409d..389058c 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -294,15 +294,29 @@
     padding-top: 1px;
   }
 
-  .mq-vector-prefix {
+  .mq-diacritic-above {
     display: block;
     text-align: center;
-    line-height: .25em;
-    margin-bottom: -.1em;
-    font-size: 0.75em;
+    line-height: .4em;
   }
 
-  .mq-vector-stem {
+  .mq-diacritic-stem {
+    display: block;
+    text-align: center;
+  }
+  
+  .mq-hat-prefix {
+    display: block;
+    text-align: center;
+    line-height: .95em;
+    margin-bottom: -.7em;
+    transform: scaleX(1.5);
+    -moz-transform: scaleX(1.5);
+    -o-transform: scaleX(1.5);
+    -webkit-transform: scaleX(1.5);
+  }
+
+  .mq-hat-stem {
     display: block;
   }
 
diff --git a/src/css/textarea.less b/src/css/textarea.less
index 1484fa7..4da9bdb 100644
--- a/src/css/textarea.less
+++ b/src/css/textarea.less
@@ -20,5 +20,10 @@
 
     width: 1px; // don't "stick out" invisibly from a math field,
     height: 1px; // can affect ancestor's .scroll{Width,Height}
+
+    // Needed to fix a Safari 10 bug where box-sizing: border-box is
+    // preventing text from being copied.
+    // https://github.com/mathquill/mathquill/issues/686
+    box-sizing: content-box;
   }
 }
diff --git a/src/intro.js b/src/intro.js
index 654b8cd..265c233 100644
--- a/src/intro.js
+++ b/src/intro.js
@@ -17,6 +17,8 @@
   min = Math.min,
   max = Math.max;
 
+if (!jQuery) throw 'MathQuill requires jQuery 1.4.3+ to be loaded first';
+
 function noop() {}
 
 /**
diff --git a/src/services/keystroke.js b/src/services/keystroke.js
index f3ec668..fc3fc8e 100644
--- a/src/services/keystroke.js
+++ b/src/services/keystroke.js
@@ -241,7 +241,7 @@
   _.ctrlDeleteDir = function(dir) {
     prayDirection(dir);
     var cursor = this.cursor;
-    if (!cursor[L] || cursor.selection) return ctrlr.deleteDir();
+    if (!cursor[L] || cursor.selection) return this.deleteDir();
 
     this.notify('edit');
     Fragment(cursor.parent.ends[L], cursor[L]).remove();
diff --git a/src/tree.js b/src/tree.js
index 649c517..335dafa 100644
--- a/src/tree.js
+++ b/src/tree.js
@@ -156,6 +156,10 @@
   _.isEmpty = function() {
     return this.ends[L] === 0 && this.ends[R] === 0;
   };
+  
+  _.isStyleBlock = function() {
+    return false;
+  };
 
   _.children = function() {
     return Fragment(this.ends[L], this.ends[R]);
diff --git a/test/unit.html b/test/unit.html
index 322f89f..601820c 100644
--- a/test/unit.html
+++ b/test/unit.html
@@ -24,7 +24,15 @@
 
     <!-- configure mocha and chai -->
     <script type="text/javascript">
-      mocha.setup('tdd');
+      var post_xunit_to = Mocha.utils.parseQuery(location.search).post_xunit_to;
+
+      mocha.setup({
+        ui: 'tdd',
+        reporter: post_xunit_to ? 'xunit' : 'html'
+      });
+
+      var xunit = '';
+      Mocha.process.stdout.write = function(line) { xunit += line; };
     </script>
 
     <!-- include the library with the tests inlined -->
@@ -49,6 +57,34 @@
 
     <div id="mock"></div>
 
-    <script type="text/javascript"> mocha.run(); </script>
+    <script type="text/javascript">
+      var runner = mocha.run();
+
+      if (post_xunit_to) {
+        // the following is based on https://github.com/saucelabs-sample-scripts/JavaScript/blob/4946c5cf0ab7325dce5562881dba7c28e30989e5/reporting_mocha.js
+        var failedTests = [];
+        runner.on('fail', function(test, err) {
+          function flattenTitles(test) {
+            var titles = [];
+            while (test.parent.title) {
+              titles.push(test.parent.title);
+              test = test.parent;
+            }
+            return titles.reverse();
+          }
+
+          failedTests.push({name: test.title, result: false, message: err.message, stack: err.stack, titles: flattenTitles(test) });
+        });
+
+        runner.on('end', function() {
+          setTimeout(function() {
+            $.post(post_xunit_to, xunit).always(function() {
+              window.mochaResults = runner.stats;
+              window.mochaResults.reports = failedTests;
+            });
+          });
+        });
+      }
+    </script>
   </body>
 </html>
diff --git a/test/unit/css.test.js b/test/unit/css.test.js
index 639cdf9..56e12d2 100644
--- a/test/unit/css.test.js
+++ b/test/unit/css.test.js
@@ -82,6 +82,37 @@
     $(mq.el()).remove();
   });
 
+  test('proper unary/binary within style block', function () {
+    var mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
+    mq.latex('\\class{dummy}{-}2\\class{dummy}{+}4');
+    var spans = $(mq.el()).find('.mq-root-block').find('span');
+    assert.equal(spans.length, 6, 'PlusMinus expression parsed incorrectly');
+
+    function isBinaryOperator(i) { return $(spans[i]).hasClass('mq-binary-operator'); }
+    function assertBinaryOperator(i, s) { assert.ok(isBinaryOperator(i), '"' + s + '" should be binary'); }
+    function assertUnaryOperator(i, s) { assert.ok(!isBinaryOperator(i), '"' + s + '" should be unary'); }
+
+    assertUnaryOperator(1, '\\class{dummy}{-}');
+    assertBinaryOperator(4, '\\class{dummy}{-}2\\class{dummy}{+}');
+
+    mq.latex('\\textcolor{red}{-}2\\textcolor{green}{+}4');
+    spans = $(mq.el()).find('.mq-root-block').find('span');
+    assert.equal(spans.length, 6, 'PlusMinus expression parsed incorrectly');
+
+    assertUnaryOperator(1, '\\textcolor{red}{-}');
+    assertBinaryOperator(4, '\\textcolor{red}{-}2\\textcolor{green}{+}');
+
+    //test recursive depths
+    mq.latex('\\textcolor{red}{\\class{dummy}{-}}2\\textcolor{green}{\\class{dummy}{+}}4');
+    spans = $(mq.el()).find('.mq-root-block').find('span');
+    assert.equal(spans.length, 8, 'PlusMinus expression parsed incorrectly');
+
+    assertUnaryOperator(2, '\\textcolor{red}{\\class{dummy}{-}}');
+    assertBinaryOperator(6, '\\textcolor{red}{\\class{dummy}{-}}2\\textcolor{green}{\\class{dummy}{+}}');
+
+    $(mq.el()).remove();
+  });
+
   test('operator name spacing e.g. sin x', function() {
     var mq = MathQuill.MathField($('<span></span>').appendTo(mock)[0]);
 
diff --git a/test/unit/focusBlur.test.js b/test/unit/focusBlur.test.js
index 1bd3af9..20b4fbd 100644
--- a/test/unit/focusBlur.test.js
+++ b/test/unit/focusBlur.test.js
@@ -1,6 +1,6 @@
 suite('focusBlur', function() {
   function assertHasFocus(mq, name, invert) {
-    assert.ok(!!invert ^ $(mq.el()).find('textarea').is(':focus'), name + (invert ? ' does not have focus' : ' has focus'));
+    assert.ok(!!invert ^ ($(mq.el()).find('textarea')[0] === document.activeElement), name + (invert ? ' does not have focus' : ' has focus'));
   }
 
   suite('handlers can shift focus away', function() {
@@ -80,7 +80,7 @@
           $(mq.el()).remove();
           done();
         });
-      }, 10);
+      }, 100);
     });
   });
 });
diff --git a/test/unit/latex.test.js b/test/unit/latex.test.js
index 4cf59fa..ab6d925 100644
--- a/test/unit/latex.test.js
+++ b/test/unit/latex.test.js
@@ -85,6 +85,24 @@
     assert.equal(tree.join('latex'), '\\left(123\\right)');
   });
 
+  test('langle/rangle (issue #508)', function() {
+    var tree = latexMathParser.parse('\\left\\langle 123\\right\\rangle)');
+
+    assert.ok(tree.ends[L] instanceof Bracket);
+    var contents = tree.ends[L].ends[L].join('latex');
+    assert.equal(contents, '123');
+    assert.equal(tree.join('latex'), '\\left\\langle 123\\right\\rangle )');
+  });
+
+  test('lVert/rVert', function() {
+    var tree = latexMathParser.parse('\\left\\lVert 123\\right\\rVert)');
+
+    assert.ok(tree.ends[L] instanceof Bracket);
+    var contents = tree.ends[L].ends[L].join('latex');
+    assert.equal(contents, '123');
+    assert.equal(tree.join('latex'), '\\left\\lVert 123\\right\\rVert )');
+  });
+
   test('parens with whitespace', function() {
     assertParsesLatex('\\left ( 123 \\right ) ', '\\left(123\\right)');
   });
@@ -308,5 +326,7 @@
     testCantParse('unmatched close brace', '}', ' 1 + 2 } ', '1 - {2 + 3} }', '\\sqrt{ x }} + \\sqrt{y}');
     testCantParse('unmatched open brace', '{', '1 * { 2 + 3', '\\frac{ \\sqrt x }{{ \\sqrt y}');
     testCantParse('unmatched \\left/\\right', '\\left ( 1 + 2 )', ' [ 1, 2 \\right ]');
+    testCantParse('langlerfish/ranglerfish (checking for confusion with langle/rangle)',
+		    '\\left\\langlerfish 123\\right\\ranglerfish)');
   });
 });
diff --git a/test/unit/publicapi.test.js b/test/unit/publicapi.test.js
index 27433bc..b15c5de 100644
--- a/test/unit/publicapi.test.js
+++ b/test/unit/publicapi.test.js
@@ -304,6 +304,23 @@
       });
     }
   });
+  
+  suite('edit handler', function() {
+    test('fires when closing a bracket expression', function() {
+      var count = 0;
+      var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
+        handlers: {
+          edit: function() {
+            count += 1;
+          }
+        }
+      });
+      mq.typedText('(3, 4');
+      var countBeforeClosingBracket = count;
+      mq.typedText(']');
+      assert.equal(count, countBeforeClosingBracket + 1);
+    });
+  });
 
   suite('.cmd(...)', function() {
     var mq;
@@ -835,7 +852,7 @@
 
       var box = mq.el().getBoundingClientRect();
       var clientX = box.left + 30;
-      var clientY = box.top + 40;
+      var clientY = box.top + 30;
       var target = document.elementFromPoint(clientX, clientY);
 
       assert.equal(document.activeElement, document.body);
@@ -860,7 +877,7 @@
 
       var box = mq.el().getBoundingClientRect();
       var clientX = box.left + 30;
-      var clientY = box.top + 40;
+      var clientY = box.top + 30;
 
       assert.equal(document.activeElement, document.body);
       mq.clickAt(clientX, clientY).write('x');
@@ -903,7 +920,7 @@
 
       mq.el().scrollIntoView();
 
-      mq.dropEmbedded(mqx + 30, mqy + 40, {
+      mq.dropEmbedded(mqx + 30, mqy + 30, {
         htmlString: '<span class="embedded-html"></span>',
         text: function () { return "embedded text" },
         latex: function () { return "embedded latex" }
diff --git a/test/unit/saneKeyboardEvents.test.js b/test/unit/saneKeyboardEvents.test.js
index 44c75d7..fe59265 100644
--- a/test/unit/saneKeyboardEvents.test.js
+++ b/test/unit/saneKeyboardEvents.test.js
@@ -146,42 +146,42 @@
       assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
     });
 
-    if (!document.hasFocus()) {
-      test('blur in keystroke handler: DOCUMENT NEEDS FOCUS, SEE CONSOLE ');
-      console.warn(
-        'The test "blur in keystroke handler" needs the document to have ' +
-        'focus. Only when the document has focus does .select() on an ' +
-        'element also focus it, which is part of the problematic behavior ' +
-        'we are testing robustness against. (Specifically, erroneously ' +
-        'calling .select() in a timeout after the textarea has blurred, ' +
-        '"stealing back" focus.)\n' +
-        'Normally, the page being open and focused is enough to have focus, ' +
-        'but with the Developer Tools open, it depends on whether you last ' +
-        'clicked on something in the Developer Tools or on the page itself. ' +
-        'Click the page, or close the Developer Tools, and Refresh.'
-      );
-    }
-    else {
-      test('blur in keystroke handler', function(done) {
-        var shim = saneKeyboardEvents(el, {
-          keystroke: function(key) {
-            assert.equal(key, 'Left');
-            el[0].blur();
-          }
-        });
+    test('blur in keystroke handler', function(done) {
+      if (!document.hasFocus()) {
+        console.warn(
+          'The test "blur in keystroke handler" needs the document to have ' +
+          'focus. Only when the document has focus does .select() on an ' +
+          'element also focus it, which is part of the problematic behavior ' +
+          'we are testing robustness against. (Specifically, erroneously ' +
+          'calling .select() in a timeout after the textarea has blurred, ' +
+          '"stealing back" focus.)\n' +
+          'Normally, the page being open and focused is enough to have focus, ' +
+          'but with the Developer Tools open, it depends on whether you last ' +
+          'clicked on something in the Developer Tools or on the page itself. ' +
+          'Click the page, or close the Developer Tools, and Refresh.'
+        );
+        el.remove(); // LOL next line skips teardown https://git.io/vaUWq
+        this.skip();
+      }
 
-        shim.select('foobar');
-        assert.ok(document.activeElement === el[0], 'textarea focused');
-
-        el.trigger(Event('keydown', { which: 37 }));
-        assert.ok(document.activeElement !== el[0], 'textarea blurred');
-
-        setTimeout(function() {
-          assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
-          done();
-        });
+      var shim = saneKeyboardEvents(el, {
+        keystroke: function(key) {
+          assert.equal(key, 'Left');
+          el[0].blur();
+        }
       });
-    }
+
+      shim.select('foobar');
+      assert.ok(document.activeElement === el[0], 'textarea focused');
+
+      el.trigger(Event('keydown', { which: 37 }));
+      assert.ok(document.activeElement !== el[0], 'textarea blurred');
+
+      setTimeout(function() {
+        assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
+        done();
+      });
+    });
 
     suite('selected text after keypress or paste doesn\'t get mistaken' +
          ' for inputted text', function() {
diff --git a/test/unit/typing.test.js b/test/unit/typing.test.js
index 25628ef..c3c89f1 100644
--- a/test/unit/typing.test.js
+++ b/test/unit/typing.test.js
@@ -56,6 +56,11 @@
       mq.typedText('\\asdf+');
       assertLatex('\\text{asdf}+');
     });
+
+    test('dollar sign', function() {
+      mq.typedText('$');
+      assertLatex('\\$');
+    });
   });
 
   suite('auto-expanding parens', function() {
@@ -1034,6 +1039,9 @@
       assert.equal(mq.typedText('^').latex(), 'x^{^{ }}');
       assert.equal(mq.typedText('2').latex(), 'x^{^2}');
       assert.equal(mq.typedText('n').latex(), 'x^{^{2n}}');
+      mq.latex('');
+      assert.equal(mq.typedText('2').latex(), '2');
+      assert.equal(mq.keystroke('Shift-Left').typedText('^').latex(), '^2');
 
       mq.latex('');
       MQ.config({ supSubsRequireOperand: true });
@@ -1052,6 +1060,9 @@
       assert.equal(mq.typedText('^').latex(), 'x^{ }');
       assert.equal(mq.typedText('2').latex(), 'x^2');
       assert.equal(mq.typedText('n').latex(), 'x^{2n}');
+      mq.latex('');
+      assert.equal(mq.typedText('2').latex(), '2');
+      assert.equal(mq.keystroke('Shift-Left').typedText('^').latex(), '^2');
     });
   });
 });
diff --git a/test/visual.html b/test/visual.html
index 8e6d6c1..91a9874 100644
--- a/test/visual.html
+++ b/test/visual.html
@@ -161,8 +161,6 @@
 

 <p id="selection-tests"><span class="mathquill-text-field different-bgcolor">lolwut $a^2 + b^2 = c^2$. $\sqrt{ \left( \frac{1}{2} \right) }$.  Also, awesomesauce: $\int_0^1 \sin x dx.</span>

 

-<p>Time taken to Select All (should be &lt;50ms): <span id="selection-performance"></span>

-

 <p>Even in IE&lt;9, the background color of the parens and square root radical should be the background color of the selection.

 

 <h3>Dynamic mathquill-ification</h3>

@@ -211,6 +209,7 @@
 <tr><td><span class="mathquill-static-math">1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}</span><td><span>1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}</span>^M

 <tr><td><span class="mathquill-static-math">x\ \ \ +\ \ \ y</span><td><span>x\ \ \ +\ \ \ y</span>^M

 <tr><td><span class="mathquill-static-math">\sum _{n=0}^3\cos x</span><td><span>\sum _{n=0}^3\cos x</span>^M

+<tr><td><span class="mathquill-static-math">\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}</span><td><span>\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}</span>^M

 <tr><td><span class="mathquill-static-math">\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span><td><span>\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span>

 <tr><td><span class="mathquill-static-math">\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span><td><span>\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span>

 <tr><td colspan=2><span id="sixes"></span>

@@ -285,7 +284,8 @@
 

 </div>

 <script type="text/javascript">

-window.onerror = function() {

+window.onerror = function(err) {

+  console.log(err); // to show up in Selenium WebDriver logs

   $('html').css('background', 'red');

 };

 </script>

@@ -335,7 +335,7 @@
     $('#selection-tests .mathquill-text-field').each(function() {

       var start = +new Date;

       $('textarea', this).focus().trigger({type: 'keydown', ctrlKey: true, which: 65});

-      $('#selection-performance').html(new Date - start);

+      console.log('Time taken to Select All (should be &lt;50ms):',new Date - start);

     });

   });

 };