Merge pull request #659 from dominique-unruh/langle-rangle-vert

Support for brackets \langle,\rangle, \lVert,\rVert
diff --git a/Makefile b/Makefile
index 39df479..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 -*-
 #
 
@@ -56,8 +75,9 @@
 CLEAN += $(BUILD_DIR)/*
 
 DISTDIR = ./mathquill-$(VERSION)
-DIST = $(DISTDIR).tgz
-CLEAN += $(DIST)
+DISTTAR = $(DISTDIR).tgz
+DISTZIP = $(DISTDIR).zip
+CLEAN += $(DISTTAR) $(DISTZIP)
 
 # programs and flags
 UGLIFY ?= ./node_modules/.bin/uglifyjs
@@ -92,7 +112,6 @@
 uglify: $(UGLY_JS)
 css: $(BUILD_CSS)
 font: $(FONT_TARGET)
-dist: $(DIST)
 clean:
 	rm -rf $(CLEAN)
 
@@ -117,7 +136,7 @@
 	$(LESSC) --modify-var="basic=true" $(LESS_OPTS) $(CSS_MAIN) > $@
 
 $(NODE_MODULES_INSTALLED): package.json
-	npm install
+	NODE_ENV=development npm install
 	touch $(NODE_MODULES_INSTALLED)
 
 $(BUILD_DIR_EXISTS):
@@ -128,10 +147,11 @@
 	rm -rf $@
 	cp -r $< $@
 
-$(DIST): $(UGLY_JS) $(BUILD_JS) $(BUILD_CSS) $(FONT_TARGET)
+dist: $(UGLY_JS) $(BUILD_JS) $(BUILD_CSS) $(FONT_TARGET)
 	rm -rf $(DISTDIR)
 	cp -r $(BUILD_DIR) $(DISTDIR)
-	tar -czf $(DIST) --exclude='\.gitkeep' $(DISTDIR)
+	zip -r -X $(DISTZIP) $(DISTDIR)
+	tar -czf $(DISTTAR) $(DISTDIR)
 	rm -r $(DISTDIR)
 
 #
@@ -147,83 +167,3 @@
 
 $(BUILD_TEST): $(INTRO) $(SOURCES_FULL) $(UNIT_TESTS) $(OUTRO) $(BUILD_DIR_EXISTS)
 	cat $^ > $@
-
-#
-# -*- site (mathquill.github.com) tasks
-#
-
-.PHONY: site publish site-pull
-
-SITE = mathquill.github.com
-SITE_CLONE_URL = git@github.com:mathquill/mathquill.github.com
-SITE_COMMITMSG = 'updating mathquill to $(VERSION)'
-
-DOWNLOADS_PAGE = $(SITE)/downloads.html
-DIST_DOWNLOAD = $(SITE)/downloads/$(DIST)
-
-site: $(SITE) $(SITE)/mathquill $(SITE)/demo.html $(SITE)/support $(DOWNLOADS_PAGE)
-
-publish: site-pull site
-	pwd
-	cd $(SITE) \
-	&& git add -- mathquill demo.html support downloads downloads.html \
-	&& git commit -m $(SITE_COMMITMSG) \
-	&& git push
-
-$(SITE)/mathquill: $(DIST)
-	mkdir -p $@
-	tar -xzf $(DIST) \
-		--directory $@ \
-		--strip-components=2
-
-$(DIST_DOWNLOAD): $(DIST)
-	mkdir -p $(dir $@)
-	cp $^ $@
-
-# freaking bsd, i swear
-# adapted from https://developer.apple.com/library/mac/documentation/opensource/Conceptual/ShellScripting/PortingScriptstoMacOSX/PortingScriptstoMacOSX.html#//apple_ref/doc/uid/TP40004268-TP40003517-SW21
-ifeq (x, $(shell echo xy | sed -r 's/(x)y/\1/' 2>/dev/null))
-  # gnu
-  SED = sed -r
-  SED_I = $(SED) -i
-else
-  # bsd
-  SED = sed -E
-  SED_I = $(SED) -i ''
-endif
-
-$(DOWNLOADS_PAGE): $(DIST_DOWNLOAD)
-	@echo Using $(SED)
-	@echo -n updating downloads page...
-	@$(SED_I) \
-		-e '/Latest version:/ s/[0-9]+[.][0-9]+[.][0-9]+/$(VERSION)/g' \
-		$(DOWNLOADS_PAGE)
-	@mkdir -p tmp
-	@ls $(SITE)/downloads/*.tgz \
-		| egrep -o '[0-9]+[.][0-9]+[.][0-9]+' \
-		| fgrep -v $(VERSION) \
-		| sort -rn -t. -k 1,1 -k 2,2 -k 3,3 \
-		| sed 's|.*|<li><a class="prev" href="downloads/mathquill-&.tgz">v&</a></li>|' \
-		> tmp/versions-list.html
-	@$(SED_I) \
-		-e '/<a class="prev"/d' \
-		-e '/<ul id="prev-versions">/ r tmp/versions-list.html' \
-		$(DOWNLOADS_PAGE)
-	@rm tmp/versions-list.html
-	@echo done.
-
-$(SITE)/demo.html: test/demo.html
-	cat $^ \
-	| $(SED) 's:../build/:mathquill/:' \
-	| $(SED) 's:local test page:live demo:' \
-	> $@
-
-$(SITE)/support: test/support
-	rm -rf $@
-	cp -r $^ $@
-
-$(SITE):
-	git clone $(SITE_CLONE_URL) $@
-
-site-pull: $(SITE)
-	cd $(SITE) && git pull
diff --git a/README.md b/README.md
index 7b93213..c5202aa 100644
--- a/README.md
+++ b/README.md
@@ -1,500 +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:
-
-* `.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][Wikia], but
-with additional trig operators like `sech`, `arcsec`, `arsinh`, etc.)
-
-[Wikia]: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
-
-`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..e7ec805
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,188 @@
+# 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:
+    # imagemagick is installed to give us access to the
+    # `convert` tool to stitch together the screenshots.
+    - sudo apt-get update; sudo apt-get install imagemagick
+    - ? |-
+        mkdir -p ~/sauce-connect
+        cd ~/sauce-connect
+        if [ ! -x sc-*-linux/bin/sc ]
+        then
+          wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
+          tar -xzf sc-latest-linux.tar.gz
+        fi
+        sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY --readyfile ~/sauce_is_ready
+      :
+        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:
+        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' | tee $CIRCLE_TEST_REPORTS/mocha/xunit.xml
+        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);
+        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
+
+    # Start taking screenshots in the background while the unit tests are running
+    - npm install wd && node script/screenshots.js http://localhost:8000/test/visual.html && touch ~/screenshots_are_ready:
+        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:
+        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..8271945
--- /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 e9889de..9c8fb28 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,10 @@
     "url": "https://github.com/mathquill/mathquill.git"
   },
   "dependencies": {
-    "pjs": "3.x"
+    "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/advancedSymbols.js b/src/commands/math/advancedSymbols.js
index 776b9a7..f5aa5a5 100644
--- a/src/commands/math/advancedSymbols.js
+++ b/src/commands/math/advancedSymbols.js
@@ -222,10 +222,6 @@
 LatexCmds.rbrack = bind(VanillaSymbol, ']');
 
 //various symbols
-LatexCmds['∫'] =
-LatexCmds['int'] =
-LatexCmds.integral = bind(Symbol,'\\int ','<big>&int;</big>');
-
 LatexCmds.slash = bind(VanillaSymbol, '/');
 LatexCmds.vert = bind(VanillaSymbol,'|');
 LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','&perp;');
@@ -303,6 +299,9 @@
 LatexCmds.xist = //LOL
 LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
   bind(VanillaSymbol,'\\exists ','&exist;');
+  
+LatexCmds.nexists = LatexCmds.nexist =
+      bind(VanillaSymbol, '\\nexists ', '&#8708;');
 
 LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
   bind(VanillaSymbol,'\\wedge ','&and;');
diff --git a/src/commands/math/basicSymbols.js b/src/commands/math/basicSymbols.js
index 2b55143..a8578ae 100644
--- a/src/commands/math/basicSymbols.js
+++ b/src/commands/math/basicSymbols.js
@@ -59,26 +59,27 @@
 var Letter = P(Variable, function(_, super_) {
   _.init = function(ch) { return super_.init.call(this, this.letter = ch); };
   _.createLeftOf = function(cursor) {
+    super_.createLeftOf.apply(this, arguments);
     var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
     if (maxLength > 0) {
       // want longest possible autocommand, so join together longest
       // sequence of letters
-      var str = this.letter, l = cursor[L], i = 1;
-      while (l instanceof Letter && i < maxLength) {
+      var str = '', l = this, i = 0;
+      // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
+      while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
         str = l.letter + str, l = l[L], i += 1;
       }
       // check for an autocommand, going thru substrings longest to shortest
       while (str.length) {
         if (autoCmds.hasOwnProperty(str)) {
-          for (var i = 2, l = cursor[L]; i < str.length; i += 1, l = l[L]);
-          Fragment(l, cursor[L]).remove();
+          for (var i = 1, l = this; i < str.length; i += 1, l = l[L]);
+          Fragment(l, this).remove();
           cursor[L] = l[L];
           return LatexCmds[str](str).createLeftOf(cursor);
         }
         str = str.slice(1);
       }
     }
-    super_.createLeftOf.apply(this, arguments);
   };
   _.italicize = function(bool) {
     this.isItalic = bool;
@@ -103,7 +104,7 @@
     // removeClass and delete flags from all letters before figuring out
     // which, if any, are part of an operator name
     Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
-      el.italicize(true).jQ.removeClass('mq-first mq-last');
+      el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
       el.ctrlSeq = el.letter;
     });
 
@@ -122,8 +123,21 @@
           first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
           last.ctrlSeq += (isBuiltIn ? ' ' : '}');
           if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
-          if (nonOperatorSymbol(first[L])) first.jQ.addClass('mq-first');
-          if (nonOperatorSymbol(last[R])) last.jQ.addClass('mq-last');
+          if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
+          if (!shouldOmitPadding(last[R])) {
+            if (last[R] instanceof SupSub) {
+              var supsub = last[R]; // XXX monkey-patching, but what's the right thing here?
+              // Have operatorname-specific code in SupSub? A CSS-like language to style the
+              // math tree, but which ignores cursor and selection (which CSS can't)?
+              var respace = supsub.siblingCreated = supsub.siblingDeleted = function() {
+                supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket));
+              };
+              respace();
+            }
+            else {
+              last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket));
+            }
+          }
 
           i += len - 1;
           first = last;
@@ -132,12 +146,13 @@
       }
     }
   };
-  function nonOperatorSymbol(node) {
-    return node instanceof Symbol && !(node instanceof BinaryOperator);
+  function shouldOmitPadding(node) {
+    // omit padding if no node, or if node already has padding (to avoid double-padding)
+    return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
   }
 });
 var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
-  // are built-into LaTeX: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
+  // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
   // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
   // and 'arsinh', which must be exported as \operatorname{hcf} and
   // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
@@ -407,8 +422,11 @@
 
   _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
     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 ? '' : 'mq-binary-operator');
+      (!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
     return this;
   };
 });
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index 9ade4b9..2b9baf8 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -138,7 +138,7 @@
 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) {
@@ -181,7 +181,6 @@
         break;
       }
     }
-    this.respace();
   };
   Options.p.charsThatBreakOutOfSupSub = '';
   _.finalizeTree = function() {
@@ -230,10 +229,6 @@
     }
     return latex('_', this.sub) + latex('^', this.sup);
   };
-  _.respace = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
-    if (dir === R) return; // ignore if sibling only changed on the right
-    this.jQ.toggleClass('mq-limit', this[L].ctrlSeq === '\\int ');
-  };
   _.addBlock = function(block) {
     if (this.supsub === 'sub') {
       this.sup = this.upInto = this.sub.upOutOf = block;
@@ -380,6 +375,26 @@
 LatexCmds.coprod =
 LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','&#8720;');
 
+LatexCmds['∫'] =
+LatexCmds['int'] =
+LatexCmds.integral = P(SummationNotation, function(_, super_) {
+  _.init = function() {
+    var htmlTemplate =
+      '<span class="mq-int mq-non-leaf">'
+    +   '<big>&int;</big>'
+    +   '<span class="mq-supsub mq-non-leaf">'
+    +     '<span class="mq-sup"><span class="mq-sup-inner">&1</span></span>'
+    +     '<span class="mq-sub">&0</span>'
+    +     '<span style="display:inline-block;width:0">&#8203</span>'
+    +   '</span>'
+    + '</span>'
+    ;
+    Symbol.prototype.init.call(this, '\\int ', htmlTemplate);
+  };
+  // FIXME: refactor rather than overriding
+  _.createLeftOf = MathCommand.p.createLeftOf;
+});
+
 var Fraction =
 LatexCmds.frac =
 LatexCmds.dfrac =
@@ -459,15 +474,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 =
@@ -485,6 +500,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);
@@ -558,8 +588,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;
diff --git a/src/commands/text.js b/src/commands/text.js
index 42f85d4..59614ee 100644
--- a/src/commands/text.js
+++ b/src/commands/text.js
@@ -48,10 +48,8 @@
     return optWhitespace
       .then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
       .map(function(text) {
-        // TODO: is this the correct behavior when parsing
-        // the latex \text{} ?  This violates the requirement that
-        // the text contents are always nonempty.  Should we just
-        // disown the parent node instead?
+        if (text.length === 0) return Fragment();
+
         TextPiece(text).adopt(textBlock, 0, 0);
         return textBlock;
       })
@@ -64,7 +62,11 @@
     });
   };
   _.text = function() { return '"' + this.textContents() + '"'; };
-  _.latex = function() { return '\\text{' + this.textContents() + '}'; };
+  _.latex = function() {
+    var contents = this.textContents();
+    if (contents.length === 0) return '';
+    return '\\text{' + contents + '}';
+  };
   _.html = function() {
     return (
         '<span class="mq-text-mode" mathquill-command-id='+this.id+'>'
@@ -107,7 +109,7 @@
     else { // split apart
       var leftBlock = TextBlock();
       var leftPc = this.ends[L];
-      leftPc.disown();
+      leftPc.disown().jQ.detach();
       leftPc.adopt(leftBlock, 0, 0);
 
       cursor.insLeftOf(this);
@@ -162,15 +164,22 @@
     }
   };
 
-  _.blur = function() {
+  _.blur = function(cursor) {
     MathBlock.prototype.blur.call(this);
-    fuseChildren(this);
+    if (!cursor) return;
+    if (this.textContents() === '') {
+      this.remove();
+      if (cursor[L] === this) cursor[L] = this[L];
+      else if (cursor[R] === this) cursor[R] = this[R];
+    }
+    else fuseChildren(this);
   };
 
   function fuseChildren(self) {
     self.jQ[0].normalize();
 
     var textPcDom = self.jQ[0].firstChild;
+    if (!textPcDom) return;
     pray('only node in TextBlock span is Text node', textPcDom.nodeType === 3);
     // nodeType === 3 has meant a Text node since ancient times:
     //   http://reference.sitepoint.com/javascript/Node/nodeType
@@ -287,7 +296,6 @@
   };
 });
 
-CharCmds.$ =
 LatexCmds.text =
 LatexCmds.textnormal =
 LatexCmds.textrm =
diff --git a/src/css/math.less b/src/css/math.less
index 6115ee8..389058c 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -49,7 +49,14 @@
 
 
   .mq-text-mode {
-    font-size: 87%;
+    display: inline-block;
+  }
+  .mq-text-mode.mq-hasCursor {
+    box-shadow: inset darkgray 0 .1em .2em;
+    padding: 0 .1em;
+    margin: 0 -.1em;
+
+    min-width: 1ex;
   }
 
   .mq-font {
@@ -80,7 +87,29 @@
   }
 
   big {
-    font-size: 125%;
+    font-size: 200%;
+  }
+
+  .mq-int {
+    > big {
+      display: inline-block;
+      .transform(scaleX(.7));
+      vertical-align: -.16em;
+    }
+
+    > .mq-supsub {
+      font-size: 80%;
+      vertical-align: -1.1em;
+      padding-right: .2em;
+
+      > .mq-sup > .mq-sup-inner {
+        vertical-align: 1.3em;
+      }
+
+      > .mq-sub {
+        margin-left: -.35em;
+      }
+    }
   }
 
   .mq-roman {
@@ -122,12 +151,9 @@
   //   its contents, rather than always being as wide as the subscript.
   //   See also .fraction
   .mq-supsub {
+    text-align: left;
     font-size: 90%;
     vertical-align: -.5em;
-    &.mq-limit {
-      font-size: 80%;
-      vertical-align: -.4em;
-    }
 
     &.mq-sup-only {
       vertical-align: .5em;
@@ -146,9 +172,6 @@
       display: block;
       float: left;
     }
-    &.mq-limit .mq-sub {
-      margin-left: -.25em;
-    }
 
     .mq-binary-operator {
       padding: 0 .1em;
@@ -208,7 +231,7 @@
   var.mq-operator-name.mq-first {
     padding-left: .2em;
   }
-  var.mq-operator-name.mq-last {
+  var.mq-operator-name.mq-last, .mq-supsub.mq-after-operator-name {
     padding-right: .2em;
   }
 
@@ -271,19 +294,35 @@
     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;
   }
 
   .mq-large-operator {
+    vertical-align: -.2em;
+    padding: .2em;
     text-align: center;
 
     .mq-from, big, .mq-to  {
diff --git a/src/cursor.js b/src/cursor.js
index b08b2f6..622a023 100644
--- a/src/cursor.js
+++ b/src/cursor.js
@@ -56,7 +56,8 @@
     this[-dir] = oppDir;
     // by contract, .blur() is called after all has been said and done
     // and the cursor has actually been moved
-    if (oldParent !== parent && oldParent.blur) oldParent.blur();
+    // FIXME pass cursor to .blur() so text can fix cursor pointers when removing itself
+    if (oldParent !== parent && oldParent.blur) oldParent.blur(this);
   };
   _.insDirOf = function(dir, el) {
     prayDirection(dir);
diff --git a/src/font/Symbola.woff b/src/font/Symbola.woff
old mode 100755
new mode 100644
index b9bba23..858ca28
--- a/src/font/Symbola.woff
+++ b/src/font/Symbola.woff
Binary files differ
diff --git a/src/font/Symbola.woff2 b/src/font/Symbola.woff2
old mode 100755
new mode 100644
index 9d3e820..07a752d
--- a/src/font/Symbola.woff2
+++ b/src/font/Symbola.woff2
Binary files differ
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/publicapi.js b/src/publicapi.js
index 45b05e5..2ccdeb2 100644
--- a/src/publicapi.js
+++ b/src/publicapi.js
@@ -15,7 +15,7 @@
 function insistOnInterVer() {
   if (window.console) console.warn(
     'You are using the MathQuill API without specifying an interface version, ' +
-    'which will fail in v1.0.0. You can fix this easily by doing this before ' +
+    'which will fail in v1.0.0. Easiest fix is to do the following before ' +
     'doing anything else:\n' +
     '\n' +
     '    MathQuill = MathQuill.getInterface(1);\n' +
@@ -213,6 +213,19 @@
       var cmd = Embed().setOptions(options);
       cmd.createLeftOf(this.__controller.cursor);
     };
+    _.clickAt = function(clientX, clientY, target) {
+      target = target || document.elementFromPoint(clientX, clientY);
+
+      var ctrlr = this.__controller, root = ctrlr.root;
+      if (!jQuery.contains(root.jQ[0], target)) target = root.jQ[0];
+      ctrlr.seek($(target), clientX + pageXOffset, clientY + pageYOffset);
+      if (ctrlr.blurred) this.focus();
+      return this;
+    };
+    _.ignoreNextMousedown = function(fn) {
+      this.__controller.cursor.options.ignoreNextMousedown = fn;
+      return this;
+    };
   });
   MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; };
   MQ.EditableField.prototype = APIClasses.EditableField.prototype;
diff --git a/src/services/latex.js b/src/services/latex.js
index 025f1a8..75369e8 100644
--- a/src/services/latex.js
+++ b/src/services/latex.js
@@ -1,6 +1,6 @@
-// Parser MathCommand
+// Parser MathBlock
 var latexMathParser = (function() {
-  function commandToBlock(cmd) {
+  function commandToBlock(cmd) { // can also take in a Fragment
     var block = MathBlock();
     cmd.adopt(block, 0, 0);
     return block;
diff --git a/src/services/mouse.js b/src/services/mouse.js
index c060461..b2b543a 100644
--- a/src/services/mouse.js
+++ b/src/services/mouse.js
@@ -3,6 +3,7 @@
  *******************************************************/
 
 Controller.open(function(_) {
+  Options.p.ignoreNextMousedown = noop;
   _.delegateMouseEvents = function() {
     var ultimateRootjQ = this.root.jQ;
     //drag-to-select event handling
@@ -12,6 +13,12 @@
       var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
       var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
 
+      e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
+      e.target.unselectable = true; // http://jsbin.com/yagekiji/1
+
+      if (cursor.options.ignoreNextMousedown(e)) return;
+      else cursor.options.ignoreNextMousedown = noop;
+
       var target;
       function mousemove(e) { target = $(e.target); }
       function docmousemove(e) {
@@ -42,8 +49,6 @@
         if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
         textarea.focus();
       }
-      e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
-      e.target.unselectable = true; // http://jsbin.com/yagekiji/1
 
       cursor.blink = noop;
       ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
diff --git a/test/unit.html b/test/unit.html
index 322f89f..04fb3b6 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, function() {
+              window.mochaResults = runner.stats;
+              window.mochaResults.reports = failedTests;
+            });
+          });
+        });
+      }
+    </script>
   </body>
 </html>
