diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
 <h1 align="center">miso</h1>
 <p align="center">
+
 <a href="https://haskell-miso.org">
   <img width=10% src="https://emojipedia-us.s3.amazonaws.com/thumbs/240/apple/96/steaming-bowl_1f35c.png">
    </a>
@@ -27,21 +28,12 @@
   </a>
 </p>
 
-<p align="center">
-  <a href="https://saucelabs.com/u/dmjio">
-    <img src="https://saucelabs.com/browser-matrix/dmjio.svg" alt="Sauce Test Status"/>
-  </a>
-</p>
-
 **Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies. **Miso** can be considered a shallow [embedded domain-specific language](https://wiki.haskell.org/Embedded_domain_specific_language) for modern web programming.
 
 ## Table of Contents
 - [Quick Start](#quick-start)
   - [Begin](#begin)
-  - [Stack](#stack)
   - [Nix](#nix)
-  - [Cabal](#cabal)
-  - [GHCJSi Caveats](#ghcjsi-caveats)
   - [Architecture](#architecture)
 - [Examples](#examples)
   - [TodoMVC](#todomvc)
@@ -49,6 +41,7 @@
   - [2048](#2048)
   - [Snake](#snake)
   - [Mario](#mario)
+  - [Miso Plane (Flappy Birds)](#miso-plane-flappy-birds)
   - [Websocket](#websocket)
   - [SSE](#sse)
   - [XHR](#xhr)
@@ -74,152 +67,35 @@
 - [License](#license)
 
 ## Quick start
-To get started quickly building applications, we recommend using the [`stack`](https://docs.haskellstack.org/en/stable/README/) or [`nix`](https://nixos.org/nix) package managers. Obtaining [`GHCJS`](https://github.com/ghcjs/ghcjs) is required as a prerequisite. `stack` and `nix` make this process easy, if you're using `cabal` we assume you have [obtained `GHCJS`](https://github.com/ghcjs/ghcjs#installation) by other means.
-
-All source code depicted below for the quick start app is available [here](https://github.com/dmjio/miso/tree/master/sample-app).
+To get started quickly building applications, we recommend using the [`nix`](https://nixos.org/nix) package manager with miso's binary cache provided by [`cachix`](https://haskell-miso.cachix.org/). It is possible to use [`stack`](https://docs.haskellstack.org/en/stable/README/) to build GHCJS projects, but support for procuring `GHCJS` has been removed [as of stack 2.0](https://github.com/commercialhaskell/stack/issues/4086). `nix` is used to procure a working version of `GHCJS`. If you're using `cabal` we assume you have [obtained `GHCJS`](https://github.com/ghcjs/ghcjs#installation) by other means. All source code depicted below for the quick start app is available [here](https://github.com/dmjio/miso/tree/master/sample-app).
 
 ### Begin
-We recommend using `nix` when working with `miso`, but it is just as fine to use `stack`.
-To build the sample-app with `nix`, execute the command below:
-
-```bash
-git clone https://github.com/dmjio/miso && cd miso/sample-app && nix-build
-```
-
-To develop with `nix` run the below command (this will put you into a shell where you can iteratively develop the project with `cabal build`).
-
-```bash
-git clone https://github.com/dmjio/miso && cd miso/sample-app && nix-shell -A env
-```
-
-For more information on using `nix` w/ `miso`, see the [`nix` section below](#nix)
-
-To build the sample-app with `stack`, execute the command below:
-```bash
-git clone https://github.com/dmjio/miso && cd miso/sample-app && stack setup && stack build
-```
-
-Note: It's important to ensure that you don't have a global `cabal-install` present on your system. This could cause build problems. If you see an error like this (below), try deleting your global `cabal-install`.
+To build the sample-app with `nix`, execute the commands below:
 
 ```bash
-exit status: 1
-stderr: solver must be one of: modular
-CallStack (from HasCallStack):
-  error, called at libraries/Cabal/Cabal/Distribution/ReadE.hs:46:24 in Cabal-2.0.1.0:Distribution.ReadE
-```
-
-
-For more information on using `stack` w/ `miso`, see the [`stack` section below](#stack)
-
-
-### Stack
-In the `miso` repository there is a [folder named `stack`](https://github.com/dmjio/miso/tree/master/stack) with "known to work" configurations for `GHCJS`. One stack file exists for both the `7.10.3` and `8.0.1` versions of `GHCJS`. In general, we recommend developing with the `7.10.3` version since it currently supports `GHCJSi` (a REPL that connects to the browser by way of a [`nodejs`](https://nodejs.org/en/) web server using [`socket.io`](https://socket.io/)) and building with the `8.0.1` version (if possible). For more information on using `stack` with `GHCJS`, please consult the [GHCJS section of the `stack` docs](https://docs.haskellstack.org/en/stable/ghcjs/).
-
-To begin, create the following directory layout
-```bash
-➜  mkdir app && touch app/{Main.hs,app.cabal,stack.yaml} && tree app
-app
-|-- Main.hs
-|-- app.cabal
-`-- stack.yaml
-```
-
-Add a `stack.yaml` file that uses a recent version of `miso`.
-```bash
-➜  cat app/stack.yaml
-resolver: lts-6.30
-compiler: ghcjs-0.2.0.9006030_ghc-7.10.3
-compiler-check: match-exact
-
-packages:
- - '.'
-extra-deps:
- - miso-0.18.0.0
-
-setup-info:
-  ghcjs:
-	source:
-	  ghcjs-0.2.0.9006030_ghc-7.10.3:
-		 url: http://ghcjs.tolysz.org/lts-6.30-9006030.tar.gz
-		 sha1: 2371e2ffe9e8781808b7a04313e6a0065b64ee51
-```
-
-Add a `cabal` file
-```bash
-➜  cat app/*.cabal
-name:                app
-version:             0.1.0.0
-synopsis:            First miso app
-category:            Web
-build-type:          Simple
-cabal-version:       >=1.10
-
-executable app
-  main-is:             Main.hs
-  build-depends:       base, miso
-  default-language:    Haskell2010
-```
-
-Add the source from [Sample Application](#sample-application) to `app/Main.hs`
-
-Run `stack setup`. This might take a long time, since it will have to build `GHCJS`.
-```
-stack setup
-```
-
-Run `stack build` to get the static assets
-```
-stack build
-```
-
-See the result
-```
-open $(stack path --local-install-root)/bin/app.jsexe/index.html
-```
-
-Using GHCJSi
-```
-stack ghci
-```
-
-If that warns with `socket.io not found, browser session not available`, you'll need to install `socket.io`
-```
-npm install socket.io
-```
-
-and update your `NODE_PATH`
-```
-export NODE_PATH=$(pwd)/node_modules
+(optional) nix-env -iA cachix -f https://cachix.org/api/v1/install
+(optional) cachix use haskell-miso
+git clone https://github.com/dmjio/miso
+cd miso/sample-app
+nix-build
+open ./result/bin/app.jsexe/index.html
 ```
 
-Now you should be connected, and the app viewable in `GHCJSi` (open http://localhost:6400).
-```bash
-➜  stack ghci
-app-0.1.0.0: initial-build-steps (exe)
-Configuring GHCi with the following packages: app
-GHCJSi, version 0.2.0.9006020-7.10.3: http://www.github.com/ghcjs/ghcjs/  :? for help
-[1 of 1] Compiling Main             ( /Users/david/Desktop/miso/sample-app/Main.hs, interpreted )
-socket.io found, browser session available at http://localhost:6400
-Ok, modules loaded: Main.
-*Main> main
-browser connected, code runs in browser from now on
-```
+The above commands will add miso's binary cache to your nix installation (support for both Linux and OSX).
+`nix-build` will fetch the dependencies from miso's cache and build the sample application.
 
 ### Nix
 `Nix` is a more powerful option for building web applications with `miso` since it encompasses development workflow, configuration management, and deployment. The source code for [`haskell-miso.org`](https://github.com/dmjio/miso/tree/master/examples/haskell-miso.org) is an example of this.
 
 If unfamiliar with `nix`, we recommend [@Gabriel439](https://github.com/Gabriel439)'s ["Nix and Haskell in production"](https://github.com/Gabriel439/haskell-nix) guide.
 
-To get started, we will use the [`cabal2nix`](https://github.com/NixOS/cabal2nix) tool to convert our `Cabal` file into a `nix` derivation (named `app.nix`). We'll then write a file named `default.nix`, which is used for building our project (via `nix-build`) and development (via `nix-shell`).
-
 To begin, make the following directory layout:
 ```bash
-➜  mkdir app && touch app/{Main.hs,app.cabal,default.nix,app.nix} && tree app
+➜  mkdir app && touch app/{Main.hs,app.cabal,default.nix} && tree app
 app
 |-- Main.hs
 |-- app.cabal
-|-- default.nix
-`-- app.nix
+`-- default.nix
 ```
 
 Add a `cabal` file
@@ -234,50 +110,21 @@
 
 executable app
   main-is:             Main.hs
+  ghcjs-options:
+    -dedupe
   build-depends:       base, miso
   default-language:    Haskell2010
 ```
 
-Use [`cabal2nix`](https://github.com/NixOS/cabal2nix) to generate a file named `app.nix`
-that looks like below.
-```bash
-➜  cabal2nix . --compiler ghcjs > app.nix
-➜  cat app.nix
-```
+Write a `default.nix` (this will fetch a recent version of `miso`). `miso` will provide you with a working `nixpkgs` named `pkgs`. `callCabal2nix` will automatically produce a nix expression that builds your cabal file.
 
 ```nix
-{ mkDerivation, base, miso, stdenv }:
-mkDerivation {
-  pname = "app";
-  version = "0.1.0.0";
-  src = ./.;
-  isLibrary = false;
-  isExecutable = true;
-  executableHaskellDepends = [ base miso ];
-  description = "First miso app";
-  license = stdenv.lib.licenses.bsd3;
-}
-```
-
-Write a `default.nix` (which calls `app.nix`), this fetches a recent version of `miso`.
-```nix
-{ pkgs ? import ((import <nixpkgs> {}).fetchFromGitHub {
-	owner = "NixOS";
-	repo = "nixpkgs";
-	rev = "a0aeb23";
-	sha256 = "04dgg0f2839c1kvlhc45hcksmjzr8a22q1bgfnrx71935ilxl33d";
-  }){}
-}:
-let
-  result = import (pkgs.fetchFromGitHub {
-	owner = "dmjio";
-	repo = "miso";
-	sha256 = "1l1gwzzqlvvcmg70jjrwc5ijv1vb6y5ljqkh7rxxq7hkyxpjyx9q";
-	rev = "95f6bc9b1ae6230b110358a82b6a573806f272c2";
-  }) {};
-in pkgs.haskell.packages.ghcjs.callPackage ./app.nix {
-  miso = result.miso-ghcjs;
-}
+with (import (builtins.fetchTarball {
+  url = "https://github.com/dmjio/miso/archive/b4c473f3ed6d6251ea7b8b489fc50076ac8d9b70.tar.gz";
+  sha256 = "11rby2s0hxbl28a4fcwdm9lcbjfysv862xd6b9jy0rgl63dh51i3";
+}) {});
+with pkgs.haskell.packages;
+ghcjs.callCabal2nix "app" ./. {}
 ```
 
 Add the source from [Sample Application](#sample-application) to `app/Main.hs`
@@ -292,7 +139,7 @@
 open ./result/bin/app.jsexe/index.html
 ```
 
-For development with `nix`, it's important to have `cabal` present for building. This command will make it available in your `PATH`.
+For development with `nix`, it can be nice to have `cabal` present for building. This command will make it available in your `PATH`.
 ```
 nix-env -iA cabal-install -f '<nixpkgs>'
 ```
@@ -302,37 +149,20 @@
 nix-shell -A env
 ```
 
-To open `GHCJSi` (`NODE_PATH` should already be set properly)
+To view the dependencies for your project, call `ghcjs-pkg list` when inside the shell.
 ```
-$ cabal configure --ghcjs
-$ cabal repl
-Package has never been configured. Configuring with default flags. If this
-fails, please run configure manually.
-Resolving dependencies...
-Configuring app-0.1.0.0...
-Preprocessing executable 'app' for app-0.1.0.0...
-GHCJSi, version 0.2.0-7.10.3: http://www.github.com/ghcjs/ghcjs/  :? for help
-[1 of 1] Compiling Main             ( Main.hs, interpreted )
-Ok, modules loaded: Main.
-*Main>
-browser connected, code runs in browser from now on
+nix-shell -A env --run 'ghcjs-pkg list'
 ```
 
-### Cabal
-The latest stable version of `miso` will be available on Hackage.
-To build with cabal, we assume `ghcjs` is in your `PATH` and `ghcjs-base` is present in your `ghcjs-pkg` list.
-```bash
-cabal sandbox init
-cabal install --ghcjs
-cabal build
-open dist/build/app/app.jsexe/index.html
+To build the project with `cabal` after entering the `nix-shell`
 ```
+nix-shell -A env --run 'cabal configure --ghcjs && cabal build`
+```
 
-### GHCJSi Caveats
-If you run `main` in `GHCJSi`, interrupt it and then run it again, you
-will end up with two copies of your app displayed above each other. As
-a workaround, you can use `clearBody >> main` which will completely
-clear the document body before rendering your application.
+For incremental development inside of the `nix-shell` we recommend using a tool like [`entr`](http://eradman.com/entrproject/) to automatically rebuild on file changes, or roll your own solution with `inotify`.
+```
+ag -l | entr 'cabal build'
+```
 
 ### Architecture
 For constructing client and server applications, we recommend using one `cabal` file with two executable sections, where the `buildable` attribute set is contingent on the compiler. An example of this layout is [here](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/haskell-miso.cabal#L16-L60). For more info on how to use `stack` with a `client`/`server` setup, see this [link](https://docs.haskellstack.org/en/stable/ghcjs/#project-with-both-client-and-server). For more information on how to use `nix` with a `client`/`server` setup, see the [nix scripts](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/default.nix) for [https://haskell-miso.org](https://haskell-miso.org).
@@ -354,6 +184,9 @@
 ### Mario
   - [Link](https://mario.haskell-miso.org/) / [Source](https://github.com/dmjio/miso/blob/master/examples/mario/Main.hs)
 
+### Miso Plane (Flappy Birds)
+  - [Link](https://miso-plane.lermex.net/) / [Source](https://github.com/Lermex/miso-plane)
+
 ### Websocket
   - [Link](https://websocket.haskell-miso.org/) / [Source](https://github.com/dmjio/miso/blob/master/examples/websocket/Main.hs)
 
@@ -449,39 +282,33 @@
 
 The easiest way to build the examples is with the [`nix`](https://nixos.org/nix/) package manager
 ```
-git clone https://github.com/dmjio/miso && cd miso && nix-build
+git clone https://github.com/dmjio/miso && cd miso && nix-build -A miso-ghcjs
 ```
 
 This will build all examples and documentation into a folder named `result`
 ```
-➜  miso git:(master) ✗ tree result -d
-result
-|-- doc
-|   |-- x86_64-osx-ghc-8.0.2
-|   |   `-- miso-0.2.0.0
-|   |       `-- html
-|   |           `-- src
-|   `-- x86_64-osx-ghcjs-0.2.0-ghc7_10_3
-|       `-- miso-0.2.0.0
-|           `-- html
-|               `-- src
-|-- examples
-|   |-- mario.jsexe
-|   |   `-- imgs
-|   |       |-- jump
-|   |       |-- stand
-|   |       `-- walk
-|   |-- router.jsexe
-|   |-- simple.jsexe
-|   |-- tests.jsexe
-|   |-- todo-mvc.jsexe
-|   `-- websocket.jsexe
+➜  miso git:(master) ✗ tree -d ./result/bin
+./result/bin
+|-- canvas2d.jsexe
+|-- compose-update.jsexe
+|-- file-reader.jsexe
+|-- mario.jsexe
+|   `-- imgs
+|-- mathml.jsexe
+|-- router.jsexe
+|-- simple.jsexe
+|-- svg.jsexe
+|-- tests.jsexe
+|-- threejs.jsexe
+|-- todo-mvc.jsexe
+|-- websocket.jsexe
+`-- xhr.jsexe
 ```
 
 To see examples, we recommend hosting them with a webserver
 
 ```
-cd result/examples/todo-mvc.jsexe && python -m SimpleHTTPServer
+cd result/bin/todo-mvc.jsexe && nix-shell -p python --run 'python -m SimpleHTTPServer'
 Serving HTTP on 0.0.0.0 port 8000 ...
 ```
 
@@ -510,25 +337,14 @@
 
 ## Pinning nixpkgs
 
-By default `miso` uses a known-to-work, pinned version of [`nixpkgs`](https://github.com/dmjio/miso/blob/master/default.nix#L1-L6).
-To override this to your system's version of `nixpkgs` write:
-
-```
-nix-build --arg nixpkgs 'import <nixpkgs> {}'
-```
+By default `miso` uses a known-to-work, pinned version of [`nixpkgs`](https://github.com/dmjio/miso/blob/master/nixpkgs.json).
 
 ## Binary cache
 
-`nix` users on a Linux distro can take advantage of a [binary cache](https://hydra.dmj.io/nix-cache-info) for faster builds. To use the binary cache simply append `https://hydra.dmj.io/nix-cache-info` during all `nix-shell` and/or `nix-build` invocations.
+`nix` users on a Linux or OSX distro can take advantage of a [binary cache](https://hydra.dmj.io/nix-cache-info) for faster builds. To use the binary cache follow the instructions on `https://haskell-miso.cachix.org/`.
 
 ```bash
-nix-build --option extra-binary-caches https://hydra.dmj.io
-```
-
-Alternatively, add `https://hydra.dmj.io` to your list of local binary caches in `nix.conf` (usually found in `/etc/nix/nix.conf`), and it will automatically be used on all invocations of `nix-build` and/or `nix-shell`.
-
-```
-binary-caches = https://hydra.dmj.io/ https://cache.nixos.org/
+cachix use haskell-miso
 ```
 
 ## Benchmarks
diff --git a/frontend-src/Miso.hs b/frontend-src/Miso.hs
--- a/frontend-src/Miso.hs
+++ b/frontend-src/Miso.hs
@@ -34,6 +34,7 @@
 import           Data.IORef
 import           Data.List
 import           Data.Sequence ((|>))
+import           System.Mem.StableName
 import qualified Data.Sequence as S
 import qualified JavaScript.Object.Internal as OI
 
@@ -102,7 +103,9 @@
         let (Acc newModel effects) = foldl' (foldEffects writeEvent update)
                                             (Acc oldModel (pure ())) actions
         effects
-        when (oldModel /= newModel) $ do
+        oldName <- liftIO $ oldModel `seq` makeStableName oldModel
+        newName <- liftIO $ newModel `seq` makeStableName newModel
+        when (oldName /= newName && oldModel /= newModel) $ do
           swapCallbacks
           newVTree <- runView (view newModel) writeEvent
           oldVTree <- liftIO (readIORef viewRef)
@@ -122,8 +125,9 @@
   common app model $ \writeEvent -> do
     let initialView = view model
     VTree (OI.Object iv) <- flip runView writeEvent initialView
+    mountEl <- mountElement mountPoint
     -- Initial diff can be bypassed, just copy DOM into VTree
-    copyDOMIntoVTree iv
+    copyDOMIntoVTree mountEl iv
     let initialVTree = VTree (OI.Object iv)
     -- Create virtual dom, perform initial diff
     liftIO (newIORef initialVTree)
diff --git a/frontend-src/Miso/Html/Internal.hs b/frontend-src/Miso/Html/Internal.hs
--- a/frontend-src/Miso/Html/Internal.hs
+++ b/frontend-src/Miso/Html/Internal.hs
@@ -258,6 +258,9 @@
 
 -- | @onCreated action@ is an event that gets called after the actual DOM
 -- element is created.
+--
+-- Important note: Any node that uses this event MUST have a unique @Key@,
+-- otherwise the event may not be reliably called!
 onCreated :: action -> Attribute action
 onCreated action =
   Attribute $ \sink n -> do
@@ -268,6 +271,9 @@
 -- | @onDestroyed action@ is an event that gets called after the DOM element
 -- is removed from the DOM. The @action@ is given the DOM element that was
 -- removed from the DOM tree.
+--
+-- Important note: Any node that uses this event MUST have a unique @Key@,
+-- otherwise the event may not be reliably called!
 onDestroyed :: action -> Attribute action
 onDestroyed action =
   Attribute $ \sink n -> do
diff --git a/frontend-src/Miso/String.hs b/frontend-src/Miso/String.hs
--- a/frontend-src/Miso/String.hs
+++ b/frontend-src/Miso/String.hs
@@ -84,7 +84,7 @@
   fromMisoString = realToFrac . jsStringToDouble
 instance ToMisoString Double where
   toMisoString = realFloatToJSString
-  fromMisoString =  jsStringToDouble
+  fromMisoString = jsStringToDouble
 instance ToMisoString Int where
   toMisoString = integralToJSString
   fromMisoString = round . jsStringToDouble
diff --git a/frontend-src/Miso/Subscription/Keyboard.hs b/frontend-src/Miso/Subscription/Keyboard.hs
--- a/frontend-src/Miso/Subscription/Keyboard.hs
+++ b/frontend-src/Miso/Subscription/Keyboard.hs
@@ -81,6 +81,7 @@
   keySetRef <- liftIO (newIORef mempty)
   windowAddEventListener "keyup" $ keyUpCallback keySetRef
   windowAddEventListener "keydown" $ keyDownCallback keySetRef
+  windowAddEventListener "blur" $ blurCallback keySetRef
     where
       keyDownCallback keySetRef = \keyDownEvent -> do
           Just key <- fromJSVal =<< getProp "keyCode" (Object keyDownEvent)
@@ -94,4 +95,12 @@
           newKeys <- liftIO $ atomicModifyIORef' keySetRef $ \keys ->
              let !new = S.delete key keys
              in (new, new)
+          liftIO (sink (f newKeys))
+
+      -- Assume keys are released the moment focus is lost. Otherwise going
+      -- back and forth to the app can cause keys to get stuck.
+      blurCallback keySetRef = \_ -> do
+          newKeys <- liftIO $ atomicModifyIORef' keySetRef $ \_ ->
+            let !new = S.empty
+            in (new, new)
           liftIO (sink (f newKeys))
diff --git a/ghc-src/Miso/String.hs b/ghc-src/Miso/String.hs
--- a/ghc-src/Miso/String.hs
+++ b/ghc-src/Miso/String.hs
@@ -54,3 +54,17 @@
 instance ToMisoString BL.ByteString where
   toMisoString = toMisoString . LT.decodeUtf8
   fromMisoString = LT.encodeUtf8 . fromMisoString
+instance ToMisoString Float where
+  toMisoString = T.pack . show
+  fromMisoString = read . T.unpack
+instance ToMisoString Double where
+  toMisoString = T.pack . show
+  fromMisoString = read . T.unpack
+instance ToMisoString Int where
+  toMisoString = T.pack . show
+  -- Replicate frontend behavior
+  fromMisoString = round . (read :: String -> Double) . T.unpack
+instance ToMisoString Word where
+  toMisoString = T.pack . show
+  -- Replicate frontend behavior
+  fromMisoString = round . (read :: String -> Double) . T.unpack
diff --git a/ghcjs-ffi/Miso/FFI.hs b/ghcjs-ffi/Miso/FFI.hs
--- a/ghcjs-ffi/Miso/FFI.hs
+++ b/ghcjs-ffi/Miso/FFI.hs
@@ -119,11 +119,11 @@
 
 
 -- | Retrieves inner height
-foreign import javascript unsafe "$r = window.innerHeight;"
+foreign import javascript unsafe "$r = window['innerHeight'];"
   windowInnerHeight :: IO Int
 
 -- | Retrieves outer height
-foreign import javascript unsafe "$r = window.innerWidth;"
+foreign import javascript unsafe "$r = window['innerWidth'];"
   windowInnerWidth :: IO Int
 
 -- | Retrieve high performance time stamp
@@ -162,7 +162,7 @@
   clearBody :: IO ()
 
 
-foreign import javascript unsafe "$r = objectToJSON($1,$2);"
+foreign import javascript unsafe "$r = window['objectToJSON']($1,$2);"
   objectToJSON
     :: JSVal -- ^ decodeAt :: [JSString]
     -> JSVal -- ^ object with impure references to the DOM
@@ -201,7 +201,7 @@
     callFunction continuation res
   delegateEvent' mountPoint events cb'
 
-foreign import javascript unsafe "delegate($1, $2, $3);"
+foreign import javascript unsafe "window['delegate']($1, $2, $3);"
   delegateEvent'
      :: JSVal               -- ^ mountPoint element
      -> JSVal               -- ^ Events
@@ -213,35 +213,38 @@
 
 -- | Copies DOM pointers into virtual dom
 -- entry point into isomorphic javascript
-foreign import javascript unsafe "copyDOMIntoVTree($1);"
-  copyDOMIntoVTree :: JSVal -> IO ()
+foreign import javascript unsafe "window['copyDOMIntoVTree']($1, $2);"
+  copyDOMIntoVTree
+    :: JSVal -- ^ mountPoint element of the isomorphic app
+    -> JSVal -- ^ VDom object
+    -> IO ()
 
 -- | Pins down the current callbacks for clearing later
-foreign import javascript unsafe "swapCallbacks();"
+foreign import javascript unsafe "window['swapCallbacks']();"
   swapCallbacks :: IO ()
 
 -- | Releases callbacks registered by the virtual DOM.
-foreign import javascript unsafe "releaseCallbacks();"
+foreign import javascript unsafe "window['releaseCallbacks']();"
   releaseCallbacks :: IO ()
 
-foreign import javascript unsafe "registerCallback($1);"
+foreign import javascript unsafe "window['registerCallback']($1);"
   registerCallback :: JSVal -> IO ()
 
 -- | Fails silently if the element is not found.
 --
 -- Analogous to @document.getElementById(id).focus()@.
-foreign import javascript unsafe "callFocus($1);"
+foreign import javascript unsafe "window['callFocus']($1);"
   focus :: JSString -> JSM ()
 
 -- | Fails silently if the element is not found.
 --
 -- Analogous to @document.getElementById(id).blur()@
-foreign import javascript unsafe "callBlur($1);"
+foreign import javascript unsafe "window['callBlur']($1);"
   blur :: JSString -> JSM ()
 
 -- | Calls @document.getElementById(id).scrollIntoView()@
 foreign import javascript unsafe
-  "document.getElementById($1).scrollIntoView();"
+  "document.getElementById($1)['scrollIntoView']();"
   scrollIntoView :: JSString -> IO ()
 
 -- | Calls the @alert()@ function.
diff --git a/jsaddle-ffi/Miso/FFI.hs b/jsaddle-ffi/Miso/FFI.hs
--- a/jsaddle-ffi/Miso/FFI.hs
+++ b/jsaddle-ffi/Miso/FFI.hs
@@ -199,8 +199,8 @@
 
 -- | Copies DOM pointers into virtual dom
 -- entry point into isomorphic javascript
-copyDOMIntoVTree :: JSVal -> JSM ()
-copyDOMIntoVTree a = () <$ jsg1 "copyDOMIntoVTree" a
+copyDOMIntoVTree :: JSVal -> JSVal -> JSM ()
+copyDOMIntoVTree mountPoint a = () <$ jsg1 "copyDOMIntoVTree" mountPoint a
 
 -- TODO For now, we do not free callbacks when compiling with JSaddle
 
diff --git a/jsbits/delegate.js b/jsbits/delegate.js
--- a/jsbits/delegate.js
+++ b/jsbits/delegate.js
@@ -1,111 +1,107 @@
-window.oldCallbacks = [];
-window.currentCallbacks = [];
+window['oldCallbacks'] = [];
+window['currentCallbacks'] = [];
 
 /* Callbacks in ghcjs need to be released. With this function one can register
    callbacks that should be released right before diffing.
 */
-window.registerCallback = function registerCallback(cb) {
-    currentCallbacks.push(cb);
+window['registerCallback'] = function registerCallback(cb) {
+  window['currentCallbacks'].push(cb);
 };
 
 /* Swaps out the new calbacks for old callbacks.
 The old callbacks should be cleared once the new callbacks have replaced them.
 */
-window.swapCallbacks = function swapCallbacks() {
-  oldCallbacks = currentCallbacks;
-  currentCallbacks = [];
+window['swapCallbacks'] = function swapCallbacks() {
+  window['oldCallbacks'] = window['currentCallbacks'];
+  window['currentCallbacks'] = [];
 };
 
 /* This releases the old callbacks. */
-window.releaseCallbacks = function releaseCallbacks() {
-    for (var i in oldCallbacks)
-      h$release(oldCallbacks[i]);
+window['releaseCallbacks'] = function releaseCallbacks() {
+  for (var i in window['oldCallbacks'])
+    h$release(window['oldCallbacks'][i]);
 
-    oldCallbacks = [];
+  window['oldCallbacks'] = [];
 };
 
 /* event delegation algorithm */
-window.delegate = function delegate(mountPointElement, events, getVTree) {
-    for (var event in events) {
-	  mountPointElement.addEventListener(events[event][0], function(e) {
-        getVTree(function (obj) {
-          delegateEvent ( e
-                          , obj
-                          , buildTargetToElement(mountPointElement, e.target)
-                          , []
-                        );
-        });
-	     }, events[event][1]);
-    }
+window['delegate'] = function delegate(mountPointElement, events, getVTree) {
+  for (var event in events) {
+    mountPointElement.addEventListener(events[event][0], function(e) {
+      getVTree(function (obj) {
+        window['delegateEvent'](e, obj, window['buildTargetToElement'](mountPointElement, e.target), []);
+      });
+    }, events[event][1]);
+  }
 };
 
 /* Accumulate parent stack as well for propagation */
-window.delegateEvent = function delegateEvent (event, obj, stack, parentStack) {
+window['delegateEvent'] = function delegateEvent (event, obj, stack, parentStack) {
 
-    /* base case, not found */
-    if (!stack.length) return;
+  /* base case, not found */
+  if (!stack.length) return;
 
-    /* stack not length 1, recurse */
-    else if (stack.length > 1) {
-      if (obj.domRef === stack[0]) parentStack.unshift(obj);
-	for (var o = 0; o < obj.children.length; o++) {
-          if (obj.children[o].type === "vtext") continue;
-          delegateEvent ( event
-                        , obj.children[o]
-                        , stack.slice(1)
-                        , parentStack
-			                  );
-       }
-    }
+  /* stack not length 1, recurse */
+  else if (stack.length > 1) {
+    if (obj['domRef'] === stack[0]) parentStack.unshift(obj);
+    for (var o = 0; o < obj.children.length; o++) {
+      if (obj.children[o]['type'] === "vtext") continue;
+      delegateEvent ( event
+                    , obj.children[o]
+                    , stack.slice(1)
+                    , parentStack
+                    );
+     }
+  }
 
-    /* stack.length == 1 */
-    else {
-	if (obj.domRef === stack[0]) {
-	    var eventObj = obj.events[event.type];
-	    if (eventObj) {
-		var options = eventObj.options;
-		if (options.preventDefault)
-		    event.preventDefault();
-		eventObj.runEvent(event);
-		if (!options.stopPropagation)
-		    propogateWhileAble (parentStack, event);
-	    } else {
-		 /* still propagate to parent handlers even if event not defined */
- 		 propogateWhileAble (parentStack, event);
-	      }
-	}
+  /* stack.length == 1 */
+  else {
+    if (obj['domRef'] === stack[0]) {
+      var eventObj = obj['events'][event.type];
+      if (eventObj) {
+        var options = eventObj.options;
+      if (options['preventDefault'])
+        event.preventDefault();
+      eventObj['runEvent'](event);
+      if (!options['stopPropagation'])
+        window['propogateWhileAble'] (parentStack, event);
+      } else {
+        /* still propagate to parent handlers even if event not defined */
+        window['propogateWhileAble'] (parentStack, event);
+      }
     }
+  }
 };
 
-window.buildTargetToElement = function buildTargetToElement (element, target) {
-    var stack = [];
-    while (element !== target) {
-      stack.unshift (target);
-      target = target.parentNode;
-    }
-    return stack;
+window['buildTargetToElement'] = function buildTargetToElement (element, target) {
+  var stack = [];
+  while (element !== target) {
+    stack.unshift (target);
+    target = target.parentNode;
+  }
+  return stack;
 };
 
-window.propogateWhileAble = function propogateWhileAble (parentStack, event) {
+window['propogateWhileAble'] = function propogateWhileAble (parentStack, event) {
   for (var i = 0; i < parentStack.length; i++) {
-    if (parentStack[i].events[event.type]) {
-      var eventObj = parentStack[i].events[event.type],
-          options = eventObj.options;
-        if (options.preventDefault) event.preventDefault();
-        eventObj.runEvent(event);
-  	if (options.stopPropagation) break;
+    if (parentStack[i]['events'][event.type]) {
+      var eventObj = parentStack[i]['events'][event.type],
+          options = eventObj['options'];
+      if (options['preventDefault']) event.preventDefault();
+      eventObj['runEvent'](event);
+      if (options['stopPropagation']) break;
     }
   }
 };
 
 /* Walks down obj following the path described by `at`, then filters primitive
  values (string, numbers and booleans)*/
-window.objectToJSON = function objectToJSON (at, obj) {
+window['objectToJSON'] = function objectToJSON (at, obj) {
   /* If at is of type [[MisoString]] */
   if (typeof at[0] == "object") {
     var ret = [];
     for (var i = 0; i < at.length; i++)
-      ret.push(objectToJSON(at[i], obj));
+      ret.push(window['objectToJSON'](at[i], obj));
     return (ret);
   }
 
@@ -115,13 +111,13 @@
   if (obj instanceof Array) {
     var newObj = [];
     for (var i = 0; i < obj.length; i++)
-      newObj.push(objectToJSON([], obj[i]));
+      newObj.push(window['objectToJSON']([], obj[i]));
     return (newObj);
   }
 
   /* If obj is a non-list-like object */
   var newObj = {};
-    for (var i in obj){
+  for (var i in obj){
     /* bug in safari, throws TypeError if the following fields are referenced on a checkbox */
     /* https://stackoverflow.com/a/25569117/453261 */
     /* https://html.spec.whatwg.org/multipage/input.html#do-not-apply */
@@ -129,6 +125,6 @@
       continue;
     if (typeof obj[i] == "string" || typeof obj[i] == "number" || typeof obj[i] == "boolean")
       newObj[i] = obj[i];
-    }
-    return newObj;
+  }
+  return newObj;
 };
diff --git a/jsbits/diff.js b/jsbits/diff.js
--- a/jsbits/diff.js
+++ b/jsbits/diff.js
@@ -1,347 +1,346 @@
 /* virtual-dom diffing algorithm, applies patches as detected */
-window.diff = function diff(currentObj, newObj, parent, doc) {
-    if (!currentObj && !newObj) return;
-    else if (!currentObj && newObj) createNode(newObj, parent, doc);
-    else if (currentObj && !newObj) destroyNode(currentObj, parent);
-    else {
-	if (currentObj.type === "vtext") {
-	    if (newObj.type === "vnode") replaceTextWithElement(currentObj, newObj, parent, doc);
-	    else diffTextNodes(currentObj, newObj);
-	} else {
-	    if (newObj.type === "vnode") diffVNodes(currentObj, newObj, parent, doc);
-	    else replaceElementWithText(currentObj, newObj, parent, doc);
-	}
+window['diff'] = function diff(currentObj, newObj, parent, doc) {
+  if (!currentObj && !newObj) return;
+  else if (!currentObj && newObj) window['createNode'](newObj, parent, doc);
+  else if (currentObj && !newObj) window['destroyNode'](currentObj, parent);
+  else {
+    if (currentObj.type === "vtext") {
+      if (newObj.type === "vnode") window['replaceTextWithElement'](currentObj, newObj, parent, doc);
+      else window['diffTextNodes'](currentObj, newObj);
+    } else {
+      if (newObj.type === "vnode") window['diffVNodes'](currentObj, newObj, parent, doc);
+      else window['replaceElementWithText'](currentObj, newObj, parent, doc);
     }
+  }
 };
 
-window.destroyNode = function destroyNode(obj, parent) {
-    parent.removeChild(obj.domRef);
-    callDestroyedRecursive(obj);
+window['destroyNode'] = function destroyNode(obj, parent) {
+  parent.removeChild(obj['domRef']);
+  window['callDestroyedRecursive'](obj);
 };
 
-window.callDestroyedRecursive = function callDestroyedRecursive(obj) {
-    callDestroyed(obj);
-    for (var i in obj.children)
-	callDestroyedRecursive(obj.children[i]);
+window['callDestroyedRecursive'] = function callDestroyedRecursive(obj) {
+  window['callDestroyed'](obj);
+  for (var i in obj.children)
+    window['callDestroyedRecursive'](obj.children[i]);
 };
 
-window.callDestroyed = function callDestroyed(obj) {
-    if (obj.onDestroyed) obj.onDestroyed();
+window['callDestroyed'] = function callDestroyed(obj) {
+  if (obj['onDestroyed']) obj['onDestroyed']();
 };
 
-window.diffTextNodes = function diffTextNodes(c, n) {
-    if (c.text !== n.text) c.domRef.textContent = n.text;
-    n.domRef = c.domRef;
+window['diffTextNodes'] = function diffTextNodes(c, n) {
+  if (c['text'] !== n['text']) c['domRef'].textContent = n['text'];
+  n['domRef'] = c['domRef'];
 };
 
-window.replaceElementWithText = function replaceElementWithText(c, n, parent, doc) {
-    n.domRef = doc.createTextNode(n.text);
-    parent.replaceChild(n.domRef, c.domRef);
-    callDestroyedRecursive(c);
+window['replaceElementWithText'] = function replaceElementWithText(c, n, parent, doc) {
+  n['domRef'] = doc.createTextNode(n['text']);
+  parent.replaceChild(n['domRef'], c['domRef']);
+  window['callDestroyedRecursive'](c);
 };
 
-window.replaceTextWithElement = function replaceTextWithElement(c, n, parent, doc) {
-    createElement(n, doc);
-    parent.replaceChild(n.domRef, c.domRef);
-    callCreated(n);
+window['replaceTextWithElement'] = function replaceTextWithElement(c, n, parent, doc) {
+  window['createElement'](n, doc);
+  parent.replaceChild(n['domRef'], c['domRef']);
+  window['callCreated'](n);
 };
 
-window.callCreated = function callCreated(obj) {
-    if (obj.onCreated) obj.onCreated();
+window['callCreated'] = function callCreated(obj) {
+  if (obj['onCreated']) obj['onCreated']();
 };
 
-window.populate = function populate(c, n, doc) {
-    if (!c) c = {
-	props: null,
-	css: null,
-	children: []
-    }
-    diffProps(c.props, n.props, n.domRef, n.ns === "svg");
-    diffCss(c.css, n.css, n.domRef);
-    diffChildren(c.children, n.children, n.domRef, doc);
+window['populate'] = function populate(c, n, doc) {
+  if (!c) c = {
+              props: null,
+              css: null,
+              children: []
+              }
+  window['diffProps'](c['props'], n['props'], n['domRef'], n['ns'] === "svg");
+  window['diffCss'](c['css'], n['css'], n['domRef']);
+  window['diffChildren'](c['children'], n['children'], n['domRef'], doc);
 };
 
-window.diffVNodes = function diffVNodes(c, n, parent, doc) {
-    if (c.tag === n.tag && n.key === c.key) {
-	n.domRef = c.domRef;
-	populate(c, n, doc);
-    } else {
-	createElement(n, doc);
-	parent.replaceChild(n.domRef, c.domRef);
-	callDestroyedRecursive(c);
-	callCreated(n);
-    }
+window['diffVNodes'] = function diffVNodes(c, n, parent, doc) {
+  if (c['tag'] === n['tag'] && n['key'] === c['key']) {
+    n['domRef'] = c['domRef'];
+    window['populate'](c, n, doc);
+  } else {
+    window['createElement'](n, doc);
+    parent.replaceChild(n['domRef'], c['domRef']);
+    window['callDestroyedRecursive'](c);
+    window['callCreated'](n);
+  }
 };
 
-window.diffProps = function diffProps(cProps, nProps, node, isSvg) {
-    var result, newProp, domProp;
-    /* Is current prop in new prop list? */
-    for (var c in cProps) {
-	newProp = nProps[c];
-	/* If current property no longer exists, remove it */
-	if (!newProp) {
-	    /* current key is not in node, remove it from DOM, if SVG, remove attribute */
-	    if (isSvg || !(c in node))
-		node.removeAttribute(c, cProps[c]);
-	    else
-		node[c] = '';
-	} else {
-	    /* Already on DOM from previous diff, continue */
-	    if (newProp === cProps[c]) continue;
-	    domProp = node[c];
-	    if (isSvg) {
-		if (c === "href")
-		    node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);
-		else
-		    node.setAttribute(c, newProp);
-	    } else if (c in node && !(c === "list" || c === "form")) {
-		node[c] = newProp;
-	    } else {
-		node.setAttribute(c, newProp);
-	    }
-	}
+window['diffProps'] = function diffProps(cProps, nProps, node, isSvg) {
+  var result, newProp;
+  /* Is current prop in new prop list? */
+  for (var c in cProps) {
+    newProp = nProps[c];
+    /* If current property no longer exists, remove it */
+    if (!newProp) {
+      /* current key is not in node, remove it from DOM, if SVG, remove attribute */
+      if (isSvg || !(c in node))
+        node.removeAttribute(c, cProps[c]);
+      else
+        node[c] = '';
+    } else {
+      /* Already on DOM from previous diff, continue */
+      if (newProp === cProps[c]) continue;
+      if (isSvg) {
+        if (c === "href")
+          node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);
+        else
+          node.setAttribute(c, newProp);
+      } else if (c in node && !(c === "list" || c === "form")) {
+        node[c] = newProp;
+      } else {
+        node.setAttribute(c, newProp);
+      }
     }
-    /* add remaining */
-    for (var n in nProps) {
-	if (cProps && cProps[n]) continue;
-	newProp = nProps[n];
-	/* Only add new properties, skip (continue) if they already exist in current property map */
-	if (isSvg) {
-	    if (n === "href")
-		node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);
-	    else
-		node.setAttribute(n, newProp);
-	} else if (n in node && !(n === "list" || n === "form")) {
-	    node[n] = nProps[n];
-	} else {
-	    node.setAttribute(n, newProp);
-	}
+  }
+  /* add remaining */
+  for (var n in nProps) {
+    if (cProps && cProps[n]) continue;
+    newProp = nProps[n];
+    /* Only add new properties, skip (continue) if they already exist in current property map */
+    if (isSvg) {
+      if (n === "href")
+        node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);
+      else
+        node.setAttribute(n, newProp);
+    } else if (n in node && !(n === "list" || n === "form")) {
+      node[n] = nProps[n];
+    } else {
+      node.setAttribute(n, newProp);
     }
+  }
 };
 
-window.diffCss = function diffCss(cCss, nCss, node) {
-    var result;
-    /* is current attribute in new attribute list? */
-    for (var c in cCss) {
-	result = nCss[c];
-	if (!result) {
-	    /* current key is not in node */
-	    node.style[c] = null;
-	} else if (result !== cCss[c]) {
-	    node.style[c] = result;
-	}
-    }
-    /* add remaining */
-    for (var n in nCss) {
-	if (cCss && cCss[n]) continue;
-	node.style[n] = nCss[n];
+window['diffCss'] = function diffCss(cCss, nCss, node) {
+  var result;
+  /* is current attribute in new attribute list? */
+  for (var c in cCss) {
+    result = nCss[c];
+    if (!result) {
+      /* current key is not in node */
+      node.style[c] = null;
+    } else if (result !== cCss[c]) {
+      node.style[c] = result;
     }
+  }
+  /* add remaining */
+  for (var n in nCss) {
+    if (cCss && cCss[n]) continue;
+    node.style[n] = nCss[n];
+  }
 };
 
-window.hasKeys = function hasKeys(ns, cs) {
-    return ns.length > 0 && cs.length > 0 && ns[0].key != null && cs[0].key != null;
+window['hasKeys'] = function hasKeys(ns, cs) {
+  return ns.length > 0 && cs.length > 0 && ns[0]['key'] != null && cs[0]['key'] != null;
 };
 
-window.diffChildren = function diffChildren(cs, ns, parent, doc) {
-    var longest = ns.length > cs.length ? ns.length : cs.length;
-    if (hasKeys(ns, cs)) {
-	syncChildren(cs, ns, parent, doc);
-    } else {
-	for (var i = 0; i < longest; i++)
-	    diff(cs[i], ns[i], parent, doc);
-    }
+window['diffChildren'] = function diffChildren(cs, ns, parent, doc) {
+  var longest = ns.length > cs.length ? ns.length : cs.length;
+  if (window['hasKeys'](ns, cs)) {
+    window['syncChildren'](cs, ns, parent, doc);
+  } else {
+    for (var i = 0; i < longest; i++)
+      window['diff'](cs[i], ns[i], parent, doc);
+  }
 };
 
-window.createElement = function createElement(obj, doc) {
-	if (obj.ns === "svg") {
-		obj.domRef = doc.createElementNS("http://www.w3.org/2000/svg", obj.tag);
-	} else if (obj.ns === "mathml") {
-		obj.domRef = doc.createElementNS("http://www.w3.org/1998/Math/MathML", obj.tag);
-	} else {
-		obj.domRef = doc.createElement(obj.tag);
-	}
-    populate(null, obj, doc);
+window['createElement'] = function createElement(obj, doc) {
+  if (obj['ns'] === "svg") {
+    obj['domRef'] = doc.createElementNS("http://www.w3.org/2000/svg", obj['tag']);
+  } else if (obj['ns'] === "mathml") {
+    obj['domRef'] = doc.createElementNS("http://www.w3.org/1998/Math/MathML", obj['tag']);
+  } else {
+    obj['domRef'] = doc.createElement(obj['tag']);
+  }
+  window['populate'](null, obj, doc);
 };
 
-window.createNode = function createNode(obj, parent, doc) {
-    if (obj.type === "vnode") createElement(obj, doc);
-    else obj.domRef = doc.createTextNode(obj.text);
-    parent.appendChild(obj.domRef);
-    callCreated(obj);
+window['createNode'] = function createNode(obj, parent, doc) {
+  if (obj.type === "vnode") window['createElement'](obj, doc);
+  else obj['domRef'] = doc.createTextNode(obj['text']);
+  parent.appendChild(obj['domRef']);
+  window['callCreated'](obj);
 };
 
 /* Child reconciliation algorithm, inspired by kivi and Bobril */
-window.syncChildren = function syncChildren(os, ns, parent, doc) {
-    var oldFirstIndex = 0,
-	newFirstIndex = 0,
-	oldLastIndex = os.length - 1,
-	newLastIndex = ns.length - 1,
-	nFirst, nLast, oLast, oFirst, tmp, found, node;
-    for (;;) {
-	/* check base case, first > last for both new and old
-	  [ ] -- old children empty (fully-swapped)
-	  [ ] -- new children empty (fully-swapped)
-	*/
-	if (newFirstIndex > newLastIndex && oldFirstIndex > oldLastIndex) {
-	    break;
-	}
+window['syncChildren'] = function syncChildren(os, ns, parent, doc) {
+  var oldFirstIndex = 0,
+  newFirstIndex = 0,
+  oldLastIndex = os.length - 1,
+  newLastIndex = ns.length - 1,
+  nFirst, nLast, oLast, oFirst, tmp, found, node;
+  for (;;) {
+    /* check base case, first > last for both new and old
+      [ ] -- old children empty (fully-swapped)
+      [ ] -- new children empty (fully-swapped)
+    */
+    if (newFirstIndex > newLastIndex && oldFirstIndex > oldLastIndex) {
+      break;
+    }
 
-	/* Initialize */
-	nFirst = ns[newFirstIndex];
-	nLast = ns[newLastIndex];
-	oFirst = os[oldFirstIndex];
-	oLast = os[oldLastIndex];
-	/* No more old nodes, create and insert all remaining nodes
-	   -> [ ] <- old children
-	   -> [ a b c ] <- new children
-	*/
-	if (oldFirstIndex > oldLastIndex) {
-	    diff(null, nFirst, parent, doc);
-	    /* insertBefore's semantics will append a node if the second argument provided is `null` or `undefined`.
-	       Otherwise, it will insert node.domRef before oLast.domRef. */
-	    parent.insertBefore(oLast.domRef, nFirst.domRef);
-	    os.splice(newFirstIndex, 0, nFirst);
-	    newFirstIndex++;
-	}
-	/* No more new nodes, delete all remaining nodes in old list
-	   -> [ a b c ] <- old children
-	   -> [ ] <- new children
-	*/
-	else if (newFirstIndex > newLastIndex) {
-	    tmp = oldLastIndex - oldFirstIndex;
-	    while (tmp >= 0) {
-		parent.removeChild(os[oldFirstIndex].domRef);
-		os.splice(oldFirstIndex, 1);
-		tmp--;
-	    }
-	    break;
-	}
-	/* happy path, everything aligns, we continue
-	   -> oldFirstIndex -> [ a b c ] <- oldLastIndex
-	   -> newFirstIndex -> [ a b c ] <- newLastIndex
-	   check if nFirst and oFirst align, if so, check nLast and oLast
-	*/
-	else if (oFirst.key === nFirst.key) {
-	    diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);
-	} else if (oLast.key === nLast.key) {
-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);
-	}
-	/* flip-flop case, nodes have been swapped, in some way or another
-	   both could have been swapped.
-	   -> [ a b c ] <- old children
-	   -> [ c b a ] <- new children
-	*/
-	else if (oFirst.key === nLast.key && nFirst.key === oLast.key) {
-	    swapDomRefs(node, oFirst.domRef, oLast.domRef, parent);
-	    swap(os, oldFirstIndex, oldLastIndex);
-	    diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);
-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);
-	}
-	/* Or just one could be swapped (d's align here)
-	       This is top left and bottom right match case.
-	       We move d to end of list, mutate old vdom to reflect the change
-	       We then continue without affecting indexes, hoping to land in a better case
-	       -> [ d a b ] <- old children
-	       -> [ a b d ] <- new children
-	       becomes
-	       -> [ a b d ] <- old children
-	       -> [ a b d ] <- new children
-	       and now we happy path
-	   */
-	else if (oFirst.key === nLast.key) {
-	    /* insertAfter */
-	    parent.insertBefore(oFirst.domRef, oLast.domRef.nextSibling);
-	    /* swap positions in old vdom */
-	    os.splice(oldLastIndex,0,os.splice(oldFirstIndex,1)[0]);
-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);
-	}
-	/* This is top right and bottom lefts match case.
-	   We move d to end of list, mutate old vdom to reflect the change
-	   -> [ b a d ] <- old children
-	   -> [ d b a ] <- new children
-	   becomes
-	   -> [ d b a ] <- old children
-	   -> [ d b a ] <- new children
-	   and now we happy path
-	*/
-	else if (oLast.key === nFirst.key) {
-	    /* insertAfter */
-	    parent.insertBefore(oLast.domRef, oFirst.domRef);
-	    /* swap positions in old vdom */
-	    os.splice(oldFirstIndex,0, os.splice(oldLastIndex,1)[0]);
-	    diff(os[oldFirstIndex++], nFirst, parent, doc);
-	    newFirstIndex++;
-	}
+    /* Initialize */
+    nFirst = ns[newFirstIndex];
+    nLast = ns[newLastIndex];
+    oFirst = os[oldFirstIndex];
+    oLast = os[oldLastIndex];
+    /* No more old nodes, create and insert all remaining nodes
+       -> [ ] <- old children
+       -> [ a b c ] <- new children
+    */
+    if (oldFirstIndex > oldLastIndex) {
+      window['diff'](null, nFirst, parent, doc);
+      /* insertBefore's semantics will append a node if the second argument provided is `null` or `undefined`.
+         Otherwise, it will insert node['domRef'] before oLast['domRef']. */
+      parent.insertBefore(oLast['domRef'], nFirst['domRef']);
+      os.splice(newFirstIndex, 0, nFirst);
+      newFirstIndex++;
+    }
+    /* No more new nodes, delete all remaining nodes in old list
+       -> [ a b c ] <- old children
+       -> [ ] <- new children
+    */
+    else if (newFirstIndex > newLastIndex) {
+      tmp = oldLastIndex - oldFirstIndex;
+      while (tmp >= 0) {
+        parent.removeChild(os[oldFirstIndex]['domRef']);
+        os.splice(oldFirstIndex, 1);
+        tmp--;
+      }
+      break;
+    }
+    /* happy path, everything aligns, we continue
+       -> oldFirstIndex -> [ a b c ] <- oldLastIndex
+       -> newFirstIndex -> [ a b c ] <- newLastIndex
+       check if nFirst and oFirst align, if so, check nLast and oLast
+    */
+    else if (oFirst['key'] === nFirst['key']) {
+      window['diff'](os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);
+    } else if (oLast['key'] === nLast['key']) {
+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);
+    }
+    /* flip-flop case, nodes have been swapped, in some way or another
+       both could have been swapped.
+       -> [ a b c ] <- old children
+       -> [ c b a ] <- new children
+    */
+    else if (oFirst['key'] === nLast['key'] && nFirst['key'] === oLast['key']) {
+      window['swapDomRefs'](node, oFirst['domRef'], oLast['domRef'], parent);
+      window['swap'](os, oldFirstIndex, oldLastIndex);
+      window['diff'](os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);
+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);
+    }
+    /* Or just one could be swapped (d's align here)
+           This is top left and bottom right match case.
+           We move d to end of list, mutate old vdom to reflect the change
+           We then continue without affecting indexes, hoping to land in a better case
+           -> [ d a b ] <- old children
+           -> [ a b d ] <- new children
+           becomes
+           -> [ a b d ] <- old children
+           -> [ a b d ] <- new children
+           and now we happy path
+       */
+    else if (oFirst['key'] === nLast['key']) {
+      /* insertAfter */
+      parent.insertBefore(oFirst['domRef'], oLast['domRef'].nextSibling);
+      /* swap positions in old vdom */
+      os.splice(oldLastIndex,0,os.splice(oldFirstIndex,1)[0]);
+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);
+    }
+    /* This is top right and bottom lefts match case.
+       We move d to end of list, mutate old vdom to reflect the change
+       -> [ b a d ] <- old children
+       -> [ d b a ] <- new children
+       becomes
+       -> [ d b a ] <- old children
+       -> [ d b a ] <- new children
+       and now we happy path
+    */
+    else if (oLast['key'] === nFirst['key']) {
+      /* insertAfter */
+      parent.insertBefore(oLast['domRef'], oFirst['domRef']);
+      /* swap positions in old vdom */
+      os.splice(oldFirstIndex,0, os.splice(oldLastIndex,1)[0]);
+      window['diff'](os[oldFirstIndex++], nFirst, parent, doc);
+      newFirstIndex++;
+    }
 
-	/* The "you're screwed" case, nothing aligns, pull the ripcord, do something more fancy
-	   This can happen when the list is sorted, for example.
-	   -> [ a e c ] <- old children
-	   -> [ b e d ] <- new children
-	*/
-	else {
-	    /* final case, perform linear search to check if new key exists in old map, decide what to do from there */
-	    found = false;
-	    tmp = oldFirstIndex;
-	    while (tmp <= oldLastIndex) {
-		if (os[tmp].key === nFirst.key) {
-		    found = true;
-		    node = os[tmp];
-		    break;
-		}
-		tmp++;
-	    }
-   	        /* If new key was found in old map this means it was moved, hypothetically as below
-		   -> [ a e b c ] <- old children
-		   -> [ b e a j ] <- new children
-			^
-		   In the above case 'b' has been moved, so we need to insert 'b' before 'a' in both vDOM and DOM
-		   We also increase oldFirstIndex and newFirstIndex.
+    /* The "you're screwed" case, nothing aligns, pull the ripcord, do something more fancy
+       This can happen when the list is sorted, for example.
+       -> [ a e c ] <- old children
+       -> [ b e d ] <- new children
+    */
+    else {
+      /* final case, perform linear search to check if new key exists in old map, decide what to do from there */
+      found = false;
+      tmp = oldFirstIndex;
+      while (tmp <= oldLastIndex) {
+        if (os[tmp]['key'] === nFirst['key']) {
+          found = true;
+          node = os[tmp];
+          break;
+        }
+        tmp++;
+      }
+          /* If new key was found in old map this means it was moved, hypothetically as below
+         -> [ a e b c ] <- old children
+         -> [ b e a j ] <- new children
+          ^
+         In the above case 'b' has been moved, so we need to insert 'b' before 'a' in both vDOM and DOM
+         We also increase oldFirstIndex and newFirstIndex.
 
-		   This results in new list below w/ updated index position
-		   -> [ b a e c ] <- old children
-		   -> [ b e a j ] <- new children
-			  ^
-		*/
-	    if (found) {
-		/* Move item to correct position */
-		os.splice(oldFirstIndex,0, os.splice(tmp,1)[0]);
-  		/* Swap DOM references */
-		parent.insertBefore(node.domRef, os[oldFirstIndex].domRef);
-		/* optionally perform `diff` here */
-		diff(os[oldFirstIndex++], nFirst, parent, doc);
-		/* increment counters */
-		newFirstIndex++;
-	    }
-	    /* If new key was *not* found in the old map this means it must now be created, example below
-		   -> [ a e d c ] <- old children
-		   -> [ b e a j ] <- new children
-			^
+         This results in new list below w/ updated index position
+         -> [ b a e c ] <- old children
+         -> [ b e a j ] <- new children
+            ^
+      */
+      if (found) {
+        /* Move item to correct position */
+        os.splice(oldFirstIndex,0, os.splice(tmp,1)[0]);
+        /* Swap DOM references */
+        parent.insertBefore(node['domRef'], os[oldFirstIndex]['domRef']);
+        /* optionally perform `diff` here */
+        window['diff'](os[oldFirstIndex++], nFirst, parent, doc);
+        /* increment counters */
+        newFirstIndex++;
+      }
+        /* If new key was *not* found in the old map this means it must now be created, example below
+           -> [ a e d c ] <- old children
+           -> [ b e a j ] <- new children
+            ^
 
-		   In the above case 'b' does not exist in the old map, so we create a new element and DOM reference.
-		   We then insertBefore in both vDOM and DOM.
+           In the above case 'b' does not exist in the old map, so we create a new element and DOM reference.
+           We then insertBefore in both vDOM and DOM.
 
-		   -> [ b a e d c ] <- old children
-		   -> [ b e a j   ] <- new children
-			  ^
-	       */
-	    else {
-		createElement(nFirst, doc);
-		parent.insertBefore(nFirst.domRef, oFirst.domRef);
-		os.splice(oldFirstIndex++, 0, nFirst);
-		newFirstIndex++;
-		oldLastIndex++;
-	    }
-	}
+           -> [ b a e d c ] <- old children
+           -> [ b e a j   ] <- new children
+              ^
+           */
+      else {
+        window['createElement'](nFirst, doc);
+        parent.insertBefore(nFirst['domRef'], oFirst['domRef']);
+        os.splice(oldFirstIndex++, 0, nFirst);
+        newFirstIndex++;
+        oldLastIndex++;
+      }
     }
+  }
 };
 
-window.swapDomRefs = function swapDomRefs(tmp,a,b,p) {
+window['swapDomRefs'] = function swapDomRefs(tmp,a,b,p) {
   tmp = a.nextSibling;
   p.insertBefore(a,b);
   p.insertBefore(b,tmp);
 };
 
-window.swap= function swap(os,l,r) {
+window['swap']= function swap(os,l,r) {
   var k = os[l];
   os[l] = os[r];
   os[r] = k;
diff --git a/jsbits/isomorphic.js b/jsbits/isomorphic.js
--- a/jsbits/isomorphic.js
+++ b/jsbits/isomorphic.js
@@ -1,23 +1,57 @@
-window.copyDOMIntoVTree = function copyDOMIntoVTree (vtree) {
-    walk (vtree, document.body.firstChild);
-};
+window['copyDOMIntoVTree'] = function copyDOMIntoVTree(mountPoint, vtree, doc = window.document) {
+  var node = mountPoint ? mountPoint.firstChild : doc.body.firstChild;
+  if (!window['walk'](vtree, node, doc)) {
+    console.warn('Could not copy DOM into virtual DOM, falling back to diff');
+    // Remove all children before rebuilding DOM
+    while (node.firstChild)
+      node.removeChild(node.lastChild);
+    window['diff'](null, vtree, node, doc);
+    return false;
+  }
+  return true;
+}
 
-window.walk = function walk (vtree, node) {
-    var i = 0, vdomChild, domChild;
-    vtree.domRef = node;
+window['walk'] = function walk(vtree, node, doc) {
+  // This is slightly more complicated than one might expect since
+  // browsers will collapse consecutive text nodes into a single text node.
+  // There can thus be fewer DOM nodes than VDOM nodes.
+  var vdomChild,
+      domChild;
 
-    // Fire onCreated events as though the elements had just been created.
-    callCreated(vtree);
+  vtree['domRef'] = node;
 
-    while (i < vtree.children.length) {
-      vdomChild = vtree.children[i];
-      domChild = node.childNodes[i];
-      if (vdomChild.type === "vtext") {
-	  vdomChild.domRef = domChild;
-	  i++;
-	  continue;
-      }
-      walk(vdomChild, domChild);
-      i++;
-   }
-};
+  // Fire onCreated events as though the elements had just been created.
+  window['callCreated'](vtree);
+
+  for (var i = 0; i < vtree.children.length; i++) {
+    vdomChild = vtree['children'][i];
+    domChild = node.childNodes[i];
+    if (vdomChild.type === "vtext") {
+        if (domChild.nodeType !== Node.TEXT_NODE) return false;
+
+        if (vdomChild['text'] === domChild.textContent) {
+          vdomChild['domRef'] = domChild;
+        } else {
+          var len = vdomChild.text.length,
+              domNodeText = domChild.textContent.substring(0, len);
+          if (domNodeText !== vdomChild.text) return false;
+
+          // There are more VDOM nodes than DOM nodes
+          // Create new DOM node to ensure synchrony between VDom and DOM
+          var partialTxt = doc.createTextNode(domNodeText);
+          node.insertBefore(partialTxt, domChild);
+          vdomChild['domRef'] = partialTxt;
+          domChild.textContent = domChild.textContent.substring(len);
+        }
+    } else {
+      if (domChild.nodeType !== Node.ELEMENT_NODE) return false;
+      window['walk'](vdomChild, domChild, doc);
+    }
+
+  }
+  // After walking the sizes of VDom and DOM should be equal
+  // Otherwise there are DOM nodes unaccounted for
+  if (vtree.children.length !== node.childNodes.length) return false;
+
+  return true;
+}
diff --git a/jsbits/util.js b/jsbits/util.js
--- a/jsbits/util.js
+++ b/jsbits/util.js
@@ -1,11 +1,11 @@
-window.callFocus = function callFocus(id) {
+window['callFocus'] = function callFocus(id) {
   setTimeout(function(){
     var ele = document.getElementById(id);
     if (ele && ele.focus) ele.focus()
   }, 50);
 }
 
-window.callBlur = function callBlur(id) {
+window['callBlur'] = function callBlur(id) {
   setTimeout(function(){
     var ele = document.getElementById(id);
     if (ele && ele.blur) ele.blur()
diff --git a/miso.cabal b/miso.cabal
--- a/miso.cabal
+++ b/miso.cabal
@@ -1,5 +1,5 @@
 name:                miso
-version:             0.21.2.0
+version:             1.0.0.0
 category:            Web, Miso, Data Structures
 license:             BSD3
 license-file:        LICENSE
@@ -44,6 +44,10 @@
   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/todo-mvc
     build-depends:
@@ -62,6 +66,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/three
     build-depends:
@@ -79,6 +87,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/file-reader
     build-depends:
@@ -96,6 +108,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/xhr
     build-depends:
@@ -113,6 +129,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/canvas2d
     build-depends:
@@ -129,6 +149,10 @@
   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/router
     build-depends:
@@ -148,6 +172,10 @@
   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/websocket
     build-depends:
@@ -166,6 +194,10 @@
   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/mario
     build-depends:
@@ -188,6 +220,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/svg
     other-modules:
@@ -206,6 +242,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/compose-update
     build-depends:
@@ -220,6 +260,10 @@
   if !impl(ghcjs) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       examples/mathml
     build-depends:
@@ -234,6 +278,10 @@
   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       exe
     build-depends:
@@ -252,6 +300,10 @@
   if !impl(ghcjs) || !flag(tests)
     buildable: False
   else
+    ghcjs-options:
+      -dedupe
+    cpp-options:
+      -DGHCJS_BROWSER
     hs-source-dirs:
       tests
     build-depends:
diff --git a/src/Miso/Html/Element.hs b/src/Miso/Html/Element.hs
--- a/src/Miso/Html/Element.hs
+++ b/src/Miso/Html/Element.hs
@@ -126,7 +126,7 @@
     , bdi_
     , bdo_
     , wbr_
-    -- * Interactive elemnts
+    -- * Interactive elements
     , details_
     , summary_
     , menuitem_
diff --git a/src/Miso/Html/Property.hs b/src/Miso/Html/Property.hs
--- a/src/Miso/Html/Property.hs
+++ b/src/Miso/Html/Property.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Miso.Html.Property
@@ -114,11 +115,17 @@
    , httpEquiv_
    , language_
    , scoped_
+   -- * Data
+   , data_
    ) where
 
 import           Miso.Html.Internal
 import           Miso.String (MisoString, intercalate)
 
+#if __GLASGOW_HASKELL__ < 841
+import           Data.Monoid ((<>))
+#endif
+
 -- | Set field to `Bool` value
 boolProp :: MisoString -> Bool -> Attribute action
 boolProp = prop
@@ -383,3 +390,7 @@
 -- <https://developer.mozilla.org/en-US/docs/Web/API/Element/className>
 class_ ::  MisoString -> Attribute action
 class_ = textProp "class"
+-- | Set "data-*" property
+-- https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*
+data_ ::  MisoString -> MisoString -> Attribute action
+data_ k v = textProp ("data-" <> k) v
diff --git a/src/Miso/Router.hs b/src/Miso/Router.hs
--- a/src/Miso/Router.hs
+++ b/src/Miso/Router.hs
@@ -36,7 +36,7 @@
 import qualified Data.Text             as T
 import           Data.Text.Encoding
 import           GHC.TypeLits
-import           Network.HTTP.Types
+import           Network.HTTP.Types    hiding (Header)
 import           Network.URI
 import           Servant.API
 import           Web.HttpApiData
@@ -117,6 +117,11 @@
   mkRouter _ a f = RQueryFlag
     (Proxy :: Proxy sym)
     (\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))
+
+-- | Header
+instance HasRouter sublayout => HasRouter (Header sym (x :: *) :> sublayout) where
+    type RouteT (Header sym x :> sublayout) a = Maybe x -> RouteT sublayout a
+    mkRouter _ a f = mkRouter (Proxy :: Proxy sublayout) a (f Nothing)
 
 -- | Path
 instance (HasRouter sublayout, KnownSymbol path)
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -95,7 +95,6 @@
     GaveUp { output = o } -> (False, o)
     Failure { output = o } -> (False, o)
     NoExpectedFailure { output = o } -> (False, o)
-    InsufficientCoverage { output = o } -> (False, o)
 
 foreign import javascript unsafe "window.global_test_results = $1;"
   writeToGlobalObject :: JSVal -> IO ()