diff --git a/test/unit/backspace.test.js b/test/unit/backspace.test.js
index 51a34ca..688d1ba 100644
--- a/test/unit/backspace.test.js
+++ b/test/unit/backspace.test.js
@@ -197,7 +197,7 @@
     assert.equal(mq.latex(),'n=1');
   });
 
-  test('backspace into text block', function() {
+  test('backspace through text block', function() {
     mq.latex('\\text{x}');
 
     mq.keystroke('Backspace');
@@ -206,6 +206,18 @@
     assert.equal(cursor.parent, textBlock, 'cursor is in text block');
     assert.equal(cursor[R], 0, 'cursor is at the end of text block');
     assert.equal(cursor[L].text, 'x', 'cursor is rightward of the x');
+    assert.equal(mq.latex(), '\\text{x}', 'the x has been deleted');
+
+    mq.keystroke('Backspace');
+    assert.equal(cursor.parent, textBlock, 'cursor is still in text block');
+    assert.equal(cursor[R], 0, 'cursor is at the right end of the text block');
+    assert.equal(cursor[L], 0, 'cursor is at the left end of the text block');
+    assert.equal(mq.latex(), '', 'the x has been deleted');
+
+    mq.keystroke('Backspace');
+    assert.equal(cursor[R], 0, 'cursor is at the right end of the root block');
+    assert.equal(cursor[L], 0, 'cursor is at the left end of the root block');
+    assert.equal(mq.latex(), '');
   });
 
   suite('empties', function() {
diff --git a/test/unit/css.test.js b/test/unit/css.test.js
index 223895f..639cdf9 100644
--- a/test/unit/css.test.js
+++ b/test/unit/css.test.js
@@ -52,5 +52,58 @@
     var mqF = $(mq.el()).find('.mq-f');
     var testVal = parseFloat(mqF.css('margin-right')) - parseFloat(mqF.css('margin-left'));
     assert.ok(testVal > 0, 'this should be truthy') ;
+
+    $(mq.el()).remove();
+  });
+
+  test('unary PlusMinus before separator', function () {
+    var mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
+    mq.latex('(-1,-1-1)-1,(+1;+1+1)+1,(\\pm1,\\pm1\\pm1)\\pm1');
+    var spans = $(mq.el()).find('.mq-root-block').find('span');
+    assert.equal(spans.length, 35, '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, '(-');
+    assertUnaryOperator(4, '(-1,-');
+    assertBinaryOperator(6, '(-1,-1-');
+    assertBinaryOperator(9, '(-1,-1-1)-');
+    assertUnaryOperator(13, '(-1,-1-1)-1,(+');
+    assertUnaryOperator(16, '(-1,-1-1)-1,(+1;+');
+    assertBinaryOperator(18, '(-1,-1-1)-1,(+1;+1+');
+    assertBinaryOperator(21, '(-1,-1-1)-1,(+1;+1+1)+');
+    assertUnaryOperator(25, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm');
+    assertUnaryOperator(28, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm');
+    assertBinaryOperator(30, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm');
+    assertBinaryOperator(33, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm1)\pm');
+
+    $(mq.el()).remove();
+  });
+
+  test('operator name spacing e.g. sin x', function() {
+    var mq = MathQuill.MathField($('<span></span>').appendTo(mock)[0]);
+
+    mq.typedText('sin');
+    var n = jQuery('#mock var.mq-operator-name:last');
+    assert.equal(n.text(), 'n');
+    assert.ok(!n.is('.mq-last'));
+
+    mq.typedText('x');
+    assert.ok(n.is('.mq-last'));
+
+    mq.keystroke('Left').typedText('(');
+    assert.ok(!n.is('.mq-last'));
+
+    mq.keystroke('Backspace').typedText('^');
+    assert.ok(!n.is('.mq-last'));
+    var supsub = jQuery('#mock .mq-supsub');
+    assert.ok(supsub.is('.mq-after-operator-name'));
+
+    mq.typedText('2').keystroke('Tab').typedText('(');
+    assert.ok(!supsub.is('.mq-after-operator-name'));
+
+    $(mq.el()).empty();
   });
 });
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 ded31aa..ab6d925 100644
--- a/test/unit/latex.test.js
+++ b/test/unit/latex.test.js
@@ -118,6 +118,7 @@
     assertParsesLatex('\\text { lol! } ', '\\text{ lol! }');
     assertParsesLatex('\\text{apples} \\ne \\text{oranges}',
                       '\\text{apples}\\ne \\text{oranges}');
+    assertParsesLatex('\\text{}', '');
   });
 
   test('not real LaTex commands, but valid symbols', function() {
diff --git a/test/unit/publicapi.test.js b/test/unit/publicapi.test.js
index a2c62c3..4791491 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;
@@ -744,6 +761,20 @@
 
       $(mq.el()).remove();
     });
+    test('integral still has empty limits', function() {
+      var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
+        sumStartsWithNEquals: true
+      });
+      assert.equal(mq.latex(), '');
+
+      mq.cmd('\\int');
+      assert.equal(mq.latex(), '\\int_{ }^{ }');
+
+      mq.cmd('0');
+      assert.equal(mq.latex(), '\\int_0^{ }', 'cursor in the from block');
+
+      $(mq.el()).remove();
+    });
   });
 
   suite('substituteTextarea', function() {
@@ -762,6 +793,58 @@
     });
   });
 
+  suite('clickAt', function() {
+    test('inserts at coordinates', function() {
+      // Insert filler so that the page is taller than the window so this test is deterministic
+      // Test that we use clientY instead of pageY
+      var windowHeight = $(window).height();
+      var filler = $('<div>').height(windowHeight);
+      filler.insertBefore('#mock');
+
+      var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
+      mq.typedText("mmmm/mmmm");
+      mq.el().scrollIntoView();
+
+      var box = mq.el().getBoundingClientRect();
+      var clientX = box.left + 30;
+      var clientY = box.top + 30;
+      var target = document.elementFromPoint(clientX, clientY);
+
+      assert.equal(document.activeElement, document.body);
+      mq.clickAt(clientX, clientY, target).write('x');
+      assert.equal(document.activeElement, $(mq.el()).find('textarea')[0]);
+
+      assert.equal(mq.latex(), "\\frac{mmmm}{mmxmm}");
+
+      filler.remove();
+      $(mq.el()).remove();
+    });
+    test('target is optional', function() {
+      // Insert filler so that the page is taller than the window so this test is deterministic
+      // Test that we use clientY instead of pageY
+      var windowHeight = $(window).height();
+      var filler = $('<div>').height(windowHeight);
+      filler.insertBefore('#mock');
+
+      var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
+      mq.typedText("mmmm/mmmm");
+      mq.el().scrollIntoView();
+
+      var box = mq.el().getBoundingClientRect();
+      var clientX = box.left + 30;
+      var clientY = box.top + 30;
+
+      assert.equal(document.activeElement, document.body);
+      mq.clickAt(clientX, clientY).write('x');
+      assert.equal(document.activeElement, $(mq.el()).find('textarea')[0]);
+
+      assert.equal(mq.latex(), "\\frac{mmmm}{mmxmm}");
+
+      filler.remove();
+      $(mq.el()).remove();
+    });
+  });
+
   suite('dropEmbedded', function() {
     test('inserts into empty', function() {
       var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
@@ -792,7 +875,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 9247803..158d425 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/text.test.js b/test/unit/text.test.js
index 6f2ecd3..982a5ac 100644
--- a/test/unit/text.test.js
+++ b/test/unit/text.test.js
@@ -65,4 +65,43 @@
 
     assert.equal(block.latex(), '\\text{x}');
   });
+
+  test('stepping out of an empty block deletes it', function() {
+    var mq = MathQuill.MathField($('<span></span>').appendTo('#mock')[0]);
+    var controller = mq.__controller;
+    var cursor = controller.cursor;
+
+    try {
+      mq.latex('\\text{x}');
+
+      mq.keystroke('Left');
+      assertSplit(cursor.jQ, 'x');
+
+      mq.keystroke('Backspace');
+      assertSplit(cursor.jQ);
+
+      mq.keystroke('Right');
+      assertSplit(cursor.jQ);
+      assert.equal(cursor[L], 0);
+    } finally {
+      $(mq.el()).remove();
+    }
+  });
+
+  test('typing $ in a textblock splits it', function() {
+    var mq = MathQuill.MathField($('<span></span>').appendTo('#mock')[0]);
+    var controller = mq.__controller;
+    var cursor = controller.cursor;
+
+    try {
+      mq.latex('\\text{asdf}');
+      mq.keystroke('Left Left Left');
+      assertSplit(cursor.jQ, 'as', 'df');
+
+      mq.typedText('$');
+      assert.equal(mq.latex(), '\\text{as}\\text{df}');
+    } finally {
+      $(mq.el()).remove();
+    }
+  });
 });
diff --git a/test/unit/typing.test.js b/test/unit/typing.test.js
index 3ba3b31..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() {
@@ -813,6 +818,7 @@
   suite('autoCommands', function() {
     setup(function() {
       MQ.config({
+        autoOperatorNames: 'sin pp',
         autoCommands: 'pi tau phi theta Gamma sum prod sqrt nthroot'
       });
     });
@@ -880,6 +886,13 @@
       assertLatex('\\sin\\pi');
     });
 
+    test('has lower "precedence" than operator names', function() {
+      mq.typedText('ppi');
+      assertLatex('\\operatorname{pp}i');
+      mq.keystroke('Left Left').typedText('i');
+      assertLatex('\\pi pi');
+    });
+
     test('command contains non-letters', function() {
       assert.throws(function() { MQ.config({ autoCommands: 'e1' }); });
     });
@@ -1026,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 });
@@ -1044,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 1a47eca..a5918b5 100644
--- a/test/visual.html
+++ b/test/visual.html
@@ -24,6 +24,9 @@
 td {

   width: 33%;

 }

+#static-latex-rendering-table td {

+  width: 50%;

+}

 #show-textareas-button {

   float: right;

 }

@@ -77,14 +80,14 @@
   <td><span class="mathquill-static-math">\sqrt{\MathQuillMathField{x^2+y^2}}</span>

 </table>

 

-<p>Clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center"><span class="mathquill-math-field">a_2 x^2 + a_1 x + a_0 = 0</span></div>

+<p>Touch taps/clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center; -webkit-tap-highlight-color: rgba(0,0,0,0)"><span class="mathquill-math-field">x_{very\ long\ thing}^2 + a_0 = 0</span></div>

 

 <h3>Redrawing</h3>

 <p>

-  <span id="reflowing-test">\sqrt{_0^1}</span>

+  <span id="reflowing-test">\sqrt{}</span>

   should look the same as

   <span class="mathquill-static-math">

-    \sqrt{\pi\sqrt\sqrt\frac12\int_0^1}

+    \sqrt{\pi\sqrt\sqrt\frac12}

   </span>

 </p>

 <script type="text/javascript">

@@ -96,7 +99,7 @@
     var textarea = $('#reflowing-test textarea');

     // paste some stuff that needs resizing

     textarea.trigger('paste');

-    textarea.val('\\pi\\sqrt{\\sqrt{\\frac12}}\\int');

+    textarea.val('\\pi\\sqrt{\\sqrt{\\frac12}}');

     setTimeout(function() { if (count !== 1) throw 'reflow not called'; });

   });

 </script>

@@ -158,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>

@@ -183,11 +184,22 @@
 </table>

 

 <h3>Static LaTeX rendering (<code>.mathquill-static-math</code>) tests</h3>

-<table>

+<table id="static-latex-rendering-table">

 <tr><td><span class="mathquill-static-math">^{\frac{as}{ }df}</span><td><span>^{\frac{as}{ }df}</span>

 <tr><td><span class="mathquill-static-math">e^{i\pi}+1=0</span><td><span>e^{i\pi}+1=0</span>

 <tr><td><span class="mathquill-static-math">\sqrt[n]{1}</span><td><span>\sqrt[n]{1}</span>

-<tr><td><span class="mathquill-static-math">\theta + \sin 2\deg</span><td><span>\theta + \sin 2\deg</span>

+<tr><td><span class="mathquill-static-math">\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span></td><td><span>\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span>

+<tr><td><span class="mathquill-static-math">12a\sin b</span></td><td><span>12a\sin b</span>

+<tr><td><span class="mathquill-static-math">1a^2 \sin b</span></td><td><span>1a^2 \sin b</span>

+<tr><td><span class="mathquill-static-math">a + \sin b</span></td><td><span>a + \sin b</span>

+<tr><td><span class="mathquill-static-math">a + b</span></td><td><span>a + b</span>

+<tr><td><span class="mathquill-static-math">\sum\sin</span></td><td><span>\sum\sin</span>

+<tr><td><span class="mathquill-static-math">\sum a</span></td><td><span>\sum a</span>

+<tr><td><span class="mathquill-static-math">(\sin)</span></td><td><span>(\sin)</span>

+<tr><td><span class="mathquill-static-math">\left(\sin\right)</span></td><td><span>\left(\sin\right)</span>

+<tr><td><span class="mathquill-static-math">(x)\sin(x)</span></td><td><span>(x)\sin(x)</span>

+<tr><td><span class="mathquill-static-math">\left(x\right)\sin\left(x\right)</span></td><td><span>\left(x\right)\sin\left(x\right)</span>

+<tr><td><span class="mathquill-static-math">a \sin b</span></td><td><span>a \sin b</span>

 <tr><td><span class="mathquill-static-math">-1 + +-2</span><td><span>-1 + +-2</span>

 <tr><td><span class="mathquill-static-math">\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span><td><span>\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span>

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

@@ -197,6 +209,23 @@
 <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>

+<script>

+$(function() {

+    var i,

+        sixes = '',

+        start = 10,

+        end = 40;

+    for (i = start; i <= end; i++) {

+        sixes += '<span style="font-size: ' + i + 'px" class="mathquill-math-field">6<\/span>';

+    }

+    $('#sixes').html(sixes);

+    $('#sixes .mathquill-math-field').each(function() { MQ.MathField(this); });

+});

+</script>

 </table>

 

 <p id="paren-alignment">Parentheses vertical alignment at font sizes ranging from 10px to 24px: <button>Click Me</button></p>

@@ -249,7 +278,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>

@@ -272,7 +302,25 @@
 // test selecting from outside the mathquill editable

 var $mq = $('.math-container .mathquill-math-field');

 $('.math-container').mousedown(function(e) {

-  if (!jQuery.contains($mq[0], e.target)) $mq.triggerHandler(e);

+  if (e.target === $mq[0] || $.contains($mq[0], e.target)) return;

+  $mq.triggerHandler(e);

+})

+// test API for "fast touch taps" #622 & #403

+.on('touchstart', function() {

+  var moved = false;

+  $(this).on('touchmove.tmp', function() { moved = true; })

+  .on('touchend.tmp', function(e) {

+    $(this).off('.tmp');

+    MathQuill($mq[0]).ignoreNextMousedown(function() {

+      return Date.now() < e.timeStamp + 1000;

+    });

+    if (moved) return; // note that this happens after .ignoreNextMousedown()

+      // because even if the touch gesture doesn't 'count' as a tap to us,

+      // we still want to suppress the legacy mouse events, else we'd react

+      // fast to some taps and slow to others, that'd be weird

+    var touch = e.originalEvent.changedTouches[0];

+    MathQuill($mq[0]).clickAt(touch.clientX, touch.clientY, touch.target);

+  });

 });

 

 // Selection Tests

@@ -281,7 +329,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);

     });

   });

 };