diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Luite Stegeman
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.markdown b/README.markdown
new file mode 100644
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,4 @@
+# virtual-dom bindings
+
+[virtual-dom](https://github.com/Matt-Esch/virtual-dom) is a library for fast incremental
+DOM updates by comparing immutable virtual DOM trees.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/data/example.css b/data/example.css
new file mode 100644
--- /dev/null
+++ b/data/example.css
@@ -0,0 +1,28 @@
+body {
+    background-color: #ccc;
+}
+
+div.row {
+    padding: 0;
+    margin: 0;
+    height: 5px;
+    line-height: 5px;
+    text-size: 10%;
+}
+
+div.pixel-red, div.pixel-white {
+    vertical-align: top;
+    display: inline-block;
+    width: 5px;
+    height: 5px;
+    margin: 0;
+    padding: 0;
+}
+
+div.pixel-red {
+    background-color: #f00;
+}
+
+div.pixel-white {
+    background-color: #fff;
+}
diff --git a/examples/Components.hs b/examples/Components.hs
new file mode 100644
--- /dev/null
+++ b/examples/Components.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-
+  ghcjs-vdom example, demonstrating components
+ -}
+
+module Main where
+
+import           GHCJS.Foreign.QQ
+import           GHCJS.Types
+
+import qualified GHCJS.VDOM.Component    as C
+import qualified GHCJS.VDOM.DOMComponent as D
+import qualified GHCJS.VDOM.Attribute    as A
+import qualified GHCJS.VDOM.Element      as E
+import qualified GHCJS.VDOM.Event        as Ev
+import           GHCJS.VDOM
+
+import qualified Data.JSString.Int       as JSS
+
+import           Data.IORef
+import           Data.Monoid
+import qualified Data.Map as M
+
+import           Control.Concurrent
+import           Control.Monad
+
+import           System.IO
+
+{-
+  example virtual-dom component:
+  a simple counter component that increments on click
+ -}
+data Counter = Counter { counterComp :: VComp
+                       , counterKey  :: Int
+                       , getCount    :: IO Int
+                       }
+
+mkCounter :: JSString -> Int -> IO Counter
+mkCounter description startValue = do
+  val <- newIORef startValue
+  let description' = E.text (description <> ": ")
+  c <- fixIO $ \c ->
+      -- example only: diff and patch should really done through a Renderer
+      let repaint   = C.render c >>= C.diff c >>= C.patch c
+          increment = modifyIORef' val (+1) >> repaint >> return ()
+      in  C.mkComponent $ do
+            v <- readIORef val
+            return $ E.div (A.class_ "counter", Ev.click (const increment))
+                           [description', E.text (JSS.decimal v)]
+  return $ Counter c startValue (readIORef val)
+
+-- example DOM component
+data Scroller = Scroller { scrollerComp :: DComp
+                         , scrollerKey  :: Int
+                         }
+
+mkScroller :: JSString -> Int -> IO Scroller
+mkScroller txt k = do
+  mounts <- newIORef M.empty
+  let mountScroller m = do
+        (n::JSVal) <- [js| document.createElement('div') |]
+        (t::JSVal) <- [js| document.createTextNode(`txt) |]
+        [jsu_| `n.appendChild(`t); |]
+        thr <- forkIO . forever $ do
+           threadDelay 200000
+           [jsu_| `t.data = `t.data.substr(1) + `t.data.substr(0,1); |]
+        atomicModifyIORef mounts ((,()) . M.insert m thr)
+        return n
+      unmountScroller m _ = do
+        Just thr <- M.lookup m <$> readIORef mounts
+        killThread thr
+        atomicModifyIORef mounts ((,()) . M.delete m)
+        return ()
+  c <- D.mkComponent mountScroller unmountScroller
+  return (Scroller c k)
+
+renderCounterList :: [Counter] -> VNode
+renderCounterList counters =
+  E.ul (A.class_ "counterList")
+       (map (\c -> E.li (A.key (counterKey c)) (C.toNode (counterComp c)))
+            counters)
+
+renderScrollerList :: [Scroller] -> VNode
+renderScrollerList scrollers =
+  E.ul (A.class_ "scrollerList")
+       (map (\s -> E.li (A.key (scrollerKey s)) (D.toNode (scrollerComp s)))
+            scrollers)
+
+render :: [Counter] -> [Scroller] -> VNode
+render counters scrollers =
+  E.div () [renderCounterList counters, renderScrollerList scrollers]
+
+main :: IO ()
+main = do
+  Ev.initEventDelegation Ev.defaultEvents
+  root <- [js| document.createElement('div') |]
+  [js_| document.body.appendChild(`root); |]
+  counters <- mapM (\i -> mkCounter ("counter " <> JSS.decimal i) i) [1..10]
+  scrollers <- mapM (\i -> mkScroller ("scroller " <> JSS.decimal i) i) [1..10]
+  m <- mount root (render counters scrollers)
+  rotateComponents m 11 counters scrollers
+
+rotateComponents :: VMount -> Int -> [Counter] -> [Scroller] -> IO ()
+rotateComponents m n counters scrollers = do
+  threadDelay 1000000
+  newCounter <- mkCounter ("counter " <> JSS.decimal n) n
+  newScroller <- mkScroller ("scroller " <> JSS.decimal n) n
+  let scrollers' = tail scrollers ++ [newScroller]
+      counters'  = tail counters ++ [newCounter]
+  void $ diff m (render counters' scrollers') >>= patch m
+  rotateComponents m (n+1) counters' scrollers'
diff --git a/examples/Render.hs b/examples/Render.hs
new file mode 100644
--- /dev/null
+++ b/examples/Render.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-}
+
+{-
+  ghcjs-vdom example, demonstrating the render queue
+ -}
+
+module Main where
+
+import           Control.Concurrent
+import           Control.Monad
+
+import           Data.IORef
+import           Data.Monoid
+
+import           GHCJS.Foreign.QQ
+import           GHCJS.Types
+
+import qualified GHCJS.VDOM.Component    as C
+import qualified GHCJS.VDOM.Attribute    as A
+import qualified GHCJS.VDOM.Element      as E
+import qualified GHCJS.VDOM.Render       as R
+import           GHCJS.VDOM
+
+import qualified Data.JSString.Int       as JSS
+
+
+-- a component with a slowed down rendering function
+mkSlow :: JSString -> Int -> IO VComp
+mkSlow descr delay = do
+  count <- newIORef (0::Int)
+  let descr' = E.text (descr <> " " <> JSS.decimal delay <> ": ")
+  C.mkComponent $ do
+    threadDelay delay
+    c <- atomicModifyIORef count (\x -> let x' = x+1 in (x',x'))
+    return $ E.div (A.class_ "slow") [descr', E.text (JSS.decimal c)]
+
+main :: IO ()
+main = do
+  root <- [js| document.createElement('div') |]
+  [js_| document.body.appendChild(`root); |]
+  slows1 <- mapM (mkSlow "slow1") [10000, 50000, 100000, 300000]
+  slows2 <- mapM (mkSlow "slow2") [10000, 50000, 100000]
+  void $ mount root (E.div () (map C.toNode (slows1++slows2)))
+  r1 <- R.mkRenderer
+  r2 <- R.mkRenderer
+  forever $ do
+    mapM_ (R.render r1) slows1
+    mapM_ (R.render r2) slows2
+    threadDelay 10000
+
+
+
diff --git a/examples/Table.hs b/examples/Table.hs
new file mode 100644
--- /dev/null
+++ b/examples/Table.hs
@@ -0,0 +1,107 @@
+{-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-}
+
+{-
+  virtual-dom bindings demo, rendering a large pixel grid with a bouncing red
+  square. the step and patch are calculated asynchronously, the update is
+  batched in an animation frame
+ -}
+
+module Main where
+
+import           Control.Monad
+
+import           Data.IntMap (IntMap)
+import qualified Data.IntMap as IM
+import qualified Data.JSString as JSS
+
+import           GHCJS.VDOM
+import           GHCJS.VDOM.QQ
+import qualified GHCJS.VDOM.Element as E
+import qualified GHCJS.VDOM.Attribute as A
+
+import           GHCJS.Foreign.Callback
+import           GHCJS.Foreign.QQ
+import           GHCJS.Types
+
+import           JavaScript.Web.AnimationFrame (inAnimationFrame)
+
+red :: JSString
+red = "pixel-red"
+
+white :: JSString
+white = "pixel-white"
+
+type Pixels = IntMap (IntMap JSString)
+
+setPixel :: Int -> Int -> JSString -> Pixels -> Pixels
+setPixel x y c p =
+  let r  = p IM.! y
+      r' = IM.insert x c r
+  in  r' `seq` IM.insert y r' p
+
+data State = State { x  :: !Int, y  :: !Int
+                   , dx :: !Int, dy :: !Int
+                   , w  :: !Int, h  :: !Int
+                   , pixels :: !Pixels
+                   }
+
+mkState :: Int -> Int -> Int -> Int -> State
+mkState w h x y = State x y 1 1 w h pix
+  where
+    pix     = IM.fromList $ map row [0..h-1]
+    row n   = (n, IM.fromList (map (col n) [0..w-1]))
+    col n m = (m, if (m,n)==(x,y) then red else white)
+
+step :: State -> State
+step (State x y dx dy w h p) =
+  let dx' = if x==0 then 1 else if x==(w-1) then -1 else dx
+      dy' = if y==0 then 1 else if y==(h-1) then -1 else dy
+      x'  = x+dx'
+      y'  = y+dy'
+      p'  = setPixel x' y' red (setPixel x y white p)
+   in State x' y' dx' dy' w h p'
+
+cls :: JSString -> Attributes'
+cls name = [att| className: name |]
+
+render :: State -> VNode
+render s = E.div (cls "state") [ch|pixelDiv,numDiv|]
+    where
+      xd       = textDiv (y s)
+      yd       = textDiv (x s)
+      numDiv   = E.div (cls "numeric") [ch|xd,yd|]
+      pixelDiv = E.div (cls "pixels")
+          (map (renderRowM (w s) . (pixels s IM.!)) [0..h s-1])
+
+textDiv :: Show a => a -> VNode
+textDiv x = E.div () [ch|c|]
+  where
+    c = E.text . JSS.pack . show $ x
+
+renderRowM !w !r = memo renderRow w r
+
+renderRow :: Int -> IntMap JSString -> VNode
+renderRow w r =
+  E.div (A.class_ "row", A.lang "EN") (map (renderPixelM r) [0..w-1])
+
+renderPixelM !r !c = memo renderPixel r c
+
+renderPixel :: IntMap JSString -> Int -> VNode
+renderPixel r c = E.div (cls (r IM.! c)) ()
+
+animate :: VMount -> State -> IO ()
+animate m s =
+  let s' = step s
+      r' = render s'
+  in do p <- diff m r'
+        void $ inAnimationFrame ContinueAsync (\_ -> patch m p >> animate m s')
+
+main :: IO ()
+main = do
+  root <- [js| document.createElement('div') |]
+  [js_| document.body.appendChild(`root); |]
+  let s = mkState 167 101 10 20
+  m <- mount root (E.div () ())
+  animate m s
+
+
diff --git a/ghcjs-vdom.cabal b/ghcjs-vdom.cabal
new file mode 100644
--- /dev/null
+++ b/ghcjs-vdom.cabal
@@ -0,0 +1,108 @@
+name:                ghcjs-vdom
+version:             0.2.0.0
+synopsis:            Virtual-dom bindings for GHCJS
+description:         Virtual-dom is a library for fast incremental DOM
+                     updates by comparing virtual immutable DOM trees to
+                     find a minimal number of changes to update the actual DOM.
+
+                     The bindings support memoized nodes which are only
+                     recomputed when the underlying data changes, using
+                     referential equality for the function and arguments.
+
+                     The diff procedure in the virtual-dom library has been
+                     modified slightly to support computing a diff in an
+                     asynchronous thread. Since computing a diff forces all data
+                     around the virtual-dom tree, the computation, the computation
+                     can be expensive.
+
+                     An asynchronous diff computation can be safely aborted
+                     with an async exception.
+
+license:             MIT
+license-file:        LICENSE
+author:              Luite Stegeman
+maintainer:          stegeman@gmail.com
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  virtual-dom/lib.require.js
+                     virtual-dom/diff.js
+                     virtual-dom/LICENSE
+                     virtual-dom/handle-thunk.js
+                     virtual-dom/README.md
+                     virtual-dom/package.json
+                     data/example.css
+                     README.markdown
+
+flag build-examples
+  description: build the example programs
+  default: False
+  manual: True
+
+library
+  js-sources: jsbits/vdom.js
+              virtual-dom/lib.js
+  ghcjs-options: -Wall
+  exposed-modules:     GHCJS.VDOM
+                       GHCJS.VDOM.Attribute
+                       GHCJS.VDOM.Component
+                       GHCJS.VDOM.DOMComponent
+                       GHCJS.VDOM.Element
+                       GHCJS.VDOM.Event
+                       GHCJS.VDOM.QQ
+                       GHCJS.VDOM.Render
+                       GHCJS.VDOM.Unsafe
+  other-modules:       GHCJS.VDOM.Internal
+                       GHCJS.VDOM.Internal.TH
+                       GHCJS.VDOM.Internal.Thunk
+                       GHCJS.VDOM.Internal.Types
+                       GHCJS.VDOM.Element.Builtin
+  build-depends:       base >=4.7 && < 5,
+                       ghc-prim,
+                       ghcjs-ffiqq,
+                       ghcjs-base >= 0.2.0.0,
+                       ghcjs-prim,
+                       containers,
+                       split,
+                       template-haskell
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+executable ghcjs-vdom-example-table
+  if !flag(build-examples)
+      buildable: False
+  Main-Is:        Table.hs
+  Default-Language: Haskell2010
+  hs-source-dirs: examples
+  Build-Depends:  base                >= 4    &&  < 5,
+                  ghcjs-ffiqq,
+                  ghcjs-vdom,
+                  containers,
+                  ghcjs-base
+  ghcjs-Options: -Wall
+
+executable ghcjs-vdom-example-components
+  if !flag(build-examples)
+      buildable: False
+  Main-Is:        Components.hs
+  Default-Language: Haskell2010
+  hs-source-dirs: examples
+  Build-Depends:  base                >= 4    &&  < 5,
+                  ghcjs-ffiqq,
+                  ghcjs-vdom,
+                  containers,
+                  ghcjs-base
+  ghcjs-Options: -Wall
+
+executable ghcjs-vdom-example-render
+  if !flag(build-examples)
+      buildable: False
+  Main-Is:        Render.hs
+  Default-Language: Haskell2010
+  hs-source-dirs: examples
+  Build-Depends:  base                >= 4    &&  < 5,
+                  ghcjs-ffiqq,
+                  ghcjs-vdom,
+                  containers,
+                  ghcjs-base
+  ghcjs-Options: -Wall
diff --git a/jsbits/vdom.js b/jsbits/vdom.js
new file mode 100644
--- /dev/null
+++ b/jsbits/vdom.js
@@ -0,0 +1,23 @@
+#include <ghcjs/rts.h>
+
+/* 
+ * global name for the things we need from the virtual-dom library
+ */
+var h$vdom;
+
+function h$vdomEventCallback(async, action, ev) {
+  var a = MK_AP1(action, MK_JSVAL(ev));
+  if(async) {
+    h$run(a);
+  } else {
+    h$runSync(a, true);
+  }
+}
+
+function h$vdomMountComponentCallback(action, mnt, comp) {
+  h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(comp)));
+}
+
+function h$vdomUnmountComponentCallback(action, mnt, node) {
+  h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(node)));
+}
diff --git a/src/GHCJS/VDOM.hs b/src/GHCJS/VDOM.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM.hs
@@ -0,0 +1,106 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+{-|
+   Bindings for the virtual-dom library.
+
+   The virtual-dom diff function has been changed slightly to allow it to work
+   with full functionality in asynchronous threads.
+
+   It's possible to implement the bindings without the modifications at the
+   cost of tail-call optimization and preemptive threading in the diff, by
+   recursively forcing the thunks in synchronous threads.
+ -}
+
+module GHCJS.VDOM ( Attributes, Children
+                  , Attributes', Children'
+                  , VMount, VNode, VComp, DComp, Patch, DOMNode
+                  , mount, unmount
+                  , diff, patch
+                  , memo, memoKey
+                  ) where
+
+import GHCJS.Types
+import GHCJS.Foreign.QQ
+import GHCJS.Prim
+import GHCJS.Marshal.Pure
+
+import Control.Monad
+import Data.Coerce
+
+import System.IO.Unsafe
+
+import           GHCJS.VDOM.Internal.Types
+import           GHCJS.VDOM.Internal.Thunk
+import           GHCJS.VDOM.Internal       (j,J)
+import qualified GHCJS.VDOM.Internal       as I
+
+class MemoNode a where memoNode :: (J, [JSIdent], a) -> a
+
+instance MemoNode VNode
+  where
+    memoNode (_,[],a) = a
+    memoNode (k,xs,v) =
+      let vd     = I.unsafeExportValue v
+          xs1    = unsafePerformIO (toJSArray $ coerce xs)
+      in VNode [j| h$vdom.th(`vd, `xs1, `k, true) |]
+    {-# INLINE memoNode #-}
+
+instance MemoNode b => MemoNode (a -> b)
+  where
+    memoNode (k,xs,f) = \a -> memoNode (k, I.objectIdent a:xs, f a)
+    {-# INLINE memoNode #-}
+
+memoKey :: MemoNode a => JSString -> a -> a
+memoKey k = memo' (pToJSVal k)
+{-# NOINLINE memoKey #-}
+
+memo :: MemoNode a => a -> a
+memo = memo' [j| $r = null; |]
+{-# NOINLINE memo #-}
+
+memo' :: MemoNode a => J -> a -> a
+memo' k f = memoNode (k,[I.objectIdent f],f)
+{-# INLINE memo' #-}
+
+{-|
+   Mount a virtual-dom tree in the real DOM. The mount point can be updated
+   with patch.
+-}
+mount :: DOMNode -> VNode -> IO VMount
+mount n v = do
+  m <- VMount <$> [js| h$vdom.mount(`n) |]
+  void $ patch m =<< diff m v
+  return m
+{-# INLINE mount #-}
+
+{-|
+   Remove a virtual-dom tree from the document. It's important to use
+   unmount rather than removing the mount point any other way since this
+   releases all associated Haskell data structures.
+ -}
+unmount :: VMount -> IO ()
+unmount (VMount m) = [jsu_| h$vdom.unmount(`m); |]
+{-# INLINE unmount #-}
+
+{-|
+   Compute a patch to update the mounted tree to match the virtual-dom tree
+ -}
+diff :: VMount -> VNode -> IO Patch
+diff (VMount m) (VNode v) = do
+  thunks <- [jsu| [] |]
+  patch  <- [jsu| `m.diff(`v, `thunks) |]
+  forceThunks thunks
+  forcePatch [j| `patch.patch |]
+  return (Patch patch)
+{-# INLINE diff #-}
+
+{-|
+   Apply a patch to a mounted virtual-dom tree. Fails if the tree has already
+   been patched after the diff was computed.
+ -}
+patch :: VMount -> Patch -> IO Bool
+patch (VMount m) (Patch p) = [jsu| `m.patch(`p); |]
+{-# INLINE patch #-}
+
+
+
diff --git a/src/GHCJS/VDOM/Attribute.hs b/src/GHCJS/VDOM/Attribute.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Attribute.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
+
+module GHCJS.VDOM.Attribute ( Attribute
+                            , Attributes
+                              -- * some predefined attributes
+                            , class_
+                            , id
+                            , href
+                            , alt
+                            , src
+                            , name
+                            , target
+                            , value
+                            , width
+                            , height
+                            , title
+                            , lang
+                            , type_
+                            , key -- virtual-dom identifiers
+                            ) where
+
+import Prelude hiding (id)
+
+import GHCJS.Types
+
+import GHCJS.VDOM.Internal.Types
+import GHCJS.VDOM.Internal
+
+mkAttrs ''JSString [ "id", "href", "src", "alt", "title"
+                   , "lang", "name", "target", "value"
+                   ]
+
+mkAttrs' ''JSString [ ("class_", "className")
+                    , ("type_", "type")
+                    ]
+
+mkAttrs ''Int [ "key", "width", "height" ]
diff --git a/src/GHCJS/VDOM/Component.hs b/src/GHCJS/VDOM/Component.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Component.hs
@@ -0,0 +1,60 @@
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE GHCForeignImportPrim #-}
+{-# LANGUAGE UnliftedFFITypes #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module GHCJS.VDOM.Component ( VComp
+                            , toNode
+                            , mkComponent
+                            , render
+                            , diff
+                            , patch
+                            ) where
+
+import           Control.Monad
+
+import           GHCJS.Foreign.QQ
+
+import           GHCJS.VDOM.Internal.Types
+
+import qualified GHCJS.VDOM.Internal       as I
+import           GHCJS.VDOM.Internal       (j)
+import qualified GHCJS.VDOM.Internal.Thunk as I
+
+import           GHC.Exts
+import           GHC.Types (IO(..))
+import           Unsafe.Coerce
+
+toNode :: VComp -> VNode
+toNode (VComp v) = VNode v
+{-# INLINE toNode #-}
+
+mkComponent :: IO VNode -> IO VComp
+mkComponent r = do
+  let renderE = I.unsafeExportValue r
+  c <- VComp <$> [jsu| h$vdom.c(`renderE, null, null, null) |]
+  void $ patch c =<< diff c =<< render c
+  return c
+
+foreign import javascript unsafe "$r = $1.hsRender;"
+  js_hsRender :: VComp -> State# RealWorld -> (# State# RealWorld, Any #)
+
+render :: VComp -> IO VNode
+render c = join $ IO (\s -> case js_hsRender c s of
+  (# s', r #) -> (# s', unsafeCoerce r #))
+{-# INLINE render #-}
+
+diff :: VComp -> VNode -> IO Patch
+diff (VComp c) (VNode v) = do
+  thunks <- [jsu| [] |]
+  patch  <- [jsu| `c.diff(`v, `thunks) |]
+  I.forceThunks thunks
+  I.forcePatch [j| `patch.patch |]
+  return (Patch patch)
+{-# INLINE diff #-}
+
+patch :: VComp -> Patch -> IO Bool
+patch (VComp c) (Patch p) = [jsu| `c.patch(`p) |]
+{-# INLINE patch #-}
+
diff --git a/src/GHCJS/VDOM/DOMComponent.hs b/src/GHCJS/VDOM/DOMComponent.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/DOMComponent.hs
@@ -0,0 +1,47 @@
+{- |
+   DOM components manage a normal DOM subtree inside a virtual-dom tree.
+
+   The component has callbacks for mounting and unmounting. The mount
+   callback returns a DOM tree that stays in the document until the
+   unmount callback is called.
+
+   A single component can be mounted multiple times. The mount callback
+   is called for each mount, and is expected to return a fresh DOM
+   tree every time.
+ -}
+
+{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, QuasiQuotes #-}
+module GHCJS.VDOM.DOMComponent ( DComp
+                               , mkComponent
+                               , toNode
+                               ) where
+
+import           GHCJS.Foreign.QQ
+import           GHCJS.Marshal.Pure
+import           GHCJS.Types
+
+import           GHCJS.VDOM.Internal.Types
+
+import qualified GHCJS.VDOM.Internal       as I
+
+
+toNode :: DComp -> VNode
+toNode (DComp v) = VNode v
+{-# INLINE toNode #-}
+
+mkComponent :: (Int -> IO JSVal)     -- ^ mount action, return a DOM node
+            -> (Int -> JSVal -> IO ()) -- ^ unmount action
+            -> IO DComp
+mkComponent mount unmount =
+  let mountE   = I.unsafeExportValue (mountComponent mount)
+      unmountE = I.unsafeExportValue (unmountComponent unmount)
+  in  DComp <$> [jsu| h$vdom.c(null, `mountE, `unmountE, null) |]
+
+
+mountComponent :: (Int -> IO JSVal) -> JSVal -> JSVal -> IO ()
+mountComponent f mnt c = do
+  node <- f (pFromJSVal mnt)
+  [jsu| `c.updateMount(`mnt, `node); |]
+
+unmountComponent :: (Int -> JSVal -> IO ()) -> JSVal -> JSVal -> IO ()
+unmountComponent f mnt node = f (pFromJSVal mnt) node
diff --git a/src/GHCJS/VDOM/Element.hs b/src/GHCJS/VDOM/Element.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Element.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
+module GHCJS.VDOM.Element ( custom
+                          , text
+                          , module GHCJS.VDOM.Element.Builtin
+                          ) where
+
+import           Data.JSString (JSString)
+
+import qualified GHCJS.VDOM.Internal as I
+import           GHCJS.VDOM.Internal.Types
+
+import           GHCJS.VDOM.Element.Builtin
+
+custom :: (Attributes a, Children c) => JSString -> a -> c -> VNode
+custom tag a c = I.mkVNode tag a c
+{-# INLINE custom #-}
diff --git a/src/GHCJS/VDOM/Element/Builtin.hs b/src/GHCJS/VDOM/Element/Builtin.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Element/Builtin.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module GHCJS.VDOM.Element.Builtin where
+
+import GHCJS.VDOM.Internal
+
+mkElems [ "address", "article", "body", "footer", "header"
+        , "h1", "h2", "h3", "h4", "h5", "h6"
+        , "hgroup", "nav", "section"
+        , "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li"
+        , "main", "ol", "p", "pre", "ul"
+        , "a", "abbr", "b", "bdi", "br", "cite", "code", "dfn"
+        , "em", "i", "kbd", "mark", "q", "rp", "rt", "rtc", "ruby"
+        , "s", "samp", "small", "span", "strong", "sub", "sup", "time"
+        , "u", "var", "wbr"
+        , "area", "audio", "img", "map", "track", "video"
+        , "embed", "iframe", "object", "param", "source"
+        , "canvas", "noscript", "script"
+        , "del", "ins"
+        , "caption", "col", "colgroup", "table", "tbody", "td", "tfoot"
+        , "th", "thead", "tr"
+        , "button", "datalist", "fieldset", "form", "input", "keygen"
+        , "label", "legend", "meter", "optgroup", "option", "output"
+        , "progress", "select", "textarea"
+        , "details", "dialog", "menu", "menuitem", "summary"
+        , "content", "element", "shadow", "template"
+        ]
+
+-- use 'data_' as the name for the data tag, data is a reserved word
+mkElem "data_" "data"
diff --git a/src/GHCJS/VDOM/Event.hs b/src/GHCJS/VDOM/Event.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Event.hs
@@ -0,0 +1,198 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+module GHCJS.VDOM.Event ( initEventDelegation
+                        , defaultEvents
+--                        , target
+                        , stopPropagation
+                        , stopImmediatePropagation
+                        , preventDefault
+                          
+                          -- * mouse
+                        , MouseEvent
+                        , click
+                        , dblclick
+                        , mousedown
+                        , mouseenter
+                        , mouseleave
+                        , mousemove
+                        , mouseout
+                        , mouseover
+                        , mouseup
+                          --
+                        , button
+                        , buttons
+                        , clientX
+                        , clientY
+                           
+                          -- * keyboard
+                        , KeyboardEvent
+                        , keydown
+                        , keypress
+                        , keyup
+                          --
+                        , key
+                        , ctrlKey
+                        , metaKey
+                        , shiftKey
+                          
+                          -- * drag
+                        , DragEvent
+                        , drag
+                        , dragend
+                        , dragenter
+                        , dragleave
+                        , dragover
+                        , dragstart
+                          
+                          -- * focus
+                        , FocusEvent
+                        , focus
+                        , blur
+
+                          -- * ui
+                        , UIEvent
+                        , resize
+                        , scroll
+                        , select
+                        , unload
+                          
+                          -- * wheel
+                        , WheelEvent
+                        , wheel
+                          --
+                        , deltaX
+                        , deltaY
+                        , deltaZ
+                        , deltaMode
+                          
+                          -- * generic
+                        , Event
+                        , submit
+                        , change
+                        ) where
+
+import Data.Coerce
+
+import Unsafe.Coerce
+
+import GHCJS.Prim
+import GHCJS.Types
+import GHCJS.Foreign.QQ
+
+import GHCJS.VDOM.Internal
+
+-- | call this to initialize the virtual-dom event handling system
+initEventDelegation :: [JSString] -> IO ()
+initEventDelegation eventTypes = do
+  a <- toJSArray (unsafeCoerce eventTypes)
+  [jsu_| h$vdom.initDelegator(`a); |]
+
+class Coercible a JSVal => Event_ a
+class Event_ a          => KeyModEvent_ a
+class Event_ a          => MouseEvent_ a
+class Event_ a          => FocusEvent_ a
+
+mkEventTypes ''Event_ [ ("MouseEvent",    [''MouseEvent_])
+                      , ("KeyboardEvent", [''KeyModEvent_])
+                      , ("FocusEvent",    [''FocusEvent_])
+                      , ("DragEvent",     [])
+                      , ("WheelEvent",    [])
+                      , ("UIEvent",       [])
+                      , ("Event",         [])
+                      ]
+
+mkEvents 'MouseEvent [ "click", "dblclick", "mousedown", "mouseenter"
+                     , "mouseleave", "mousemove", "mouseout"
+                     , "mouseover", "mouseup"
+                     ]
+
+mkEvents 'KeyboardEvent [ "keydown", "keypress", "keyup" ]
+
+mkEvents 'DragEvent [ "drag", "dragend", "dragenter", "dragleave"
+                    , "dragover", "dragstart" ]
+
+mkEvents 'FocusEvent [ "focus", "blur" ]
+
+mkEvents 'UIEvent [ "resize", "scroll", "select", "unload" ]
+
+mkEvents 'WheelEvent [ "wheel" ]
+
+mkEvents 'Event [ "submit", "change" ]
+
+er :: Event_ a => (JSVal -> b) -> a -> b
+er f x = f (coerce x)
+
+-- -----------------------------------------------------------------------------
+
+-- this contains all event types added with mkEvents
+defaultEvents :: [JSString]
+defaultEvents = $(mkDefaultEvents)
+
+
+-- target :: Event_ a => a -> VNode
+-- target e = undefined
+
+stopPropagation :: Event_ a => a -> IO ()
+stopPropagation = er $ \e -> [jsu_| `e.stopPropagation(); |]
+{-# INLINE stopPropagation #-}
+
+stopImmediatePropagation :: Event_ a => a -> IO ()
+stopImmediatePropagation = er $ \e -> [jsu_| `e.stopImmediatePropagation(); |]
+{-# INLINE stopImmediatePropagation #-}
+
+preventDefault :: Event_ a => a -> IO ()
+preventDefault = er $ \e -> [jsu_| `e.preventDefault(); |]
+{-# INLINE preventDefault #-}
+
+ctrlKey :: KeyModEvent_ a => a -> Bool
+ctrlKey = er $ \e -> [jsu'| `e.ctrlKey |]
+{-# INLINE ctrlKey #-}
+
+metaKey :: KeyModEvent_ a => a -> Bool
+metaKey = er $ \e -> [jsu'| `e.ctrlKey |]
+{-# INLINE metaKey #-}
+
+shiftKey :: KeyModEvent_ a => a -> Bool
+shiftKey = er $ \e -> [jsu'| `e.ctrlKey |]
+{-# INLINE shiftKey #-}
+
+key :: KeyboardEvent -> JSString
+key = er $ \e -> [jsu'| `e.key |]
+{-# INLINE key #-}
+
+button :: MouseEvent_ a => a -> Int
+button = er $ \e -> [jsu'| `e.button |]
+{-# INLINE button #-}
+
+buttons :: MouseEvent_ a => a -> Int
+buttons = er $ \e -> [jsu'| `e.buttons |]
+{-# INLINE buttons #-}
+
+deltaX :: WheelEvent -> Double
+deltaX = er $ \e -> [jsu'| `e.deltaX |]
+{-# INLINE deltaX #-}
+
+deltaY :: WheelEvent -> Double
+deltaY = er $ \e -> [jsu'| `e.deltaY |]
+{-# INLINE deltaY #-}
+
+deltaZ :: WheelEvent -> Double
+deltaZ = er $ \e -> [jsu'| `e.deltaZ |]
+{-# INLINE deltaZ #-}
+
+deltaMode :: WheelEvent -> Double
+deltaMode = er $ \e -> [jsu'| `e.deltaMode |]
+{-# INLINE deltaMode #-}
+
+clientX :: MouseEvent -> Int
+clientX = er $ \e -> [jsu'| `e.clientX|0 |]
+{-# INLINE clientX #-}
+
+clientY :: MouseEvent -> Int
+clientY = er $ \e -> [jsu'| `e.clientY|0 |]
+{-# INLINE clientY #-}
diff --git a/src/GHCJS/VDOM/Internal.hs b/src/GHCJS/VDOM/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Internal.hs
@@ -0,0 +1,199 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE UnliftedFFITypes #-}
+{-# LANGUAGE GHCForeignImportPrim #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module GHCJS.VDOM.Internal where
+
+import GHCJS.VDOM.Internal.Types
+
+import Language.Haskell.TH
+import Language.Haskell.TH.Quote
+import Language.Haskell.TH.Syntax
+
+import GHC.Prim (Any, State#, RealWorld)
+
+import Control.Monad
+import Unsafe.Coerce
+
+import GHCJS.Foreign.QQ
+import GHCJS.Types
+import GHCJS.Marshal.Pure
+
+import Data.List (foldl')
+import Data.String (IsString(..))
+import Data.Typeable
+
+import GHC.IO           ( IO(..) )
+import GHC.Base         ( StableName# )
+
+type J = JSVal
+
+j :: QuasiQuoter
+j = jsu'
+
+mkVNode :: (Attributes a, Children c) => JSString -> a -> c -> VNode
+mkVNode tag atts children = js_vnode tag (mkAttributes atts) (mkChildren children)
+{-# INLINE mkVNode #-}
+
+mkElems :: [String] -> Q [Dec]
+mkElems = fmap concat . mapM (join mkElem)
+
+mkElem :: String -> String -> Q [Dec]
+mkElem name tag = do
+     let n = mkName name
+     a <- newName "a"
+     c <- newName "c"
+     b <- [| mkVNode (fromString tag) |]
+     typ <- [t|forall a c. (Attributes a, Children c) => a -> c -> VNode |]
+     return [ SigD n typ
+            , FunD n [Clause [VarP a, VarP c] (NormalB (AppE (AppE b (VarE a)) (VarE c))) []]
+            , PragmaD (InlineP n Inline FunLike AllPhases)
+            ]
+
+mkAttrs :: Name -> [String] -> Q [Dec]
+mkAttrs ty = fmap concat . mapM (join (mkAttr ty))
+
+mkAttrs' :: Name -> [(String, String)] -> Q [Dec]
+mkAttrs' ty = fmap concat . mapM (uncurry (mkAttr ty))
+
+mkAttr :: Name -> String -> String -> Q [Dec]
+mkAttr ty name attr = do
+  let n = mkName name
+  x <- newName "x"
+  b <- [| \y -> Attribute attr (pToJSVal y) |]
+  return [ SigD n (AppT (AppT ArrowT (ConT ty)) (ConT ''Attribute))
+         , FunD n [Clause [VarP x] (NormalB (AppE b (VarE x))) []]
+         , PragmaD (InlineP n Inline FunLike AllPhases)
+         ]
+
+mkEventTypes :: Name -> [(String, [Name])] -> Q [Dec]
+mkEventTypes base = fmap concat . mapM mk
+  where
+    mk (n, cls) = do
+      let nn     = mkName n
+#if MIN_VERSION_template_haskell(2,11,0)
+          mkI cn = InstanceD Nothing [] (AppT (ConT cn) (ConT nn)) []
+#else
+          mkI cn = InstanceD [] (AppT (ConT cn) (ConT nn)) []
+#endif
+          insts  = map mkI (base : cls)
+      jsr <- [t| JSVal |]
+      typ <- [t| Typeable |]
+#if MIN_VERSION_template_haskell(2,11,0)
+      return $ (NewtypeD []  nn [] Nothing (NormalC nn [(Bang NoSourceUnpackedness NoSourceStrictness, jsr)]) [ typ ]) : insts
+#else
+      return $ (NewtypeD [] nn [] (NormalC nn [(NotStrict, jsr)]) [''Typeable]) : insts
+#endif
+
+newtype CreatedEvents = CreatedEvents { unCreatedEvents :: [String] }
+  deriving (Typeable)
+
+addCreatedEvent :: String -> CreatedEvents -> CreatedEvents
+addCreatedEvent ev (CreatedEvents es) = CreatedEvents (ev:es)
+
+-- dcon must be a newtype constructor, not a data con
+mkEvents :: Name -> [String] -> Q [Dec]
+mkEvents dcon xs = fmap concat (mapM (\x -> mkEvent dcon x ("ev-"++x)) xs)
+
+-- dcon must be a newtype constructor, not a data con
+mkEvent :: Name -> String -> String -> Q [Dec]
+mkEvent dcon name attr = do
+  let n    = mkName name
+      emsg = "GHCJS.VDOM.Internal.mkEvent: expected newtype constructor"
+  i <- reify dcon
+  dctyp <- case i of
+#if MIN_VERSION_template_haskell(2,11,0)
+    DataConI _ _ pn -> do
+      pni <- reify pn
+      case pni of
+         TyConI (NewtypeD _ ctn _ _ _ _) -> return (ConT ctn)
+         _                               -> error emsg
+    _                 -> error emsg
+#else
+    DataConI _ _ pn _ -> do
+      pni <- reify pn
+      case pni of
+         TyConI (NewtypeD _ ctn _ _ _) -> return (ConT ctn)
+         _                             -> error emsg
+    _                 -> error emsg
+#endif
+  iou <- [t| IO () |]
+  h <- newName "h"
+  b <- [| mkEventAttr (fromString attr) |]
+  let ht = AppT (AppT ArrowT dctyp) iou
+  -- typ <- [t| (dctyp -> IO ()) -> Attribute |]
+  qPutQ . maybe (CreatedEvents [name]) (addCreatedEvent name) =<< qGetQ
+  return [ SigD n (AppT (AppT ArrowT ht) (ConT ''Attribute))
+         , FunD n [Clause [VarP h] (NormalB (AppE (AppE b (ConE dcon)) (VarE h))) []]
+         , PragmaD (InlineP n Inline FunLike AllPhases)
+         ]
+
+-- a must be a newtype of JSVal!
+mkEventAttr :: JSString -> (JSVal -> a) -> (a -> IO ()) -> Attribute
+mkEventAttr attr _wrap h =
+  
+  let e  = unsafeExportValue h
+      h' = [js'| h$vdom.makeHandler(`e, false) |]
+  in  h' `seq` Attribute attr h'
+{-# INLINE mkEventAttr #-}
+
+{-
+eventLogger :: JSVal ()
+eventLogger = [js'| function(ev) { console.log("event caught"); } |]
+-}
+
+-- generate a list of all events stored in the persistent TH state, created with mkEvent
+mkDefaultEvents :: Q Exp
+mkDefaultEvents = do
+  evs <- maybe [] unCreatedEvents <$> qGetQ
+  nil  <- [| [] |]
+  cons <- [| (:) |]
+  return $ foldl' (\xs e -> AppE (AppE cons (LitE . stringL $ e)) xs) nil evs
+  
+js_vnode :: JSString -> Attributes' -> Children' -> VNode
+js_vnode tag (Attributes' props) (Children' children) =
+  VNode [jsu'| h$vdom.v(`tag, `props, `children) |]
+  --VNode [jsu'| new h$vdom.VNode(`tag, `props, `children) |]
+
+getThunk :: J -> IO J
+getThunk x = IO (js_getThunk x)
+
+foreign import javascript unsafe "$r = $1.hst;"
+  js_getThunk :: J -> State# RealWorld -> (# State# RealWorld, J #)
+
+-- -----------------------------------------------------------------------------
+{-|
+   Export an arbitrary Haskell value to JS.
+
+   be careful with these JSVal values, losing track of them will result in
+   incorrect memory management. As long as we keep the values directly in
+   a Property or VNode, the ghcjs-vdom extensible retention system will know
+   where to find them.
+ -}
+unsafeExportValue :: a -> JSVal
+unsafeExportValue x = js_export (unsafeCoerce x)
+{-# INLINE unsafeExportValue #-}
+
+{-|
+   make a unique identifier that can be easily compared in JS
+   if(objectIdent(o1) === objectIdent(o2) or both are NaN, then o1 and o2 are
+   are the same Haskell value
+ -}
+objectIdent :: a -> JSIdent
+objectIdent x = x `seq` js_makeObjectIdent (unsafeExportValue x)
+{-
+  unsafePerformIO . IO $ \s ->
+  case makeStableName# x s of (# s', sn #) -> (# s', js_convertSn sn #)
+-}
+{-# INLINE objectIdent #-}
+                             
+foreign import javascript unsafe "$r = $1;" js_export    :: Any -> JSVal
+foreign import javascript unsafe "$r = $1;" js_convertSn :: StableName# a -> JSIdent
+
+foreign import javascript unsafe "h$makeStableName($1)" js_makeObjectIdent :: JSVal -> JSIdent
diff --git a/src/GHCJS/VDOM/Internal/TH.hs b/src/GHCJS/VDOM/Internal/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Internal/TH.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE CPP, TemplateHaskell, QuasiQuotes #-}
+
+module GHCJS.VDOM.Internal.TH where
+
+import Data.List (foldl')
+
+import Language.Haskell.TH
+
+import Unsafe.Coerce
+
+mkTupleChildrenInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec]
+mkTupleChildrenInstances cls method ty con wrapper xs =
+  concat <$> mapM (mkTupleChildrenInstance cls method ty con wrapper) xs
+
+{-
+
+instance cls (ty, ty, ...) where
+  method (con x1, con x2, ...) = wrapper (buildArrayIN x1 x2 ...)
+  {-# INLINE method #-}
+
+-}
+mkTupleChildrenInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec]
+mkTupleChildrenInstance cls method ty con wrapper n = do
+  let xs    = map (mkName.('x':).show) [1..n]
+      t     = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n)
+      build = mkName ("GHCJS.Prim.Internal.Build.buildArrayI" ++ show n)
+      pat   = [TupP (map (ConP con . (:[]) . VarP) xs)]
+      body  = NormalB (AppE (ConE wrapper)
+                           (foldl' (\e v -> AppE e (VarE v)) (VarE build) xs))
+#if MIN_VERSION_template_haskell(2,11,0)
+  return [InstanceD Nothing [] t [ FunD method [Clause pat body []]
+                                 , PragmaD (InlineP method Inline FunLike AllPhases)
+                                 ]
+         ]
+#else
+  return [InstanceD [] t [ FunD method [Clause pat body []]
+                         , PragmaD (InlineP method Inline FunLike AllPhases)
+                         ]
+         ]
+#endif
+
+mkTupleAttrInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec]
+mkTupleAttrInstances cls method ty con wrapper xs =
+  concat <$> mapM (mkTupleAttrInstance cls method ty con wrapper) xs
+
+{-
+
+instance cls (ty, ty, ...) where
+  method (con k1 v1, con k2 v2, ...) =
+    wrapper (buildObjectIN k1 k2 v1 v2 ...)
+  {-# INLINE method #-}
+
+ -}
+mkTupleAttrInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec]
+mkTupleAttrInstance cls method ty con wrapper n = do
+  let xs    = map (\i -> let si = show i in [mkName ('k':si), mkName ('v':si)]) [1..n]
+      t     = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n)
+      build = mkName ("GHCJS.Prim.Internal.Build.buildObjectI" ++ show n)
+      pat   = [TupP (map (ConP con . map VarP) xs)]
+      app e k v = AppE (AppE e (AppE (VarE 'unsafeCoerce) (VarE k))) (VarE v)
+      body  = NormalB (AppE (ConE wrapper)
+                           (foldl' (\e [k,v] -> app e k v) (VarE build) xs))
+#if MIN_VERSION_template_haskell(2,11,0)
+  return [InstanceD Nothing [] t [ FunD method [Clause pat body []]
+                                 , PragmaD (InlineP method Inline FunLike AllPhases)
+                                 ]
+         ]
+#else
+  return [InstanceD [] t [ FunD method [Clause pat body []]
+                         , PragmaD (InlineP method Inline FunLike AllPhases)
+                         ]
+         ]
+#endif
diff --git a/src/GHCJS/VDOM/Internal/Thunk.hs b/src/GHCJS/VDOM/Internal/Thunk.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Internal/Thunk.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE TemplateHaskell, QuasiQuotes, LambdaCase, GHCForeignImportPrim #-}
+{-|
+   Code that deals with forcing thunks in virtual-dom trees. When
+   computing a diff, the virtual-dom code returns a list of thunks
+   found in the tree. The caller then forces the thunks and recurses
+   into them to advance the diff computation, until all thunks have
+   been evaluated.
+ -}
+module GHCJS.VDOM.Internal.Thunk where
+
+import GHCJS.Foreign.QQ
+import GHCJS.Prim
+
+import Control.Exception
+import Control.Monad
+
+import GHC.Exts (Any)
+import Unsafe.Coerce
+
+import           GHCJS.VDOM.Internal       (j,J)
+import qualified GHCJS.VDOM.Internal       as I
+import           GHCJS.VDOM.Internal.Types
+
+diff' :: J -> J -> IO J
+diff' a b = do
+  thunks <- [jsu| [] |]
+  p <- [jsu| h$vdom.diff(`a, `b, `thunks) |]
+  forceThunks thunks
+  forcePatch p
+  return p
+
+forceThunks :: J -> IO ()
+forceThunks thunks
+  | [j| `thunks.length > 0 |] = fromJSArray thunks >>= mapM_ forceNode
+  | otherwise                 = return ()
+  where
+    forceNode n = do
+      forceThunkNode [j| `n.a |]
+      forceThunkNode [j| `n.b |]
+      patch <- diff' [j| `n.a.vnode |] [j| `n.b.vnode |]
+      [jsu_| h$vdom.setThunkPatch(`n, `patch); |]
+
+forceThunkNode :: J -> IO ()
+forceThunkNode x =
+  [jsu| `x && `x.hst && !`x.vnode |] >>= \case
+    True -> do
+      (VNode u) <- fmap unsafeCoerce . js_toHeapObject =<< I.getThunk x
+      [jsu| `x.hst = null;
+            `x.vnode = `u;
+          |]
+    _ -> return ()
+
+
+forcePatch :: J -> IO ()
+forcePatch p = do
+  thunks <- [jsu| h$vdom.forcePatch(`p) |]
+  forceTree [thunks]
+
+forceTree :: [J] -> IO ()
+forceTree [] = return ()
+forceTree (x:xs) = do
+  x' <- fromJSArray x
+  ys <- forM x' $ \t -> do
+    forceThunkNode t
+    newThunks <- [jsu| [] |]
+    [jsu_| h$vdom.forceTree(`t.vnode, `newThunks) |]
+    return newThunks
+  forceTree (filter (\a -> [jsu'| `a.length !== 0 |]) ys ++ xs)
+
+foreign import javascript unsafe
+  "$r = $1;" js_toHeapObject :: JSVal -> IO Any
diff --git a/src/GHCJS/VDOM/Internal/Types.hs b/src/GHCJS/VDOM/Internal/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Internal/Types.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE QuasiQuotes, TemplateHaskell, FlexibleInstances #-}
+
+module GHCJS.VDOM.Internal.Types where
+
+import qualified Data.JSString as JSS
+import           Data.String (IsString(..))
+
+import           GHCJS.Foreign.QQ
+import           GHCJS.Types
+import qualified GHCJS.Prim.Internal.Build
+import qualified GHCJS.Prim.Internal.Build as IB
+
+import           GHCJS.VDOM.Internal.TH
+
+import           Unsafe.Coerce
+
+-- do not export the constructors for these, this ensures that the objects are opaque
+-- and cannot be mutated
+newtype VNode      = VNode      { unVNode      :: JSVal }
+newtype VComp      = VComp      { unVComp      :: JSVal }
+newtype DComp      = DComp      { unDComp      :: JSVal }
+newtype Patch      = Patch      { unPatch      :: JSVal }
+newtype VMount     = VMount     { unVMount     :: JSVal }
+
+-- fixme: make newtype?
+-- newtype JSIdent = JSIdent JSVal
+-- newtype DOMNode = DOMNode JSVal
+type JSIdent = JSVal
+type DOMNode = JSVal
+
+
+class Attributes a where
+  mkAttributes :: a -> Attributes'
+newtype Attributes' = Attributes' JSVal
+
+data Attribute = Attribute JSString JSVal
+
+class Children a where
+  mkChildren :: a -> Children'
+newtype Children' = Children' { unChildren :: JSVal }
+
+instance Children Children' where
+  mkChildren x = x
+  {-# INLINE mkChildren #-}
+
+instance Children () where
+  mkChildren _ = Children' [jsu'| [] |]
+  {-# INLINE mkChildren #-}
+
+instance Children VNode where
+  mkChildren (VNode v) =
+    Children' [jsu'| [`v] |]
+  {-# INLINE mkChildren #-}
+
+mkTupleChildrenInstances ''Children 'mkChildren ''VNode 'VNode 'Children' [2..32]
+
+instance Children [VNode] where
+  mkChildren xs = Children' $ IB.buildArrayI (unsafeCoerce xs)
+  {-# INLINE mkChildren #-}
+
+instance Attributes Attributes' where
+  mkAttributes x = x
+  {-# INLINE mkAttributes #-}
+
+instance Attributes () where
+  mkAttributes _ = Attributes' [jsu'| {} |]
+  {-# INLINE mkAttributes #-}
+
+instance Attributes Attribute where
+  mkAttributes (Attribute k v) =
+    Attributes' (IB.buildObjectI1 (unsafeCoerce k) v)
+
+instance Attributes [Attribute] where
+  mkAttributes xs = Attributes' (IB.buildObjectI $
+                                map (\(Attribute k v) -> (unsafeCoerce k,v)) xs)
+
+mkTupleAttrInstances ''Attributes 'mkAttributes ''Attribute 'Attribute 'Attributes' [2..32]
+
+instance IsString VNode where fromString xs = text (JSS.pack xs)
+
+text :: JSString -> VNode
+text xs = VNode [jsu'| h$vdom.t(`xs) |]
+{-# INLINE text #-}
diff --git a/src/GHCJS/VDOM/QQ.hs b/src/GHCJS/VDOM/QQ.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/QQ.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE QuasiQuotes, DeriveDataTypeable, TemplateHaskell #-}
+{-
+  More efficient JavaScript literals with QuasiQuoters
+
+  mostly experimental, might not stay
+ -}
+module GHCJS.VDOM.QQ (ch, children, att, attributes) where
+
+import           Language.Haskell.TH.Quote
+import           Language.Haskell.TH.Syntax
+
+import           GHCJS.VDOM.Internal.Types
+
+import           GHCJS.Types
+import           GHCJS.Marshal
+
+import           Control.Applicative
+
+import           Data.Char
+import qualified Data.List as L
+import           Data.List.Split
+import           Data.Typeable
+
+import           System.IO.Unsafe
+
+att :: QuasiQuoter
+att = attributes
+
+ch :: QuasiQuoter
+ch = children
+
+-- example: [props|a:1, b: null, c: x, d: y |]
+-- every value is either a literal or a variable referring to a convertible Haskell name
+-- fixme, this does not have a proper parser
+attributes :: QuasiQuoter
+attributes = QuasiQuoter { quoteExp = quoteProps }
+
+quoteProps :: String -> Q Exp
+quoteProps pat = jsExpQQ ('{':ffiPat++"}") (map mkName names)
+                 (\x -> AppE (VarE 'unsafePerformIO) (AppE (VarE 'toJSVal) x))
+                 (AppE (ConE 'Attributes'))
+  where
+    (names, ffiPat) = genpat 1 $ map (break (==':') . trim) (linesBy (==',') pat)
+    isName [] = False
+    isName (x:xs) = isAlpha x && all isAlphaNum xs
+    genpat :: Int -> [(String,String)] -> ([String], String)
+    genpat _ [] = ([], "")
+    genpat k ((x,':':n):xs)
+      | isName n' = (n':ns, x ++ ": $" ++ (show k) ++ p)
+      | otherwise = (ns, x ++ ':' : n ++ sep ++ p)
+      where
+        n'       = trim n
+        ~(ns, p) = genpat (k+1) xs
+        sep      = if null xs then "" else ","
+    genpat _ _ = error "invalid pattern"
+
+
+-- example: [children|x,y,z|] for haskell names x,y,z :: VNode
+children :: QuasiQuoter
+children = QuasiQuoter { quoteExp = quoteChildren }
+
+quoteChildren :: String -> Q Exp
+quoteChildren pat = jsExpQQ ffiPat names (AppE (VarE 'unVNode)) (AppE (ConE 'Children'))
+  where
+    names  = map (mkName.trim) (linesBy (==',') pat)
+    ffiPat = '[' : L.intercalate "," (map (('$':).show) (take (length names) [(1::Int)..])) ++ "]"
+
+trim :: String -> String
+trim = let f = reverse . dropWhile isSpace in f . f
+
+newtype QQCounter = QQCounter { getCount :: Int } deriving (Typeable, Show)
+
+jsExpQQ :: String -> [Name] -> (Exp -> Exp) -> (Exp -> Exp) -> Q Exp
+jsExpQQ pat args unwrap wrap = do
+  c <- maybe 0 getCount <$> qGetQ
+  n <- newName ("__ghcjs_vdom_qq_spliced_" ++ show c)
+  let ffiDecl      = ForeignD (ImportF CCall Unsafe pat' n (ty $ length args))
+      ty :: Int -> Type
+      ty 0         = ref
+      ty n         = AppT (AppT ArrowT ref) (ty (n-1))
+      ref          = ConT ''JSVal
+      ffiCall []     = (VarE n)
+      ffiCall (y:ys) = AppE (ffiCall ys) (unwrap (VarE y))
+      pat'           = "__ghcjs_javascript_" ++ L.intercalate "_" (map (show . ord) pat)
+  qAddTopDecls [ffiDecl]
+  qPutQ (QQCounter (c+1))
+  return $ wrap (ffiCall $ reverse args)
+
diff --git a/src/GHCJS/VDOM/Render.hs b/src/GHCJS/VDOM/Render.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Render.hs
@@ -0,0 +1,104 @@
+{-# LANGUAGE TypeFamilies, QuasiQuotes, ScopedTypeVariables #-}
+{-|
+   Some utilities to manage a render queue for ghcjs-vdom components.
+
+   Diffing is performed in an asynchronous background thread. When a
+   patch is ready, an animationframe is requested to apply the update.
+
+   Using the render queue is optional, directly calling diff and patch
+   for the components is a lower level way to update the document.
+ -}
+
+module GHCJS.VDOM.Render ( Renderer
+                         , render
+                         , mkRenderer
+                         ) where
+
+import           GHCJS.Foreign.Callback
+import           GHCJS.Foreign.QQ
+import           GHCJS.Types
+
+import           GHCJS.VDOM.Internal.Types
+import qualified GHCJS.VDOM.Component as C
+import qualified GHCJS.VDOM as V
+
+import           JavaScript.Web.AnimationFrame
+
+import           Control.Concurrent
+import qualified Control.Exception as E
+import           Control.Monad
+
+import           Data.IORef
+import           Data.Map (Map)
+import qualified Data.Map as M
+import           Data.Typeable
+
+class Renderable a where
+  type Render a
+  doRender :: Renderer -> a -> Render a
+
+instance Renderable VComp where
+  type Render VComp = IO ()
+  doRender r c@(VComp vcj) =
+    enqueueRender r vcj (C.render c >>= C.diff c >>= addPatch vcj) (flushPatch vcj)
+
+instance Renderable VMount where
+  type Render VMount = (VNode -> IO ())
+  doRender r vm@(VMount vmj) vn =
+    enqueueRender r vmj (V.diff vm vn >>= addPatch vmj) (flushPatch vmj)
+
+render :: Renderable a => Renderer -> a -> Render a
+render r x = doRender r x
+
+mkRenderer :: IO Renderer
+mkRenderer = do
+  r <- Renderer <$> newIORef M.empty
+                <*> newIORef M.empty
+                <*> newChan
+  void (forkIO (renderThread r))
+  return r
+
+data Renderer = Renderer
+  { renderPending      :: IORef (Map Double (IO (), IO ()))
+  , renderFlushPending :: IORef (Map Double (IO ()))
+  , renderQueue        :: Chan Double
+  } deriving (Typeable)
+
+renderThread :: Renderer -> IO ()
+renderThread r = forever $ do
+  k <- readChan (renderQueue r)
+  Just (compute, flush) <-
+    atomicModifyIORef (renderPending r) (\m -> (M.delete k m, M.lookup k m))
+  let actions = do
+        compute
+        m <- atomicModifyIORef (renderFlushPending r)
+                               (\m -> (M.insert k flush m, m))
+        when (M.null m) (void $ inAnimationFrame ThrowWouldBlock
+                                                 (\_ -> flushPatches r))
+  actions`E.catch` \(_::E.SomeException) -> return ()
+
+flushPatches :: Renderer -> IO ()
+flushPatches r =
+  mapM_ (\m -> m `E.catch` \(_::E.SomeException) -> return ()) =<<
+    atomicModifyIORef (renderFlushPending r)
+                      (\m -> (M.empty, M.elems m))
+
+enqueueRender :: Renderer
+              -> JSVal
+              -> IO ()
+              -> IO ()
+              -> IO ()
+enqueueRender r renderable compute flush = do
+  k <- renderableKey renderable
+  m <- atomicModifyIORef (renderPending r)
+                         (\m -> (M.insert k (compute, flush) m, m))
+  when (M.notMember k m) (writeChan (renderQueue r) k)
+
+renderableKey :: JSVal -> IO Double
+renderableKey r = [jsu| `r._key |]
+
+addPatch :: JSVal -> Patch -> IO ()
+addPatch r (Patch p) = [jsu_| `r.addPatch(`p); |]
+
+flushPatch :: JSVal -> IO ()
+flushPatch r = [jsu_| `r.patch(null); |]
diff --git a/src/GHCJS/VDOM/Unsafe.hs b/src/GHCJS/VDOM/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/GHCJS/VDOM/Unsafe.hs
@@ -0,0 +1,31 @@
+{-|
+   Unsafe API for constructing Children' and Attributes', useful for
+   writing new Children and Attributes instances
+ -}
+module GHCJS.VDOM.Unsafe ( Attributes(..),       Children(..)
+                         , Attributes',          Children'
+                         , unsafeToAttributes,   unsafeToChildren ) where
+
+import GHCJS.Types
+
+import GHCJS.VDOM.Internal.Types
+
+{-|
+   Convert a JSVal, which must be a JS object, to Attributes'. The object
+   may not be mutated.
+
+   FIXME what do we need to always be able to find Haskell callbacks?
+ -}
+unsafeToAttributes :: JSVal -> Attributes'
+unsafeToAttributes = Attributes'
+
+{-|
+   Convert a JSVal, which must be an array of virtual-dom nodes
+   to Children'. The array and the child nodes may not be mutated.
+
+   note: All nodes must either be a virtual node or an instance of HSThunk,
+   if you use other node types, the extensible retention (see scanTree in
+   virtual-dom/lib.require.js) must be extended.
+ -}
+unsafeToChildren :: JSVal -> Children'
+unsafeToChildren = Children'
diff --git a/virtual-dom/LICENSE b/virtual-dom/LICENSE
new file mode 100644
--- /dev/null
+++ b/virtual-dom/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Matt-Esch.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/virtual-dom/README.md b/virtual-dom/README.md
new file mode 100644
--- /dev/null
+++ b/virtual-dom/README.md
@@ -0,0 +1,13 @@
+to initiate:
+
+    $ npm install
+
+to build
+
+    $ grunt
+
+(install grunt with: npm install -g grunt-cli)
+
+to watch for changes and rebuild
+
+    $ grunt watch
diff --git a/virtual-dom/diff.js b/virtual-dom/diff.js
new file mode 100644
--- /dev/null
+++ b/virtual-dom/diff.js
@@ -0,0 +1,445 @@
+/*
+   vtree/diff module modified to defer rendering thunks. this makes it possible to
+   implement thunks that cannot be called directly as a function, but have
+   an asynchronous callback or require evaluation in some specific runtime
+   environment
+ */
+
+var isArray = require("x-is-array")
+
+var VPatch = require("virtual-dom/vnode/vpatch")
+var isVNode = require("virtual-dom/vnode/is-vnode")
+var isVText = require("virtual-dom/vnode/is-vtext")
+var isWidget = require("virtual-dom/vnode/is-widget")
+var isThunk = require("virtual-dom/vnode/is-thunk")
+var handleThunk = require("./handle-thunk")
+
+var diffProps = require("virtual-dom/vtree/diff-props")
+
+module.exports = diff
+
+// unevaluated thunks are added to the thunks argument (array)
+function diff(a, b, thunks) {
+    if(!a) throw new Error ("diff a: " + a);
+    if(!a) throw new Error ("diff b: " + b);
+    var patch = { a: a }
+    walk(a, b, patch, thunks, 0)
+    return patch
+}
+
+function walk(a, b, patch, thunks, index) {
+    if (a === b) {
+        return
+    }
+
+    var apply = patch[index]
+    var applyClear = false
+
+    if (isThunk(a) || isThunk(b)) {
+        doThunks(a, b, patch, thunks, index)
+    } else if (b == null) {
+
+        // If a is a widget we will add a remove patch for it
+        // Otherwise any child widgets/hooks must be destroyed.
+        // This prevents adding two remove patches for a widget.
+        if (!isWidget(a)) {
+            clearState(a, patch, index)
+            apply = patch[index]
+        }
+
+        apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b))
+    } else if (isVNode(b)) {
+        if (isVNode(a)) {
+            if (a.tagName === b.tagName &&
+                a.namespace === b.namespace &&
+                a.key === b.key) {
+                var propsPatch = diffProps(a.properties, b.properties)
+                if (propsPatch) {
+                    apply = appendPatch(apply,
+                        new VPatch(VPatch.PROPS, a, propsPatch))
+                }
+                apply = diffChildren(a, b, patch, apply, thunks, index)
+            } else {
+                apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
+                applyClear = true
+            }
+        } else {
+            apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
+            applyClear = true
+        }
+    } else if (isVText(b)) {
+        if (!isVText(a)) {
+            apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
+            applyClear = true
+        } else if (a.text !== b.text) {
+            apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
+        }
+    } else if (isWidget(b)) {
+        if (!isWidget(a)) {
+            applyClear = true
+        }
+
+        apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))
+    }
+
+    if (apply) {
+        patch[index] = apply
+    }
+
+    if (applyClear) {
+        clearState(a, patch, index)
+    }
+}
+
+function diffChildren(a, b, patch, apply, thunks, index) {
+    var aChildren = a.children
+    var orderedSet = reorder(aChildren, b.children)
+    var bChildren = orderedSet.children
+
+    var aLen = aChildren.length
+    var bLen = bChildren.length
+    var len = aLen > bLen ? aLen : bLen
+
+    for (var i = 0; i < len; i++) {
+        var leftNode = aChildren[i]
+        var rightNode = bChildren[i]
+        index += 1
+
+        if (!leftNode) {
+            if (rightNode) {
+                // Excess nodes in b need to be added
+                apply = appendPatch(apply,
+                    new VPatch(VPatch.INSERT, null, rightNode))
+            }
+        } else {
+            walk(leftNode, rightNode, patch, thunks, index)
+        }
+
+        if (isVNode(leftNode) && leftNode.count) {
+            index += leftNode.count
+        }
+    }
+
+    if (orderedSet.moves) {
+        // Reorder nodes last
+        apply = appendPatch(apply, new VPatch(
+            VPatch.ORDER,
+            a,
+            orderedSet.moves
+        ))
+    }
+
+    return apply
+}
+
+function clearState(vNode, patch, index) {
+    // TODO: Make this a single walk, not two
+    unhook(vNode, patch, index)
+    destroyWidgets(vNode, patch, index)
+}
+
+// Patch records for all destroyed widgets must be added because we need
+// a DOM node reference for the destroy function
+function destroyWidgets(vNode, patch, index) {
+    if (isWidget(vNode)) {
+        if (typeof vNode.destroy === "function") {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch(VPatch.REMOVE, vNode, null)
+            )
+        }
+    } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {
+        var children = vNode.children
+        var len = children.length
+        for (var i = 0; i < len; i++) {
+            var child = children[i]
+            index += 1
+
+            destroyWidgets(child, patch, index)
+
+            if (isVNode(child) && child.count) {
+                index += child.count
+            }
+        }
+    } else if (isThunk(vNode)) {
+        doThunks(vNode, null, patch, thunks, index)
+    }
+}
+
+// Create a sub-patch for thunks
+function doThunks(a, b, patch, thunks, index) {
+    var ts = handleThunk(a, b);
+    if(ts.a || ts.b) {
+        // defer rendering, caller is responsible for:
+        //   - filling ts.a.vnode / ts.b.vnode with the result from the thunk
+        //   - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode))
+        // before using it with patch
+        thunks.push({ i: index, p: patch, a: a, b: b});
+    } else {
+        var thunkPatch = diff(a.vnode, b.vnode, thunks)
+        if (hasPatches(thunkPatch)) {
+            patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch)
+        }
+    }
+}
+
+function hasPatches(patch) {
+    for (var index in patch) {
+        if (index !== "a") {
+            return true
+        }
+    }
+
+    return false
+}
+
+// Execute hooks when two nodes are identical
+function unhook(vNode, patch, index) {
+    if (isVNode(vNode)) {
+        if (vNode.hooks) {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch(
+                    VPatch.PROPS,
+                    vNode,
+                    undefinedKeys(vNode.hooks)
+                )
+            )
+        }
+
+        if (vNode.descendantHooks || vNode.hasThunks) {
+            var children = vNode.children
+            var len = children.length
+            for (var i = 0; i < len; i++) {
+                var child = children[i]
+                index += 1
+
+                unhook(child, patch, index)
+
+                if (isVNode(child) && child.count) {
+                    index += child.count
+                }
+            }
+        }
+    } else if (isThunk(vNode)) {
+        doThunks(vNode, null, patch, thunks, index)
+    }
+}
+
+function undefinedKeys(obj) {
+    var result = {}
+
+    for (var key in obj) {
+        result[key] = undefined
+    }
+
+    return result
+}
+
+// List diff, naive left to right reordering
+function reorder(aChildren, bChildren) {
+    // O(M) time, O(M) memory
+    var bChildIndex = keyIndex(bChildren)
+    var bKeys = bChildIndex.keys
+    var bFree = bChildIndex.free
+
+    if (bFree.length === bChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(N) time, O(N) memory
+    var aChildIndex = keyIndex(aChildren)
+    var aKeys = aChildIndex.keys
+    var aFree = aChildIndex.free
+
+    if (aFree.length === aChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(MAX(N, M)) memory
+    var newChildren = []
+
+    var freeIndex = 0
+    var freeCount = bFree.length
+    var deletedItems = 0
+
+    // Iterate through a and match a node in b
+    // O(N) time,
+    for (var i = 0 ; i < aChildren.length; i++) {
+        var aItem = aChildren[i]
+        var itemIndex
+
+        if (aItem.key) {
+            if (bKeys.hasOwnProperty(aItem.key)) {
+                // Match up the old keys
+                itemIndex = bKeys[aItem.key]
+                newChildren.push(bChildren[itemIndex])
+
+            } else {
+                // Remove old keyed items
+                itemIndex = i - deletedItems++
+                newChildren.push(null)
+            }
+        } else {
+            // Match the item in a with the next free item in b
+            if (freeIndex < freeCount) {
+                itemIndex = bFree[freeIndex++]
+                newChildren.push(bChildren[itemIndex])
+            } else {
+                // There are no free items in b to match with
+                // the free items in a, so the extra free nodes
+                // are deleted.
+                itemIndex = i - deletedItems++
+                newChildren.push(null)
+            }
+        }
+    }
+
+    var lastFreeIndex = freeIndex >= bFree.length ?
+        bChildren.length :
+        bFree[freeIndex]
+
+    // Iterate through b and append any new keys
+    // O(M) time
+    for (var j = 0; j < bChildren.length; j++) {
+        var newItem = bChildren[j]
+
+        if (newItem.key) {
+            if (!aKeys.hasOwnProperty(newItem.key)) {
+                // Add any new keyed items
+                // We are adding new items to the end and then sorting them
+                // in place. In future we should insert new items in place.
+                newChildren.push(newItem)
+            }
+        } else if (j >= lastFreeIndex) {
+            // Add any leftover non-keyed items
+            newChildren.push(newItem)
+        }
+    }
+
+    var simulate = newChildren.slice()
+    var simulateIndex = 0
+    var removes = []
+    var inserts = []
+    var simulateItem
+
+    for (var k = 0; k < bChildren.length;) {
+        var wantedItem = bChildren[k]
+        simulateItem = simulate[simulateIndex]
+
+        // remove items
+        while (simulateItem === null && simulate.length) {
+            removes.push(remove(simulate, simulateIndex, null))
+            simulateItem = simulate[simulateIndex]
+        }
+
+        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+            // if we need a key in this position...
+            if (wantedItem.key) {
+                if (simulateItem && simulateItem.key) {
+                    // if an insert doesn't put this key in place, it needs to move
+                    if (bKeys[simulateItem.key] !== k + 1) {
+                        removes.push(remove(simulate, simulateIndex, simulateItem.key))
+                        simulateItem = simulate[simulateIndex]
+                        // if the remove didn't put the wanted item in place, we need to insert it
+                        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+                            inserts.push({key: wantedItem.key, to: k})
+                        }
+                        // items are matching, so skip ahead
+                        else {
+                            simulateIndex++
+                        }
+                    }
+                    else {
+                        inserts.push({key: wantedItem.key, to: k})
+                    }
+                }
+                else {
+                    inserts.push({key: wantedItem.key, to: k})
+                }
+                k++
+            }
+            // a key in simulate has no matching wanted key, remove it
+            else if (simulateItem && simulateItem.key) {
+                removes.push(remove(simulate, simulateIndex, simulateItem.key))
+            }
+        }
+        else {
+            simulateIndex++
+            k++
+        }
+    }
+
+    // remove all the remaining nodes from simulate
+    while(simulateIndex < simulate.length) {
+        simulateItem = simulate[simulateIndex]
+        removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))
+    }
+
+    // If the only moves we have are deletes then we can just
+    // let the delete patch remove these items.
+    if (removes.length === deletedItems && !inserts.length) {
+        return {
+            children: newChildren,
+            moves: null
+        }
+    }
+
+    return {
+        children: newChildren,
+        moves: {
+            removes: removes,
+            inserts: inserts
+        }
+    }
+}
+
+function remove(arr, index, key) {
+    arr.splice(index, 1)
+
+    return {
+        from: index,
+        key: key
+    }
+}
+
+function keyIndex(children) {
+    var keys = {}
+    var free = []
+    var length = children.length
+
+    for (var i = 0; i < length; i++) {
+        var child = children[i]
+
+        if (child.key) {
+            keys[child.key] = i
+        } else {
+            free.push(i)
+        }
+    }
+
+    return {
+        keys: keys,     // A hash of key name to index
+        free: free      // An array of unkeyed item indices
+    }
+}
+
+function appendPatch(apply, patch) {
+    if (apply) {
+        if (isArray(apply)) {
+            apply.push(patch)
+        } else {
+            apply = [apply, patch]
+        }
+
+        return apply
+    } else {
+        return patch
+    }
+}
diff --git a/virtual-dom/handle-thunk.js b/virtual-dom/handle-thunk.js
new file mode 100644
--- /dev/null
+++ b/virtual-dom/handle-thunk.js
@@ -0,0 +1,18 @@
+var isVNode = require("virtual-dom/vnode/is-vnode")
+var isVText = require("virtual-dom/vnode/is-vtext")
+var isWidget = require("virtual-dom/vnode/is-widget")
+var isThunk = require("virtual-dom/vnode/is-thunk")
+
+module.exports = handleThunk
+
+function handleThunk(a, b) {
+    return { a: isThunk(a) ? renderThunk(a, null) : null
+           , b: isThunk(b) ? renderThunk(b, a) : null
+           }
+}
+
+function renderThunk(thunk, previous) {
+    if(thunk.vnode) return null;
+    thunk.render(previous);
+    return thunk.vnode ? null : thunk;
+}
diff --git a/virtual-dom/lib.js b/virtual-dom/lib.js
new file mode 100644
--- /dev/null
+++ b/virtual-dom/lib.js
@@ -0,0 +1,2710 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+   vtree/diff module modified to defer rendering thunks. this makes it possible to
+   implement thunks that cannot be called directly as a function, but have
+   an asynchronous callback or require evaluation in some specific runtime
+   environment
+ */
+
+var isArray = require("x-is-array")
+
+var VPatch = require("virtual-dom/vnode/vpatch")
+var isVNode = require("virtual-dom/vnode/is-vnode")
+var isVText = require("virtual-dom/vnode/is-vtext")
+var isWidget = require("virtual-dom/vnode/is-widget")
+var isThunk = require("virtual-dom/vnode/is-thunk")
+var handleThunk = require("./handle-thunk")
+
+var diffProps = require("virtual-dom/vtree/diff-props")
+
+module.exports = diff
+
+// unevaluated thunks are added to the thunks argument (array)
+function diff(a, b, thunks) {
+    if(!a) throw new Error ("diff a: " + a);
+    if(!a) throw new Error ("diff b: " + b);
+    var patch = { a: a }
+    walk(a, b, patch, thunks, 0)
+    return patch
+}
+
+function walk(a, b, patch, thunks, index) {
+    if (a === b) {
+        return
+    }
+
+    var apply = patch[index]
+    var applyClear = false
+
+    if (isThunk(a) || isThunk(b)) {
+        doThunks(a, b, patch, thunks, index)
+    } else if (b == null) {
+
+        // If a is a widget we will add a remove patch for it
+        // Otherwise any child widgets/hooks must be destroyed.
+        // This prevents adding two remove patches for a widget.
+        if (!isWidget(a)) {
+            clearState(a, patch, index)
+            apply = patch[index]
+        }
+
+        apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b))
+    } else if (isVNode(b)) {
+        if (isVNode(a)) {
+            if (a.tagName === b.tagName &&
+                a.namespace === b.namespace &&
+                a.key === b.key) {
+                var propsPatch = diffProps(a.properties, b.properties)
+                if (propsPatch) {
+                    apply = appendPatch(apply,
+                        new VPatch(VPatch.PROPS, a, propsPatch))
+                }
+                apply = diffChildren(a, b, patch, apply, thunks, index)
+            } else {
+                apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
+                applyClear = true
+            }
+        } else {
+            apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
+            applyClear = true
+        }
+    } else if (isVText(b)) {
+        if (!isVText(a)) {
+            apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
+            applyClear = true
+        } else if (a.text !== b.text) {
+            apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
+        }
+    } else if (isWidget(b)) {
+        if (!isWidget(a)) {
+            applyClear = true
+        }
+
+        apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))
+    }
+
+    if (apply) {
+        patch[index] = apply
+    }
+
+    if (applyClear) {
+        clearState(a, patch, index)
+    }
+}
+
+function diffChildren(a, b, patch, apply, thunks, index) {
+    var aChildren = a.children
+    var orderedSet = reorder(aChildren, b.children)
+    var bChildren = orderedSet.children
+
+    var aLen = aChildren.length
+    var bLen = bChildren.length
+    var len = aLen > bLen ? aLen : bLen
+
+    for (var i = 0; i < len; i++) {
+        var leftNode = aChildren[i]
+        var rightNode = bChildren[i]
+        index += 1
+
+        if (!leftNode) {
+            if (rightNode) {
+                // Excess nodes in b need to be added
+                apply = appendPatch(apply,
+                    new VPatch(VPatch.INSERT, null, rightNode))
+            }
+        } else {
+            walk(leftNode, rightNode, patch, thunks, index)
+        }
+
+        if (isVNode(leftNode) && leftNode.count) {
+            index += leftNode.count
+        }
+    }
+
+    if (orderedSet.moves) {
+        // Reorder nodes last
+        apply = appendPatch(apply, new VPatch(
+            VPatch.ORDER,
+            a,
+            orderedSet.moves
+        ))
+    }
+
+    return apply
+}
+
+function clearState(vNode, patch, index) {
+    // TODO: Make this a single walk, not two
+    unhook(vNode, patch, index)
+    destroyWidgets(vNode, patch, index)
+}
+
+// Patch records for all destroyed widgets must be added because we need
+// a DOM node reference for the destroy function
+function destroyWidgets(vNode, patch, index) {
+    if (isWidget(vNode)) {
+        if (typeof vNode.destroy === "function") {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch(VPatch.REMOVE, vNode, null)
+            )
+        }
+    } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {
+        var children = vNode.children
+        var len = children.length
+        for (var i = 0; i < len; i++) {
+            var child = children[i]
+            index += 1
+
+            destroyWidgets(child, patch, index)
+
+            if (isVNode(child) && child.count) {
+                index += child.count
+            }
+        }
+    } else if (isThunk(vNode)) {
+        doThunks(vNode, null, patch, thunks, index)
+    }
+}
+
+// Create a sub-patch for thunks
+function doThunks(a, b, patch, thunks, index) {
+    var ts = handleThunk(a, b);
+    if(ts.a || ts.b) {
+        // defer rendering, caller is responsible for:
+        //   - filling ts.a.vnode / ts.b.vnode with the result from the thunk
+        //   - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode))
+        // before using it with patch
+        thunks.push({ i: index, p: patch, a: a, b: b});
+    } else {
+        var thunkPatch = diff(a.vnode, b.vnode, thunks)
+        if (hasPatches(thunkPatch)) {
+            patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch)
+        }
+    }
+}
+
+function hasPatches(patch) {
+    for (var index in patch) {
+        if (index !== "a") {
+            return true
+        }
+    }
+
+    return false
+}
+
+// Execute hooks when two nodes are identical
+function unhook(vNode, patch, index) {
+    if (isVNode(vNode)) {
+        if (vNode.hooks) {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch(
+                    VPatch.PROPS,
+                    vNode,
+                    undefinedKeys(vNode.hooks)
+                )
+            )
+        }
+
+        if (vNode.descendantHooks || vNode.hasThunks) {
+            var children = vNode.children
+            var len = children.length
+            for (var i = 0; i < len; i++) {
+                var child = children[i]
+                index += 1
+
+                unhook(child, patch, index)
+
+                if (isVNode(child) && child.count) {
+                    index += child.count
+                }
+            }
+        }
+    } else if (isThunk(vNode)) {
+        doThunks(vNode, null, patch, thunks, index)
+    }
+}
+
+function undefinedKeys(obj) {
+    var result = {}
+
+    for (var key in obj) {
+        result[key] = undefined
+    }
+
+    return result
+}
+
+// List diff, naive left to right reordering
+function reorder(aChildren, bChildren) {
+    // O(M) time, O(M) memory
+    var bChildIndex = keyIndex(bChildren)
+    var bKeys = bChildIndex.keys
+    var bFree = bChildIndex.free
+
+    if (bFree.length === bChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(N) time, O(N) memory
+    var aChildIndex = keyIndex(aChildren)
+    var aKeys = aChildIndex.keys
+    var aFree = aChildIndex.free
+
+    if (aFree.length === aChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(MAX(N, M)) memory
+    var newChildren = []
+
+    var freeIndex = 0
+    var freeCount = bFree.length
+    var deletedItems = 0
+
+    // Iterate through a and match a node in b
+    // O(N) time,
+    for (var i = 0 ; i < aChildren.length; i++) {
+        var aItem = aChildren[i]
+        var itemIndex
+
+        if (aItem.key) {
+            if (bKeys.hasOwnProperty(aItem.key)) {
+                // Match up the old keys
+                itemIndex = bKeys[aItem.key]
+                newChildren.push(bChildren[itemIndex])
+
+            } else {
+                // Remove old keyed items
+                itemIndex = i - deletedItems++
+                newChildren.push(null)
+            }
+        } else {
+            // Match the item in a with the next free item in b
+            if (freeIndex < freeCount) {
+                itemIndex = bFree[freeIndex++]
+                newChildren.push(bChildren[itemIndex])
+            } else {
+                // There are no free items in b to match with
+                // the free items in a, so the extra free nodes
+                // are deleted.
+                itemIndex = i - deletedItems++
+                newChildren.push(null)
+            }
+        }
+    }
+
+    var lastFreeIndex = freeIndex >= bFree.length ?
+        bChildren.length :
+        bFree[freeIndex]
+
+    // Iterate through b and append any new keys
+    // O(M) time
+    for (var j = 0; j < bChildren.length; j++) {
+        var newItem = bChildren[j]
+
+        if (newItem.key) {
+            if (!aKeys.hasOwnProperty(newItem.key)) {
+                // Add any new keyed items
+                // We are adding new items to the end and then sorting them
+                // in place. In future we should insert new items in place.
+                newChildren.push(newItem)
+            }
+        } else if (j >= lastFreeIndex) {
+            // Add any leftover non-keyed items
+            newChildren.push(newItem)
+        }
+    }
+
+    var simulate = newChildren.slice()
+    var simulateIndex = 0
+    var removes = []
+    var inserts = []
+    var simulateItem
+
+    for (var k = 0; k < bChildren.length;) {
+        var wantedItem = bChildren[k]
+        simulateItem = simulate[simulateIndex]
+
+        // remove items
+        while (simulateItem === null && simulate.length) {
+            removes.push(remove(simulate, simulateIndex, null))
+            simulateItem = simulate[simulateIndex]
+        }
+
+        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+            // if we need a key in this position...
+            if (wantedItem.key) {
+                if (simulateItem && simulateItem.key) {
+                    // if an insert doesn't put this key in place, it needs to move
+                    if (bKeys[simulateItem.key] !== k + 1) {
+                        removes.push(remove(simulate, simulateIndex, simulateItem.key))
+                        simulateItem = simulate[simulateIndex]
+                        // if the remove didn't put the wanted item in place, we need to insert it
+                        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+                            inserts.push({key: wantedItem.key, to: k})
+                        }
+                        // items are matching, so skip ahead
+                        else {
+                            simulateIndex++
+                        }
+                    }
+                    else {
+                        inserts.push({key: wantedItem.key, to: k})
+                    }
+                }
+                else {
+                    inserts.push({key: wantedItem.key, to: k})
+                }
+                k++
+            }
+            // a key in simulate has no matching wanted key, remove it
+            else if (simulateItem && simulateItem.key) {
+                removes.push(remove(simulate, simulateIndex, simulateItem.key))
+            }
+        }
+        else {
+            simulateIndex++
+            k++
+        }
+    }
+
+    // remove all the remaining nodes from simulate
+    while(simulateIndex < simulate.length) {
+        simulateItem = simulate[simulateIndex]
+        removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))
+    }
+
+    // If the only moves we have are deletes then we can just
+    // let the delete patch remove these items.
+    if (removes.length === deletedItems && !inserts.length) {
+        return {
+            children: newChildren,
+            moves: null
+        }
+    }
+
+    return {
+        children: newChildren,
+        moves: {
+            removes: removes,
+            inserts: inserts
+        }
+    }
+}
+
+function remove(arr, index, key) {
+    arr.splice(index, 1)
+
+    return {
+        from: index,
+        key: key
+    }
+}
+
+function keyIndex(children) {
+    var keys = {}
+    var free = []
+    var length = children.length
+
+    for (var i = 0; i < length; i++) {
+        var child = children[i]
+
+        if (child.key) {
+            keys[child.key] = i
+        } else {
+            free.push(i)
+        }
+    }
+
+    return {
+        keys: keys,     // A hash of key name to index
+        free: free      // An array of unkeyed item indices
+    }
+}
+
+function appendPatch(apply, patch) {
+    if (apply) {
+        if (isArray(apply)) {
+            apply.push(patch)
+        } else {
+            apply = [apply, patch]
+        }
+
+        return apply
+    } else {
+        return patch
+    }
+}
+
+},{"./handle-thunk":2,"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-vtext":40,"virtual-dom/vnode/is-widget":41,"virtual-dom/vnode/vpatch":44,"virtual-dom/vtree/diff-props":46,"x-is-array":47}],2:[function(require,module,exports){
+var isVNode = require("virtual-dom/vnode/is-vnode")
+var isVText = require("virtual-dom/vnode/is-vtext")
+var isWidget = require("virtual-dom/vnode/is-widget")
+var isThunk = require("virtual-dom/vnode/is-thunk")
+
+module.exports = handleThunk
+
+function handleThunk(a, b) {
+    return { a: isThunk(a) ? renderThunk(a, null) : null
+           , b: isThunk(b) ? renderThunk(b, a) : null
+           }
+}
+
+function renderThunk(thunk, previous) {
+    if(thunk.vnode) return null;
+    thunk.render(previous);
+    return thunk.vnode ? null : thunk;
+}
+
+},{"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-vtext":40,"virtual-dom/vnode/is-widget":41}],3:[function(require,module,exports){
+/*
+  to generate lib.js, install virtual-dom and process file:
+
+     $ npm install
+     $ grunt
+   the ./diff module is vtree/diff with a few changes to
+   allow diff to run in an asynchronous thread in the presence of
+   memoized nodes.
+ */
+
+/*
+   Note on memory management:
+
+   To ensure accurate heap tracing for finalization and profiling purposes,
+   GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some
+   Haskell values inside JavaScript references and uses extensible retention
+   to collect these values. It's crucial that all data structures that may
+   contain Haskell values are stored directly in a JSVal and not inside other
+   JS data structures.
+
+   The recognized types are:
+
+     - HSPatch:
+         The patch object contains the new (target) virtual-dom tree and
+         the original tree is reachable trough the parent. Since all handlers,
+         components and thunks are reachable through these trees, the patch itself
+         does not need to be traversed.
+
+     - HSMount:
+         All mounted HSMount points are scanned as roots. The current virtual tree
+         (mount.vtree) is traversed for this.
+
+     - HSComponent:
+         Traversed when reachable through Haskell heap or virtual-dom tree. Contains
+         current tree (component.vtree) and rendering action (component.hsRender)
+
+     - HSThunk:
+         Traversed when reachable through Haskell heap or virtual-dom tree. Contains
+         Haskell suspension (thunk.hst) or rendered tree (thunk.vnode)
+
+     - virtual node ( isVirtualNode(x) )
+         Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*'
+         properties, which virtual-dom adds to the node's hooks (vnode.hooks). If
+         a virtual node contains thunks, widgets or any of its descendants have hooks,
+         the children of the node have to be traversed.
+
+   forceThunks and forcePatch fill an array of thunks, which is not directly
+   scannable; however, these functions are only used as part of a `diff` operation.
+   The initial diff creates an HSPatch object, through which the original and target
+   virtual-dom tree are completely reachable.
+ */
+
+var isVirtualNode = require('virtual-dom/vnode/is-vnode');
+var isThunk       = require('virtual-dom/vnode/is-thunk');
+var isWidget      = require("virtual-dom/vnode/is-widget");
+var h             = require('virtual-dom/h');
+var isArray       = require('x-is-array');
+var VPatch        = require("virtual-dom/vnode/vpatch");
+var VText         = require('virtual-dom/vnode/vtext');
+var vdomPatch     = require('virtual-dom/vdom/patch');
+var DomDelegator  = require('dom-delegator');
+
+var diff = require('./diff');
+
+var VRenderableN = 0;
+
+/** @constructor */
+function HSPatch(patch, old, vnode, parent) {
+  this.patch   = patch;
+  this.old     = old;
+  this.vnode   = vnode;
+  this.parent  = parent;
+}
+
+/** @constructor */
+function HSThunk(t, ids, key) {
+  this.hst        = t;   // haskell thunk
+  this.ids        = ids; // array of haskell unique ids
+  this.key        = key;
+  this.vnode      = null;
+  this._ghcjsMark = 0;
+}
+
+HSThunk.prototype.type = 'Thunk';
+
+/* 
+  render returns the deferred rendering object
+  null if the thunk has already been rendered, in which case the value is in this.vnode
+ */
+HSThunk.prototype.render = function(previous) {
+  if(previous && !this.vnode && eqIds(this.ids, previous.ids)) {
+    if(previous.hst) {
+      this.hst = previous.hst;
+    } else {
+      this.hst   = null;
+      this.vnode = previous.vnode;
+    }
+  }
+  return this.vnode ? null : this;
+}
+
+/** @constructor */
+function HSComponent(r, mnt, unmnt, key) {
+  this._key      = ++VRenderableN;
+  this.hsRender  = r;   // IO action that produces a vdom tree
+  this.hsMount   = mnt;
+  this.hsUnmount = unmnt;
+  this.key       = key || this._key;
+  this.vnode     = this.initialVnode = new VText("");
+  this.pending   = [];
+  this.mounts    = {};
+  this.version   = 0;
+  this.latest    = 0;
+}
+
+HSComponent.prototype.type = 'Widget';
+
+HSComponent.prototype.init = function() {
+  var n = document.createTextNode('');
+  if(this.vnode !== this.initialVnode) {
+    var thunks = [];
+    var p = diff(this.initialVnode, this.vnode, thunks);
+    if(thunks.length !== 0) {
+      throw new Error("HSComponent vnode contains unevaluated thunks");
+    }
+    n = vdomPatch(n, p);
+  }
+  var m = new HSComponentMount(n);
+  n._key = m._key;
+  n._widget = this;
+  this.mounts[m._key] = m;
+  if(this.hsMount) {
+    h$vdomMountComponentCallback(this.hsMount, m._key, this);
+  }
+  return n;
+}
+
+HSComponent.prototype.destroy = function(domNode) {
+  delete this.mounts[domNode._key];
+  if(this.hsUnmount) {
+    h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode);
+  }
+}
+
+HSComponent.prototype.diff = function(v, thunks) {
+  var vn = this.vnode;
+  if(this.pending.length > 0) {
+    vn = this.pending[this.pending.length-1].vnode;
+  }
+  return new HSPatch( diff(vn, v, thunks)
+		    , vn
+		    , v
+		    , this);
+}
+
+HSComponent.prototype.addPatch = function(p) {
+  var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]
+                                    : this.vnode;
+  if(p.old === cur) this.pending.push(p);
+}
+
+HSComponent.prototype.patch = function(p) {
+  if(this.pending.length > 0) {
+    var pnd = this.pending;
+    this.pending = [];
+    for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);
+  }
+  if(!p) return;
+  if(p.parent !== this || p.old !== this.vnode) {
+    return false;
+  }
+  for(var k in this.mounts) {
+    var m = this.mounts[k];
+    m.node = vdomPatch(m.node, p.patch);
+  }
+  this.vnode = p.vnode;
+  return true;
+}
+
+// only use this for manually updated components (i.e. no diff/patch)
+HSComponent.prototype.updateMount = function(mnt, node) {
+  var m = this.mounts[mnt];
+  node._key = mnt;
+  node._widget = this;
+  m.node.parentNode.replaceChild(node, m.node);
+  m.node = node;
+}
+
+HSComponent.prototype.update = function(leftVNode, node) {
+  if(node._widget) {
+    if(node._widget == this) return node;
+    node._widget.destroy(node);
+  }
+  return this.init();
+}
+
+var HSComponentMountN = 0;
+function HSComponentMount(domNode) {
+  this._key = ++HSComponentMountN;
+  this.node = domNode;
+}
+
+/** @constructor */
+function HSMount(domNode) {
+  this._key       = ++VRenderableN;
+  // this.version    = 0;
+  this.vnode      = new VText(""); // currently rendered vdom tree
+  this.pending    = [];            // pending patches, not yet applied
+  this.node       = document.createTextNode("");
+  this.parentNode = domNode;
+}
+
+HSMount.prototype.diff = function(v, thunks) {
+  var vn = this.vnode;
+  if(this.pending.length > 0) {
+    vn = this.pending[this.pending.length-1].vnode;
+  }
+  return new HSPatch( diff(vn, v, thunks)
+		    , vn
+		    , v
+		    , this);
+}
+
+HSMount.prototype.addPatch = function(p) {
+  var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]
+                                    : this.vnode;
+  if(p.old === cur) this.pending.push(p);
+}
+
+// HSMount.patch(null) to flush pending list
+HSMount.prototype.patch = function(p) {
+  if(this.pending.length > 0) {
+    var pnd = this.pending;
+    this.pending = [];
+    for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);
+  }
+  if(!p) return;
+  if(p.parent !== this || p.old !== this.vnode) {
+    return false;
+  }
+  this.node  = vdomPatch(this.node, p.patch);
+  this.vnode = p.vnode;
+  return true;
+}
+  
+/* mount a vdom tree, making it visible to extensible retention */
+function mount(domNode) {
+  while(domNode.firstChild) domNode.removeChild(domNode.firstChild);
+  var m = new HSMount(domNode);
+  domNode.appendChild(m.node);
+  vdomMounts.add(m);
+  return m;
+}
+
+/* unmount a tree, removing all child nodes. */
+function unmount(vmount) {
+  var n = vmount.parentNode;
+  while(n.firstChild) n.removeChild(n.firstChild);
+  vdomMounts.remove(vmount);
+}
+
+/*
+   Compare lists of object identifiers associated with a thunk node. If the lists are equal,
+   the subtree does not have to be recomputed.
+*/
+function eqIds(ids1, ids2) {
+  if(!ids1 || !ids2 || ids1.length != ids2.length) return false;
+  for(var i=ids1.length-1;i>=0;i--) {
+    var id1 = ids1[i], id2 = ids2[i];
+    if(typeof id1 === 'number') {
+      if(typeof id2 !== 'number') return false;
+      if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false;
+    } else {
+      if(id1 !== id2) return false;
+    }
+  }
+  return true;
+}
+
+function forcePatch(p) {
+  var thunks = [], i, j, pi;
+  for(i in p) {
+    var pi = p[i];
+    if(isArray(pi)) {
+      for(j=pi.length-1;j>=0;j--) {
+	forceTree(pi[j].patch, thunks);
+      }
+    }
+    else if(pi.patch) forceTree(pi.patch, thunks);
+    else forceTree(pi, thunks);
+  }
+  return thunks;
+}
+
+function forceTree(n, t) {
+  if(isThunk(n)) {
+    if(n.vnode) forceTree(n.vnode, t);
+    else t.push(n);
+  } else if(isVirtualNode(n) && n.hasThunks) {
+    for(var i=n.children.length-1;i>=0;i--) {
+      forceTree(n.children[i], t);
+    }
+  }
+}
+
+/*
+   scan all mounted virtual-dom trees
+ */
+function scanMounts(currentMark) {
+  var i = vdomMounts.iter(), m, res = [];
+  while((m = i.next()) !== null) {
+    scanTreeRec(m.vnode, res, currentMark);
+    if(m.pending.length > 0) {
+      scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark);
+    }
+  }
+  return res;
+}
+
+/*
+   scan a tree (extensible retention callback).
+
+   returns:
+     - an array of haskell items if any
+     - true if no haskell items have been found
+     - false if the object is not a ghcjs-vdom tree
+         (fallthrough to other extensible retention scanners)
+ */
+var scanTreeRes = [];
+function scanTree(o, currentMark) {
+  if(isVirtualNode(o) || isThunk(o) || isWidget(o) ||
+     o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) {
+    var r = scanTreeRes;
+    scanTreeRec(o, r, currentMark);
+    if(r.length > 0) {
+      scanTreeRes = [];
+      return r;
+    } else {
+      return true;
+    }
+  } else { // not a ghcjs-vdom object, fall through
+    return false; 
+  }
+}
+
+function scanTreeRec(o, r, currentMark) {
+  if(o instanceof HSPatch) {
+    scanTreeRec(o.vnode, r, currentMark);
+    scanTreeRec(o.parent);
+  } else if(o instanceof HSThunk) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      if(o.hst) r.push(o.hst);
+      if(o.vnode) scanTreeRec(o.vnode, r, currentMark);
+    }
+  } else if(o instanceof HSComponent) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      if(o.hsRender) r.push(o.hsRender);
+      if(o.hsMount) r.push(o.hsMount);
+      if(o.hsUnmount) r.push(o.hsUnmount);
+      if(o.vnode) scanTreeRec(o.vnode, r, currentMark);
+      if(o.pending.length > 0) {
+	scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark);
+      }
+    }
+  } else if(isVirtualNode(o)) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      // collect event handlers
+      var hooks = o.hooks;
+      for(var p in hooks) {
+	if(p.indexOf('ev-') === 0) {
+	  var handler = hooks[p];
+	  if(handler.value && handler.value.hsAction) {
+	    r.push(handler.value.hsAction);
+	  }
+	}
+      }
+      // recurse if any of the children may have thunks, components or handlers
+      if(o.hasWidgets || o.hasThunks || o.descendantHooks) {
+	for(var i=o.children.length-1;i>=0;i--) {
+          scanTreeRec(o.children[i], r, currentMark);
+	}
+      }
+    }
+  }
+}
+
+function setThunkPatch(n, p) {
+  if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p);
+}
+
+function hasPatches(patch) {
+  for (var index in patch) {
+    if (index !== "a") {
+      return true;
+    }
+  }
+  return false;
+}
+
+function initDelegator(evTypes) {
+  var d = DomDelegator();
+  var l = evTypes.length;
+  for(var i = 0; i < l; i++) {
+    d.listenTo(evTypes[i]);
+  }
+}
+
+function v(tag, props, children) {
+  return h(tag, props, children);
+}
+
+function t(text) {
+  return new VText(text);
+}
+
+function th(t, ids, key) {
+  return new HSThunk(t, ids, key);
+}
+
+function c(r, m, u, key) {
+  return new HSComponent(r, m, u, key);
+}
+
+function makeHandler(action, async) {
+  var f = function(ev) {
+    return h$vdomEventCallback(async, action, ev);
+  }
+  f.hsAction = action;
+  return f;
+}
+
+var vdomMounts = new h$Set();
+
+module.exports = { setThunkPatch: setThunkPatch
+                 , forceTree:     forceTree
+                 , forcePatch:    forcePatch
+                 , diff:          diff
+                 , mount:         mount
+                 , unmount:       unmount
+                 , initDelegator: initDelegator
+                 , v:             v
+                 , th:            th
+                 , t:             t
+                 , c:             c
+                 , makeHandler:   makeHandler
+                 };
+
+// the global variable we're using in the bindings
+h$vdom = module.exports;
+
+h$registerExtensibleRetention(scanTree);
+h$registerExtensibleRetentionRoot(scanMounts);
+
+
+
+},{"./diff":1,"dom-delegator":6,"virtual-dom/h":19,"virtual-dom/vdom/patch":30,"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-widget":41,"virtual-dom/vnode/vpatch":44,"virtual-dom/vnode/vtext":45,"x-is-array":47}],4:[function(require,module,exports){
+var EvStore = require("ev-store")
+
+module.exports = addEvent
+
+function addEvent(target, type, handler) {
+    var events = EvStore(target)
+    var event = events[type]
+
+    if (!event) {
+        events[type] = handler
+    } else if (Array.isArray(event)) {
+        if (event.indexOf(handler) === -1) {
+            event.push(handler)
+        }
+    } else if (event !== handler) {
+        events[type] = [event, handler]
+    }
+}
+
+},{"ev-store":8}],5:[function(require,module,exports){
+var globalDocument = require("global/document")
+var EvStore = require("ev-store")
+var createStore = require("weakmap-shim/create-store")
+
+var addEvent = require("./add-event.js")
+var removeEvent = require("./remove-event.js")
+var ProxyEvent = require("./proxy-event.js")
+
+var HANDLER_STORE = createStore()
+
+module.exports = DOMDelegator
+
+function DOMDelegator(document) {
+    if (!(this instanceof DOMDelegator)) {
+        return new DOMDelegator(document);
+    }
+
+    document = document || globalDocument
+
+    this.target = document.documentElement
+    this.events = {}
+    this.rawEventListeners = {}
+    this.globalListeners = {}
+}
+
+DOMDelegator.prototype.addEventListener = addEvent
+DOMDelegator.prototype.removeEventListener = removeEvent
+
+DOMDelegator.allocateHandle =
+    function allocateHandle(func) {
+        var handle = new Handle()
+
+        HANDLER_STORE(handle).func = func;
+
+        return handle
+    }
+
+DOMDelegator.transformHandle =
+    function transformHandle(handle, broadcast) {
+        var func = HANDLER_STORE(handle).func
+
+        return this.allocateHandle(function (ev) {
+            broadcast(ev, func);
+        })
+    }
+
+DOMDelegator.prototype.addGlobalEventListener =
+    function addGlobalEventListener(eventName, fn) {
+        var listeners = this.globalListeners[eventName] || [];
+        if (listeners.indexOf(fn) === -1) {
+            listeners.push(fn)
+        }
+
+        this.globalListeners[eventName] = listeners;
+    }
+
+DOMDelegator.prototype.removeGlobalEventListener =
+    function removeGlobalEventListener(eventName, fn) {
+        var listeners = this.globalListeners[eventName] || [];
+
+        var index = listeners.indexOf(fn)
+        if (index !== -1) {
+            listeners.splice(index, 1)
+        }
+    }
+
+DOMDelegator.prototype.listenTo = function listenTo(eventName) {
+    if (!(eventName in this.events)) {
+        this.events[eventName] = 0;
+    }
+
+    this.events[eventName]++;
+
+    if (this.events[eventName] !== 1) {
+        return
+    }
+
+    var listener = this.rawEventListeners[eventName]
+    if (!listener) {
+        listener = this.rawEventListeners[eventName] =
+            createHandler(eventName, this)
+    }
+
+    this.target.addEventListener(eventName, listener, true)
+}
+
+DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) {
+    if (!(eventName in this.events)) {
+        this.events[eventName] = 0;
+    }
+
+    if (this.events[eventName] === 0) {
+        throw new Error("already unlistened to event.");
+    }
+
+    this.events[eventName]--;
+
+    if (this.events[eventName] !== 0) {
+        return
+    }
+
+    var listener = this.rawEventListeners[eventName]
+
+    if (!listener) {
+        throw new Error("dom-delegator#unlistenTo: cannot " +
+            "unlisten to " + eventName)
+    }
+
+    this.target.removeEventListener(eventName, listener, true)
+}
+
+function createHandler(eventName, delegator) {
+    var globalListeners = delegator.globalListeners;
+    var delegatorTarget = delegator.target;
+
+    return handler
+
+    function handler(ev) {
+        var globalHandlers = globalListeners[eventName] || []
+
+        if (globalHandlers.length > 0) {
+            var globalEvent = new ProxyEvent(ev);
+            globalEvent.currentTarget = delegatorTarget;
+            callListeners(globalHandlers, globalEvent)
+        }
+
+        findAndInvokeListeners(ev.target, ev, eventName)
+    }
+}
+
+function findAndInvokeListeners(elem, ev, eventName) {
+    var listener = getListener(elem, eventName)
+
+    if (listener && listener.handlers.length > 0) {
+        var listenerEvent = new ProxyEvent(ev);
+        listenerEvent.currentTarget = listener.currentTarget
+        callListeners(listener.handlers, listenerEvent)
+
+        if (listenerEvent._bubbles) {
+            var nextTarget = listener.currentTarget.parentNode
+            findAndInvokeListeners(nextTarget, ev, eventName)
+        }
+    }
+}
+
+function getListener(target, type) {
+    // terminate recursion if parent is `null`
+    if (target === null || typeof target === "undefined") {
+        return null
+    }
+
+    var events = EvStore(target)
+    // fetch list of handler fns for this event
+    var handler = events[type]
+    var allHandler = events.event
+
+    if (!handler && !allHandler) {
+        return getListener(target.parentNode, type)
+    }
+
+    var handlers = [].concat(handler || [], allHandler || [])
+    return new Listener(target, handlers)
+}
+
+function callListeners(handlers, ev) {
+    handlers.forEach(function (handler) {
+        if (typeof handler === "function") {
+            handler(ev)
+        } else if (typeof handler.handleEvent === "function") {
+            handler.handleEvent(ev)
+        } else if (handler.type === "dom-delegator-handle") {
+            HANDLER_STORE(handler).func(ev)
+        } else {
+            throw new Error("dom-delegator: unknown handler " +
+                "found: " + JSON.stringify(handlers));
+        }
+    })
+}
+
+function Listener(target, handlers) {
+    this.currentTarget = target
+    this.handlers = handlers
+}
+
+function Handle() {
+    this.type = "dom-delegator-handle"
+}
+
+},{"./add-event.js":4,"./proxy-event.js":16,"./remove-event.js":17,"ev-store":8,"global/document":11,"weakmap-shim/create-store":14}],6:[function(require,module,exports){
+var Individual = require("individual")
+var cuid = require("cuid")
+var globalDocument = require("global/document")
+
+var DOMDelegator = require("./dom-delegator.js")
+
+var versionKey = "13"
+var cacheKey = "__DOM_DELEGATOR_CACHE@" + versionKey
+var cacheTokenKey = "__DOM_DELEGATOR_CACHE_TOKEN@" + versionKey
+var delegatorCache = Individual(cacheKey, {
+    delegators: {}
+})
+var commonEvents = [
+    "blur", "change", "click",  "contextmenu", "dblclick",
+    "error","focus", "focusin", "focusout", "input", "keydown",
+    "keypress", "keyup", "load", "mousedown", "mouseup",
+    "resize", "select", "submit", "touchcancel",
+    "touchend", "touchstart", "unload"
+]
+
+/*  Delegator is a thin wrapper around a singleton `DOMDelegator`
+        instance.
+
+    Only one DOMDelegator should exist because we do not want
+        duplicate event listeners bound to the DOM.
+
+    `Delegator` will also `listenTo()` all events unless
+        every caller opts out of it
+*/
+module.exports = Delegator
+
+function Delegator(opts) {
+    opts = opts || {}
+    var document = opts.document || globalDocument
+
+    var cacheKey = document[cacheTokenKey]
+
+    if (!cacheKey) {
+        cacheKey =
+            document[cacheTokenKey] = cuid()
+    }
+
+    var delegator = delegatorCache.delegators[cacheKey]
+
+    if (!delegator) {
+        delegator = delegatorCache.delegators[cacheKey] =
+            new DOMDelegator(document)
+    }
+
+    if (opts.defaultEvents !== false) {
+        for (var i = 0; i < commonEvents.length; i++) {
+            delegator.listenTo(commonEvents[i])
+        }
+    }
+
+    return delegator
+}
+
+Delegator.allocateHandle = DOMDelegator.allocateHandle;
+Delegator.transformHandle = DOMDelegator.transformHandle;
+
+},{"./dom-delegator.js":5,"cuid":7,"global/document":11,"individual":12}],7:[function(require,module,exports){
+/**
+ * cuid.js
+ * Collision-resistant UID generator for browsers and node.
+ * Sequential for fast db lookups and recency sorting.
+ * Safe for element IDs and server-side lookups.
+ *
+ * Extracted from CLCTR
+ *
+ * Copyright (c) Eric Elliott 2012
+ * MIT License
+ */
+
+/*global window, navigator, document, require, process, module */
+(function (app) {
+  'use strict';
+  var namespace = 'cuid',
+    c = 0,
+    blockSize = 4,
+    base = 36,
+    discreteValues = Math.pow(base, blockSize),
+
+    pad = function pad(num, size) {
+      var s = "000000000" + num;
+      return s.substr(s.length-size);
+    },
+
+    randomBlock = function randomBlock() {
+      return pad((Math.random() *
+            discreteValues << 0)
+            .toString(base), blockSize);
+    },
+
+    safeCounter = function () {
+      c = (c < discreteValues) ? c : 0;
+      c++; // this is not subliminal
+      return c - 1;
+    },
+
+    api = function cuid() {
+      // Starting with a lowercase letter makes
+      // it HTML element ID friendly.
+      var letter = 'c', // hard-coded allows for sequential access
+
+        // timestamp
+        // warning: this exposes the exact date and time
+        // that the uid was created.
+        timestamp = (new Date().getTime()).toString(base),
+
+        // Prevent same-machine collisions.
+        counter,
+
+        // A few chars to generate distinct ids for different
+        // clients (so different computers are far less
+        // likely to generate the same id)
+        fingerprint = api.fingerprint(),
+
+        // Grab some more chars from Math.random()
+        random = randomBlock() + randomBlock();
+
+        counter = pad(safeCounter().toString(base), blockSize);
+
+      return  (letter + timestamp + counter + fingerprint + random);
+    };
+
+  api.slug = function slug() {
+    var date = new Date().getTime().toString(36),
+      counter,
+      print = api.fingerprint().slice(0,1) +
+        api.fingerprint().slice(-1),
+      random = randomBlock().slice(-2);
+
+      counter = safeCounter().toString(36).slice(-4);
+
+    return date.slice(-2) +
+      counter + print + random;
+  };
+
+  api.globalCount = function globalCount() {
+    // We want to cache the results of this
+    var cache = (function calc() {
+        var i,
+          count = 0;
+
+        for (i in window) {
+          count++;
+        }
+
+        return count;
+      }());
+
+    api.globalCount = function () { return cache; };
+    return cache;
+  };
+
+  api.fingerprint = function browserPrint() {
+    return pad((navigator.mimeTypes.length +
+      navigator.userAgent.length).toString(36) +
+      api.globalCount().toString(36), 4);
+  };
+
+  // don't change anything from here down.
+  if (app.register) {
+    app.register(namespace, api);
+  } else if (typeof module !== 'undefined') {
+    module.exports = api;
+  } else {
+    app[namespace] = api;
+  }
+
+}(this.applitude || this));
+
+},{}],8:[function(require,module,exports){
+'use strict';
+
+var OneVersionConstraint = require('individual/one-version');
+
+var MY_VERSION = '7';
+OneVersionConstraint('ev-store', MY_VERSION);
+
+var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
+
+module.exports = EvStore;
+
+function EvStore(elem) {
+    var hash = elem[hashKey];
+
+    if (!hash) {
+        hash = elem[hashKey] = {};
+    }
+
+    return hash;
+}
+
+},{"individual/one-version":10}],9:[function(require,module,exports){
+(function (global){
+'use strict';
+
+/*global window, global*/
+
+var root = typeof window !== 'undefined' ?
+    window : typeof global !== 'undefined' ?
+    global : {};
+
+module.exports = Individual;
+
+function Individual(key, value) {
+    if (key in root) {
+        return root[key];
+    }
+
+    root[key] = value;
+
+    return value;
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],10:[function(require,module,exports){
+'use strict';
+
+var Individual = require('./index.js');
+
+module.exports = OneVersion;
+
+function OneVersion(moduleName, version, defaultValue) {
+    var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
+    var enforceKey = key + '_ENFORCE_SINGLETON';
+
+    var versionValue = Individual(enforceKey, version);
+
+    if (versionValue !== version) {
+        throw new Error('Can only have one copy of ' +
+            moduleName + '.\n' +
+            'You already have version ' + versionValue +
+            ' installed.\n' +
+            'This means you cannot install version ' + version);
+    }
+
+    return Individual(key, defaultValue);
+}
+
+},{"./index.js":9}],11:[function(require,module,exports){
+(function (global){
+var topLevel = typeof global !== 'undefined' ? global :
+    typeof window !== 'undefined' ? window : {}
+var minDoc = require('min-document');
+
+if (typeof document !== 'undefined') {
+    module.exports = document;
+} else {
+    var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+
+    if (!doccy) {
+        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
+    }
+
+    module.exports = doccy;
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"min-document":18}],12:[function(require,module,exports){
+(function (global){
+var root = typeof window !== 'undefined' ?
+    window : typeof global !== 'undefined' ?
+    global : {};
+
+module.exports = Individual
+
+function Individual(key, value) {
+    if (root[key]) {
+        return root[key]
+    }
+
+    Object.defineProperty(root, key, {
+        value: value
+        , configurable: true
+    })
+
+    return value
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],13:[function(require,module,exports){
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
+
+},{}],14:[function(require,module,exports){
+var hiddenStore = require('./hidden-store.js');
+
+module.exports = createStore;
+
+function createStore() {
+    var key = {};
+
+    return function (obj) {
+        if ((typeof obj !== 'object' || obj === null) &&
+            typeof obj !== 'function'
+        ) {
+            throw new Error('Weakmap-shim: Key must be object')
+        }
+
+        var store = obj.valueOf(key);
+        return store && store.identity === key ?
+            store : hiddenStore(obj, key);
+    };
+}
+
+},{"./hidden-store.js":15}],15:[function(require,module,exports){
+module.exports = hiddenStore;
+
+function hiddenStore(obj, key) {
+    var store = { identity: key };
+    var valueOf = obj.valueOf;
+
+    Object.defineProperty(obj, "valueOf", {
+        value: function (value) {
+            return value !== key ?
+                valueOf.apply(this, arguments) : store;
+        },
+        writable: true
+    });
+
+    return store;
+}
+
+},{}],16:[function(require,module,exports){
+var inherits = require("inherits")
+
+var ALL_PROPS = [
+    "altKey", "bubbles", "cancelable", "ctrlKey",
+    "eventPhase", "metaKey", "relatedTarget", "shiftKey",
+    "target", "timeStamp", "type", "view", "which"
+]
+var KEY_PROPS = ["char", "charCode", "key", "keyCode"]
+var MOUSE_PROPS = [
+    "button", "buttons", "clientX", "clientY", "layerX",
+    "layerY", "offsetX", "offsetY", "pageX", "pageY",
+    "screenX", "screenY", "toElement"
+]
+
+var rkeyEvent = /^key|input/
+var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/
+
+module.exports = ProxyEvent
+
+function ProxyEvent(ev) {
+    if (!(this instanceof ProxyEvent)) {
+        return new ProxyEvent(ev)
+    }
+
+    if (rkeyEvent.test(ev.type)) {
+        return new KeyEvent(ev)
+    } else if (rmouseEvent.test(ev.type)) {
+        return new MouseEvent(ev)
+    }
+
+    for (var i = 0; i < ALL_PROPS.length; i++) {
+        var propKey = ALL_PROPS[i]
+        this[propKey] = ev[propKey]
+    }
+
+    this._rawEvent = ev
+    this._bubbles = false;
+}
+
+ProxyEvent.prototype.preventDefault = function () {
+    this._rawEvent.preventDefault()
+}
+
+ProxyEvent.prototype.startPropagation = function () {
+    this._bubbles = true;
+}
+
+function MouseEvent(ev) {
+    for (var i = 0; i < ALL_PROPS.length; i++) {
+        var propKey = ALL_PROPS[i]
+        this[propKey] = ev[propKey]
+    }
+
+    for (var j = 0; j < MOUSE_PROPS.length; j++) {
+        var mousePropKey = MOUSE_PROPS[j]
+        this[mousePropKey] = ev[mousePropKey]
+    }
+
+    this._rawEvent = ev
+}
+
+inherits(MouseEvent, ProxyEvent)
+
+function KeyEvent(ev) {
+    for (var i = 0; i < ALL_PROPS.length; i++) {
+        var propKey = ALL_PROPS[i]
+        this[propKey] = ev[propKey]
+    }
+
+    for (var j = 0; j < KEY_PROPS.length; j++) {
+        var keyPropKey = KEY_PROPS[j]
+        this[keyPropKey] = ev[keyPropKey]
+    }
+
+    this._rawEvent = ev
+}
+
+inherits(KeyEvent, ProxyEvent)
+
+},{"inherits":13}],17:[function(require,module,exports){
+var EvStore = require("ev-store")
+
+module.exports = removeEvent
+
+function removeEvent(target, type, handler) {
+    var events = EvStore(target)
+    var event = events[type]
+
+    if (!event) {
+        return
+    } else if (Array.isArray(event)) {
+        var index = event.indexOf(handler)
+        if (index !== -1) {
+            event.splice(index, 1)
+        }
+    } else if (event === handler) {
+        events[type] = null
+    }
+}
+
+},{"ev-store":8}],18:[function(require,module,exports){
+
+},{}],19:[function(require,module,exports){
+var h = require("./virtual-hyperscript/index.js")
+
+module.exports = h
+
+},{"./virtual-hyperscript/index.js":34}],20:[function(require,module,exports){
+/*!
+ * Cross-Browser Split 1.1.1
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
+ * Available under the MIT License
+ * ECMAScript compliant, uniform cross-browser split method
+ */
+
+/**
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
+ * separator are not included in the result array. However, if `separator` is a regex that contains
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
+ * cross-browser.
+ * @param {String} str String to split.
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
+ * @param {Number} [limit] Maximum number of items to include in the result array.
+ * @returns {Array} Array of substrings.
+ * @example
+ *
+ * // Basic use
+ * split('a b c d', ' ');
+ * // -> ['a', 'b', 'c', 'd']
+ *
+ * // With limit
+ * split('a b c d', ' ', 2);
+ * // -> ['a', 'b']
+ *
+ * // Backreferences in result array
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
+ */
+module.exports = (function split(undef) {
+
+  var nativeSplit = String.prototype.split,
+    compliantExecNpcg = /()??/.exec("")[1] === undef,
+    // NPCG: nonparticipating capturing group
+    self;
+
+  self = function(str, separator, limit) {
+    // If `separator` is not a regex, use `nativeSplit`
+    if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
+      return nativeSplit.call(str, separator, limit);
+    }
+    var output = [],
+      flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
+      (separator.sticky ? "y" : ""),
+      // Firefox 3+
+      lastLastIndex = 0,
+      // Make `global` and avoid `lastIndex` issues by working with a copy
+      separator = new RegExp(separator.source, flags + "g"),
+      separator2, match, lastIndex, lastLength;
+    str += ""; // Type-convert
+    if (!compliantExecNpcg) {
+      // Doesn't need flags gy, but they don't hurt
+      separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
+    }
+    /* Values for `limit`, per the spec:
+     * If undefined: 4294967295 // Math.pow(2, 32) - 1
+     * If 0, Infinity, or NaN: 0
+     * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
+     * If negative number: 4294967296 - Math.floor(Math.abs(limit))
+     * If other: Type-convert, then use the above rules
+     */
+    limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
+    limit >>> 0; // ToUint32(limit)
+    while (match = separator.exec(str)) {
+      // `separator.lastIndex` is not reliable cross-browser
+      lastIndex = match.index + match[0].length;
+      if (lastIndex > lastLastIndex) {
+        output.push(str.slice(lastLastIndex, match.index));
+        // Fix browsers whose `exec` methods don't consistently return `undefined` for
+        // nonparticipating capturing groups
+        if (!compliantExecNpcg && match.length > 1) {
+          match[0].replace(separator2, function() {
+            for (var i = 1; i < arguments.length - 2; i++) {
+              if (arguments[i] === undef) {
+                match[i] = undef;
+              }
+            }
+          });
+        }
+        if (match.length > 1 && match.index < str.length) {
+          Array.prototype.push.apply(output, match.slice(1));
+        }
+        lastLength = match[0].length;
+        lastLastIndex = lastIndex;
+        if (output.length >= limit) {
+          break;
+        }
+      }
+      if (separator.lastIndex === match.index) {
+        separator.lastIndex++; // Avoid an infinite loop
+      }
+    }
+    if (lastLastIndex === str.length) {
+      if (lastLength || !separator.test("")) {
+        output.push("");
+      }
+    } else {
+      output.push(str.slice(lastLastIndex));
+    }
+    return output.length > limit ? output.slice(0, limit) : output;
+  };
+
+  return self;
+})();
+
+},{}],21:[function(require,module,exports){
+module.exports=require(8)
+},{"individual/one-version":23}],22:[function(require,module,exports){
+module.exports=require(9)
+},{}],23:[function(require,module,exports){
+module.exports=require(10)
+},{"./index.js":22}],24:[function(require,module,exports){
+module.exports=require(11)
+},{"min-document":18}],25:[function(require,module,exports){
+"use strict";
+
+module.exports = function isObject(x) {
+	return typeof x === "object" && x !== null;
+};
+
+},{}],26:[function(require,module,exports){
+var isObject = require("is-object")
+var isHook = require("../vnode/is-vhook.js")
+
+module.exports = applyProperties
+
+function applyProperties(node, props, previous) {
+    for (var propName in props) {
+        var propValue = props[propName]
+
+        if (propValue === undefined) {
+            removeProperty(node, propName, propValue, previous);
+        } else if (isHook(propValue)) {
+            removeProperty(node, propName, propValue, previous)
+            if (propValue.hook) {
+                propValue.hook(node,
+                    propName,
+                    previous ? previous[propName] : undefined)
+            }
+        } else {
+            if (isObject(propValue)) {
+                patchObject(node, props, previous, propName, propValue);
+            } else {
+                node[propName] = propValue
+            }
+        }
+    }
+}
+
+function removeProperty(node, propName, propValue, previous) {
+    if (previous) {
+        var previousValue = previous[propName]
+
+        if (!isHook(previousValue)) {
+            if (propName === "attributes") {
+                for (var attrName in previousValue) {
+                    node.removeAttribute(attrName)
+                }
+            } else if (propName === "style") {
+                for (var i in previousValue) {
+                    node.style[i] = ""
+                }
+            } else if (typeof previousValue === "string") {
+                node[propName] = ""
+            } else {
+                node[propName] = null
+            }
+        } else if (previousValue.unhook) {
+            previousValue.unhook(node, propName, propValue)
+        }
+    }
+}
+
+function patchObject(node, props, previous, propName, propValue) {
+    var previousValue = previous ? previous[propName] : undefined
+
+    // Set attributes
+    if (propName === "attributes") {
+        for (var attrName in propValue) {
+            var attrValue = propValue[attrName]
+
+            if (attrValue === undefined) {
+                node.removeAttribute(attrName)
+            } else {
+                node.setAttribute(attrName, attrValue)
+            }
+        }
+
+        return
+    }
+
+    if(previousValue && isObject(previousValue) &&
+        getPrototype(previousValue) !== getPrototype(propValue)) {
+        node[propName] = propValue
+        return
+    }
+
+    if (!isObject(node[propName])) {
+        node[propName] = {}
+    }
+
+    var replacer = propName === "style" ? "" : undefined
+
+    for (var k in propValue) {
+        var value = propValue[k]
+        node[propName][k] = (value === undefined) ? replacer : value
+    }
+}
+
+function getPrototype(value) {
+    if (Object.getPrototypeOf) {
+        return Object.getPrototypeOf(value)
+    } else if (value.__proto__) {
+        return value.__proto__
+    } else if (value.constructor) {
+        return value.constructor.prototype
+    }
+}
+
+},{"../vnode/is-vhook.js":38,"is-object":25}],27:[function(require,module,exports){
+var document = require("global/document")
+
+var applyProperties = require("./apply-properties")
+
+var isVNode = require("../vnode/is-vnode.js")
+var isVText = require("../vnode/is-vtext.js")
+var isWidget = require("../vnode/is-widget.js")
+var handleThunk = require("../vnode/handle-thunk.js")
+
+module.exports = createElement
+
+function createElement(vnode, opts) {
+    var doc = opts ? opts.document || document : document
+    var warn = opts ? opts.warn : null
+
+    vnode = handleThunk(vnode).a
+
+    if (isWidget(vnode)) {
+        return vnode.init()
+    } else if (isVText(vnode)) {
+        return doc.createTextNode(vnode.text)
+    } else if (!isVNode(vnode)) {
+        if (warn) {
+            warn("Item is not a valid virtual dom node", vnode)
+        }
+        return null
+    }
+
+    var node = (vnode.namespace === null) ?
+        doc.createElement(vnode.tagName) :
+        doc.createElementNS(vnode.namespace, vnode.tagName)
+
+    var props = vnode.properties
+    applyProperties(node, props)
+
+    var children = vnode.children
+
+    for (var i = 0; i < children.length; i++) {
+        var childNode = createElement(children[i], opts)
+        if (childNode) {
+            node.appendChild(childNode)
+        }
+    }
+
+    return node
+}
+
+},{"../vnode/handle-thunk.js":36,"../vnode/is-vnode.js":39,"../vnode/is-vtext.js":40,"../vnode/is-widget.js":41,"./apply-properties":26,"global/document":24}],28:[function(require,module,exports){
+// Maps a virtual DOM tree onto a real DOM tree in an efficient manner.
+// We don't want to read all of the DOM nodes in the tree so we use
+// the in-order tree indexing to eliminate recursion down certain branches.
+// We only recurse into a DOM node if we know that it contains a child of
+// interest.
+
+var noChild = {}
+
+module.exports = domIndex
+
+function domIndex(rootNode, tree, indices, nodes) {
+    if (!indices || indices.length === 0) {
+        return {}
+    } else {
+        indices.sort(ascending)
+        return recurse(rootNode, tree, indices, nodes, 0)
+    }
+}
+
+function recurse(rootNode, tree, indices, nodes, rootIndex) {
+    nodes = nodes || {}
+
+
+    if (rootNode) {
+        if (indexInRange(indices, rootIndex, rootIndex)) {
+            nodes[rootIndex] = rootNode
+        }
+
+        var vChildren = tree.children
+
+        if (vChildren) {
+
+            var childNodes = rootNode.childNodes
+
+            for (var i = 0; i < tree.children.length; i++) {
+                rootIndex += 1
+
+                var vChild = vChildren[i] || noChild
+                var nextIndex = rootIndex + (vChild.count || 0)
+
+                // skip recursion down the tree if there are no nodes down here
+                if (indexInRange(indices, rootIndex, nextIndex)) {
+                    recurse(childNodes[i], vChild, indices, nodes, rootIndex)
+                }
+
+                rootIndex = nextIndex
+            }
+        }
+    }
+
+    return nodes
+}
+
+// Binary search for an index in the interval [left, right]
+function indexInRange(indices, left, right) {
+    if (indices.length === 0) {
+        return false
+    }
+
+    var minIndex = 0
+    var maxIndex = indices.length - 1
+    var currentIndex
+    var currentItem
+
+    while (minIndex <= maxIndex) {
+        currentIndex = ((maxIndex + minIndex) / 2) >> 0
+        currentItem = indices[currentIndex]
+
+        if (minIndex === maxIndex) {
+            return currentItem >= left && currentItem <= right
+        } else if (currentItem < left) {
+            minIndex = currentIndex + 1
+        } else  if (currentItem > right) {
+            maxIndex = currentIndex - 1
+        } else {
+            return true
+        }
+    }
+
+    return false;
+}
+
+function ascending(a, b) {
+    return a > b ? 1 : -1
+}
+
+},{}],29:[function(require,module,exports){
+var applyProperties = require("./apply-properties")
+
+var isWidget = require("../vnode/is-widget.js")
+var VPatch = require("../vnode/vpatch.js")
+
+var updateWidget = require("./update-widget")
+
+module.exports = applyPatch
+
+function applyPatch(vpatch, domNode, renderOptions) {
+    var type = vpatch.type
+    var vNode = vpatch.vNode
+    var patch = vpatch.patch
+
+    switch (type) {
+        case VPatch.REMOVE:
+            return removeNode(domNode, vNode)
+        case VPatch.INSERT:
+            return insertNode(domNode, patch, renderOptions)
+        case VPatch.VTEXT:
+            return stringPatch(domNode, vNode, patch, renderOptions)
+        case VPatch.WIDGET:
+            return widgetPatch(domNode, vNode, patch, renderOptions)
+        case VPatch.VNODE:
+            return vNodePatch(domNode, vNode, patch, renderOptions)
+        case VPatch.ORDER:
+            reorderChildren(domNode, patch)
+            return domNode
+        case VPatch.PROPS:
+            applyProperties(domNode, patch, vNode.properties)
+            return domNode
+        case VPatch.THUNK:
+            return replaceRoot(domNode,
+                renderOptions.patch(domNode, patch, renderOptions))
+        default:
+            return domNode
+    }
+}
+
+function removeNode(domNode, vNode) {
+    var parentNode = domNode.parentNode
+
+    if (parentNode) {
+        parentNode.removeChild(domNode)
+    }
+
+    destroyWidget(domNode, vNode);
+
+    return null
+}
+
+function insertNode(parentNode, vNode, renderOptions) {
+    var newNode = renderOptions.render(vNode, renderOptions)
+
+    if (parentNode) {
+        parentNode.appendChild(newNode)
+    }
+
+    return parentNode
+}
+
+function stringPatch(domNode, leftVNode, vText, renderOptions) {
+    var newNode
+
+    if (domNode.nodeType === 3) {
+        domNode.replaceData(0, domNode.length, vText.text)
+        newNode = domNode
+    } else {
+        var parentNode = domNode.parentNode
+        newNode = renderOptions.render(vText, renderOptions)
+
+        if (parentNode && newNode !== domNode) {
+            parentNode.replaceChild(newNode, domNode)
+        }
+    }
+
+    return newNode
+}
+
+function widgetPatch(domNode, leftVNode, widget, renderOptions) {
+    var updating = updateWidget(leftVNode, widget)
+    var newNode
+
+    if (updating) {
+        newNode = widget.update(leftVNode, domNode) || domNode
+    } else {
+        newNode = renderOptions.render(widget, renderOptions)
+    }
+
+    var parentNode = domNode.parentNode
+
+    if (parentNode && newNode !== domNode) {
+        parentNode.replaceChild(newNode, domNode)
+    }
+
+    if (!updating) {
+        destroyWidget(domNode, leftVNode)
+    }
+
+    return newNode
+}
+
+function vNodePatch(domNode, leftVNode, vNode, renderOptions) {
+    var parentNode = domNode.parentNode
+    var newNode = renderOptions.render(vNode, renderOptions)
+
+    if (parentNode && newNode !== domNode) {
+        parentNode.replaceChild(newNode, domNode)
+    }
+
+    return newNode
+}
+
+function destroyWidget(domNode, w) {
+    if (typeof w.destroy === "function" && isWidget(w)) {
+        w.destroy(domNode)
+    }
+}
+
+function reorderChildren(domNode, moves) {
+    var childNodes = domNode.childNodes
+    var keyMap = {}
+    var node
+    var remove
+    var insert
+
+    for (var i = 0; i < moves.removes.length; i++) {
+        remove = moves.removes[i]
+        node = childNodes[remove.from]
+        if (remove.key) {
+            keyMap[remove.key] = node
+        }
+        domNode.removeChild(node)
+    }
+
+    var length = childNodes.length
+    for (var j = 0; j < moves.inserts.length; j++) {
+        insert = moves.inserts[j]
+        node = keyMap[insert.key]
+        // this is the weirdest bug i've ever seen in webkit
+        domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to])
+    }
+}
+
+function replaceRoot(oldRoot, newRoot) {
+    if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) {
+        oldRoot.parentNode.replaceChild(newRoot, oldRoot)
+    }
+
+    return newRoot;
+}
+
+},{"../vnode/is-widget.js":41,"../vnode/vpatch.js":44,"./apply-properties":26,"./update-widget":31}],30:[function(require,module,exports){
+var document = require("global/document")
+var isArray = require("x-is-array")
+
+var render = require("./create-element")
+var domIndex = require("./dom-index")
+var patchOp = require("./patch-op")
+module.exports = patch
+
+function patch(rootNode, patches, renderOptions) {
+    renderOptions = renderOptions || {}
+    renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
+        ? renderOptions.patch
+        : patchRecursive
+    renderOptions.render = renderOptions.render || render
+
+    return renderOptions.patch(rootNode, patches, renderOptions)
+}
+
+function patchRecursive(rootNode, patches, renderOptions) {
+    var indices = patchIndices(patches)
+
+    if (indices.length === 0) {
+        return rootNode
+    }
+
+    var index = domIndex(rootNode, patches.a, indices)
+    var ownerDocument = rootNode.ownerDocument
+
+    if (!renderOptions.document && ownerDocument !== document) {
+        renderOptions.document = ownerDocument
+    }
+
+    for (var i = 0; i < indices.length; i++) {
+        var nodeIndex = indices[i]
+        rootNode = applyPatch(rootNode,
+            index[nodeIndex],
+            patches[nodeIndex],
+            renderOptions)
+    }
+
+    return rootNode
+}
+
+function applyPatch(rootNode, domNode, patchList, renderOptions) {
+    if (!domNode) {
+        return rootNode
+    }
+
+    var newNode
+
+    if (isArray(patchList)) {
+        for (var i = 0; i < patchList.length; i++) {
+            newNode = patchOp(patchList[i], domNode, renderOptions)
+
+            if (domNode === rootNode) {
+                rootNode = newNode
+            }
+        }
+    } else {
+        newNode = patchOp(patchList, domNode, renderOptions)
+
+        if (domNode === rootNode) {
+            rootNode = newNode
+        }
+    }
+
+    return rootNode
+}
+
+function patchIndices(patches) {
+    var indices = []
+
+    for (var key in patches) {
+        if (key !== "a") {
+            indices.push(Number(key))
+        }
+    }
+
+    return indices
+}
+
+},{"./create-element":27,"./dom-index":28,"./patch-op":29,"global/document":24,"x-is-array":47}],31:[function(require,module,exports){
+var isWidget = require("../vnode/is-widget.js")
+
+module.exports = updateWidget
+
+function updateWidget(a, b) {
+    if (isWidget(a) && isWidget(b)) {
+        if ("name" in a && "name" in b) {
+            return a.id === b.id
+        } else {
+            return a.init === b.init
+        }
+    }
+
+    return false
+}
+
+},{"../vnode/is-widget.js":41}],32:[function(require,module,exports){
+'use strict';
+
+var EvStore = require('ev-store');
+
+module.exports = EvHook;
+
+function EvHook(value) {
+    if (!(this instanceof EvHook)) {
+        return new EvHook(value);
+    }
+
+    this.value = value;
+}
+
+EvHook.prototype.hook = function (node, propertyName) {
+    var es = EvStore(node);
+    var propName = propertyName.substr(3);
+
+    es[propName] = this.value;
+};
+
+EvHook.prototype.unhook = function(node, propertyName) {
+    var es = EvStore(node);
+    var propName = propertyName.substr(3);
+
+    es[propName] = undefined;
+};
+
+},{"ev-store":21}],33:[function(require,module,exports){
+'use strict';
+
+module.exports = SoftSetHook;
+
+function SoftSetHook(value) {
+    if (!(this instanceof SoftSetHook)) {
+        return new SoftSetHook(value);
+    }
+
+    this.value = value;
+}
+
+SoftSetHook.prototype.hook = function (node, propertyName) {
+    if (node[propertyName] !== this.value) {
+        node[propertyName] = this.value;
+    }
+};
+
+},{}],34:[function(require,module,exports){
+'use strict';
+
+var isArray = require('x-is-array');
+
+var VNode = require('../vnode/vnode.js');
+var VText = require('../vnode/vtext.js');
+var isVNode = require('../vnode/is-vnode');
+var isVText = require('../vnode/is-vtext');
+var isWidget = require('../vnode/is-widget');
+var isHook = require('../vnode/is-vhook');
+var isVThunk = require('../vnode/is-thunk');
+
+var parseTag = require('./parse-tag.js');
+var softSetHook = require('./hooks/soft-set-hook.js');
+var evHook = require('./hooks/ev-hook.js');
+
+module.exports = h;
+
+function h(tagName, properties, children) {
+    var childNodes = [];
+    var tag, props, key, namespace;
+
+    if (!children && isChildren(properties)) {
+        children = properties;
+        props = {};
+    }
+
+    props = props || properties || {};
+    tag = parseTag(tagName, props);
+
+    // support keys
+    if (props.hasOwnProperty('key')) {
+        key = props.key;
+        props.key = undefined;
+    }
+
+    // support namespace
+    if (props.hasOwnProperty('namespace')) {
+        namespace = props.namespace;
+        props.namespace = undefined;
+    }
+
+    // fix cursor bug
+    if (tag === 'INPUT' &&
+        !namespace &&
+        props.hasOwnProperty('value') &&
+        props.value !== undefined &&
+        !isHook(props.value)
+    ) {
+        props.value = softSetHook(props.value);
+    }
+
+    transformProperties(props);
+
+    if (children !== undefined && children !== null) {
+        addChild(children, childNodes, tag, props);
+    }
+
+
+    return new VNode(tag, props, childNodes, key, namespace);
+}
+
+function addChild(c, childNodes, tag, props) {
+    if (typeof c === 'string') {
+        childNodes.push(new VText(c));
+    } else if (typeof c === 'number') {
+        childNodes.push(new VText(String(c)));
+    } else if (isChild(c)) {
+        childNodes.push(c);
+    } else if (isArray(c)) {
+        for (var i = 0; i < c.length; i++) {
+            addChild(c[i], childNodes, tag, props);
+        }
+    } else if (c === null || c === undefined) {
+        return;
+    } else {
+        throw UnexpectedVirtualElement({
+            foreignObject: c,
+            parentVnode: {
+                tagName: tag,
+                properties: props
+            }
+        });
+    }
+}
+
+function transformProperties(props) {
+    for (var propName in props) {
+        if (props.hasOwnProperty(propName)) {
+            var value = props[propName];
+
+            if (isHook(value)) {
+                continue;
+            }
+
+            if (propName.substr(0, 3) === 'ev-') {
+                // add ev-foo support
+                props[propName] = evHook(value);
+            }
+        }
+    }
+}
+
+function isChild(x) {
+    return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x);
+}
+
+function isChildren(x) {
+    return typeof x === 'string' || isArray(x) || isChild(x);
+}
+
+function UnexpectedVirtualElement(data) {
+    var err = new Error();
+
+    err.type = 'virtual-hyperscript.unexpected.virtual-element';
+    err.message = 'Unexpected virtual child passed to h().\n' +
+        'Expected a VNode / Vthunk / VWidget / string but:\n' +
+        'got:\n' +
+        errorString(data.foreignObject) +
+        '.\n' +
+        'The parent vnode is:\n' +
+        errorString(data.parentVnode)
+        '\n' +
+        'Suggested fix: change your `h(..., [ ... ])` callsite.';
+    err.foreignObject = data.foreignObject;
+    err.parentVnode = data.parentVnode;
+
+    return err;
+}
+
+function errorString(obj) {
+    try {
+        return JSON.stringify(obj, null, '    ');
+    } catch (e) {
+        return String(obj);
+    }
+}
+
+},{"../vnode/is-thunk":37,"../vnode/is-vhook":38,"../vnode/is-vnode":39,"../vnode/is-vtext":40,"../vnode/is-widget":41,"../vnode/vnode.js":43,"../vnode/vtext.js":45,"./hooks/ev-hook.js":32,"./hooks/soft-set-hook.js":33,"./parse-tag.js":35,"x-is-array":47}],35:[function(require,module,exports){
+'use strict';
+
+var split = require('browser-split');
+
+var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
+var notClassId = /^\.|#/;
+
+module.exports = parseTag;
+
+function parseTag(tag, props) {
+    if (!tag) {
+        return 'DIV';
+    }
+
+    var noId = !(props.hasOwnProperty('id'));
+
+    var tagParts = split(tag, classIdSplit);
+    var tagName = null;
+
+    if (notClassId.test(tagParts[1])) {
+        tagName = 'DIV';
+    }
+
+    var classes, part, type, i;
+
+    for (i = 0; i < tagParts.length; i++) {
+        part = tagParts[i];
+
+        if (!part) {
+            continue;
+        }
+
+        type = part.charAt(0);
+
+        if (!tagName) {
+            tagName = part;
+        } else if (type === '.') {
+            classes = classes || [];
+            classes.push(part.substring(1, part.length));
+        } else if (type === '#' && noId) {
+            props.id = part.substring(1, part.length);
+        }
+    }
+
+    if (classes) {
+        if (props.className) {
+            classes.push(props.className);
+        }
+
+        props.className = classes.join(' ');
+    }
+
+    return props.namespace ? tagName : tagName.toUpperCase();
+}
+
+},{"browser-split":20}],36:[function(require,module,exports){
+var isVNode = require("./is-vnode")
+var isVText = require("./is-vtext")
+var isWidget = require("./is-widget")
+var isThunk = require("./is-thunk")
+
+module.exports = handleThunk
+
+function handleThunk(a, b) {
+    var renderedA = a
+    var renderedB = b
+
+    if (isThunk(b)) {
+        renderedB = renderThunk(b, a)
+    }
+
+    if (isThunk(a)) {
+        renderedA = renderThunk(a, null)
+    }
+
+    return {
+        a: renderedA,
+        b: renderedB
+    }
+}
+
+function renderThunk(thunk, previous) {
+    var renderedThunk = thunk.vnode
+
+    if (!renderedThunk) {
+        renderedThunk = thunk.vnode = thunk.render(previous)
+    }
+
+    if (!(isVNode(renderedThunk) ||
+            isVText(renderedThunk) ||
+            isWidget(renderedThunk))) {
+        throw new Error("thunk did not return a valid node");
+    }
+
+    return renderedThunk
+}
+
+},{"./is-thunk":37,"./is-vnode":39,"./is-vtext":40,"./is-widget":41}],37:[function(require,module,exports){
+module.exports = isThunk
+
+function isThunk(t) {
+    return t && t.type === "Thunk"
+}
+
+},{}],38:[function(require,module,exports){
+module.exports = isHook
+
+function isHook(hook) {
+    return hook &&
+      (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
+       typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
+}
+
+},{}],39:[function(require,module,exports){
+var version = require("./version")
+
+module.exports = isVirtualNode
+
+function isVirtualNode(x) {
+    return x && x.type === "VirtualNode" && x.version === version
+}
+
+},{"./version":42}],40:[function(require,module,exports){
+var version = require("./version")
+
+module.exports = isVirtualText
+
+function isVirtualText(x) {
+    return x && x.type === "VirtualText" && x.version === version
+}
+
+},{"./version":42}],41:[function(require,module,exports){
+module.exports = isWidget
+
+function isWidget(w) {
+    return w && w.type === "Widget"
+}
+
+},{}],42:[function(require,module,exports){
+module.exports = "2"
+
+},{}],43:[function(require,module,exports){
+var version = require("./version")
+var isVNode = require("./is-vnode")
+var isWidget = require("./is-widget")
+var isThunk = require("./is-thunk")
+var isVHook = require("./is-vhook")
+
+module.exports = VirtualNode
+
+var noProperties = {}
+var noChildren = []
+
+function VirtualNode(tagName, properties, children, key, namespace) {
+    this.tagName = tagName
+    this.properties = properties || noProperties
+    this.children = children || noChildren
+    this.key = key != null ? String(key) : undefined
+    this.namespace = (typeof namespace === "string") ? namespace : null
+
+    var count = (children && children.length) || 0
+    var descendants = 0
+    var hasWidgets = false
+    var hasThunks = false
+    var descendantHooks = false
+    var hooks
+
+    for (var propName in properties) {
+        if (properties.hasOwnProperty(propName)) {
+            var property = properties[propName]
+            if (isVHook(property) && property.unhook) {
+                if (!hooks) {
+                    hooks = {}
+                }
+
+                hooks[propName] = property
+            }
+        }
+    }
+
+    for (var i = 0; i < count; i++) {
+        var child = children[i]
+        if (isVNode(child)) {
+            descendants += child.count || 0
+
+            if (!hasWidgets && child.hasWidgets) {
+                hasWidgets = true
+            }
+
+            if (!hasThunks && child.hasThunks) {
+                hasThunks = true
+            }
+
+            if (!descendantHooks && (child.hooks || child.descendantHooks)) {
+                descendantHooks = true
+            }
+        } else if (!hasWidgets && isWidget(child)) {
+            if (typeof child.destroy === "function") {
+                hasWidgets = true
+            }
+        } else if (!hasThunks && isThunk(child)) {
+            hasThunks = true;
+        }
+    }
+
+    this.count = count + descendants
+    this.hasWidgets = hasWidgets
+    this.hasThunks = hasThunks
+    this.hooks = hooks
+    this.descendantHooks = descendantHooks
+}
+
+VirtualNode.prototype.version = version
+VirtualNode.prototype.type = "VirtualNode"
+
+},{"./is-thunk":37,"./is-vhook":38,"./is-vnode":39,"./is-widget":41,"./version":42}],44:[function(require,module,exports){
+var version = require("./version")
+
+VirtualPatch.NONE = 0
+VirtualPatch.VTEXT = 1
+VirtualPatch.VNODE = 2
+VirtualPatch.WIDGET = 3
+VirtualPatch.PROPS = 4
+VirtualPatch.ORDER = 5
+VirtualPatch.INSERT = 6
+VirtualPatch.REMOVE = 7
+VirtualPatch.THUNK = 8
+
+module.exports = VirtualPatch
+
+function VirtualPatch(type, vNode, patch) {
+    this.type = Number(type)
+    this.vNode = vNode
+    this.patch = patch
+}
+
+VirtualPatch.prototype.version = version
+VirtualPatch.prototype.type = "VirtualPatch"
+
+},{"./version":42}],45:[function(require,module,exports){
+var version = require("./version")
+
+module.exports = VirtualText
+
+function VirtualText(text) {
+    this.text = String(text)
+}
+
+VirtualText.prototype.version = version
+VirtualText.prototype.type = "VirtualText"
+
+},{"./version":42}],46:[function(require,module,exports){
+var isObject = require("is-object")
+var isHook = require("../vnode/is-vhook")
+
+module.exports = diffProps
+
+function diffProps(a, b) {
+    var diff
+
+    for (var aKey in a) {
+        if (!(aKey in b)) {
+            diff = diff || {}
+            diff[aKey] = undefined
+        }
+
+        var aValue = a[aKey]
+        var bValue = b[aKey]
+
+        if (aValue === bValue) {
+            continue
+        } else if (isObject(aValue) && isObject(bValue)) {
+            if (getPrototype(bValue) !== getPrototype(aValue)) {
+                diff = diff || {}
+                diff[aKey] = bValue
+            } else if (isHook(bValue)) {
+                 diff = diff || {}
+                 diff[aKey] = bValue
+            } else {
+                var objectDiff = diffProps(aValue, bValue)
+                if (objectDiff) {
+                    diff = diff || {}
+                    diff[aKey] = objectDiff
+                }
+            }
+        } else {
+            diff = diff || {}
+            diff[aKey] = bValue
+        }
+    }
+
+    for (var bKey in b) {
+        if (!(bKey in a)) {
+            diff = diff || {}
+            diff[bKey] = b[bKey]
+        }
+    }
+
+    return diff
+}
+
+function getPrototype(value) {
+  if (Object.getPrototypeOf) {
+    return Object.getPrototypeOf(value)
+  } else if (value.__proto__) {
+    return value.__proto__
+  } else if (value.constructor) {
+    return value.constructor.prototype
+  }
+}
+
+},{"../vnode/is-vhook":38,"is-object":25}],47:[function(require,module,exports){
+var nativeIsArray = Array.isArray
+var toString = Object.prototype.toString
+
+module.exports = nativeIsArray || isArray
+
+function isArray(obj) {
+    return toString.call(obj) === "[object Array]"
+}
+
+},{}]},{},[3]);
diff --git a/virtual-dom/lib.require.js b/virtual-dom/lib.require.js
new file mode 100644
--- /dev/null
+++ b/virtual-dom/lib.require.js
@@ -0,0 +1,457 @@
+/*
+  to generate lib.js, install virtual-dom and process file:
+
+     $ npm install
+     $ grunt
+   the ./diff module is vtree/diff with a few changes to
+   allow diff to run in an asynchronous thread in the presence of
+   memoized nodes.
+ */
+
+/*
+   Note on memory management:
+
+   To ensure accurate heap tracing for finalization and profiling purposes,
+   GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some
+   Haskell values inside JavaScript references and uses extensible retention
+   to collect these values. It's crucial that all data structures that may
+   contain Haskell values are stored directly in a JSVal and not inside other
+   JS data structures.
+
+   The recognized types are:
+
+     - HSPatch:
+         The patch object contains the new (target) virtual-dom tree and
+         the original tree is reachable trough the parent. Since all handlers,
+         components and thunks are reachable through these trees, the patch itself
+         does not need to be traversed.
+
+     - HSMount:
+         All mounted HSMount points are scanned as roots. The current virtual tree
+         (mount.vtree) is traversed for this.
+
+     - HSComponent:
+         Traversed when reachable through Haskell heap or virtual-dom tree. Contains
+         current tree (component.vtree) and rendering action (component.hsRender)
+
+     - HSThunk:
+         Traversed when reachable through Haskell heap or virtual-dom tree. Contains
+         Haskell suspension (thunk.hst) or rendered tree (thunk.vnode)
+
+     - virtual node ( isVirtualNode(x) )
+         Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*'
+         properties, which virtual-dom adds to the node's hooks (vnode.hooks). If
+         a virtual node contains thunks, widgets or any of its descendants have hooks,
+         the children of the node have to be traversed.
+
+   forceThunks and forcePatch fill an array of thunks, which is not directly
+   scannable; however, these functions are only used as part of a `diff` operation.
+   The initial diff creates an HSPatch object, through which the original and target
+   virtual-dom tree are completely reachable.
+ */
+
+var isVirtualNode = require('virtual-dom/vnode/is-vnode');
+var isThunk       = require('virtual-dom/vnode/is-thunk');
+var isWidget      = require("virtual-dom/vnode/is-widget");
+var h             = require('virtual-dom/h');
+var isArray       = require('x-is-array');
+var VPatch        = require("virtual-dom/vnode/vpatch");
+var VText         = require('virtual-dom/vnode/vtext');
+var vdomPatch     = require('virtual-dom/vdom/patch');
+var DomDelegator  = require('dom-delegator');
+
+var diff = require('./diff');
+
+var VRenderableN = 0;
+
+/** @constructor */
+function HSPatch(patch, old, vnode, parent) {
+  this.patch   = patch;
+  this.old     = old;
+  this.vnode   = vnode;
+  this.parent  = parent;
+}
+
+/** @constructor */
+function HSThunk(t, ids, key) {
+  this.hst        = t;   // haskell thunk
+  this.ids        = ids; // array of haskell unique ids
+  this.key        = key;
+  this.vnode      = null;
+  this._ghcjsMark = 0;
+}
+
+HSThunk.prototype.type = 'Thunk';
+
+/* 
+  render returns the deferred rendering object
+  null if the thunk has already been rendered, in which case the value is in this.vnode
+ */
+HSThunk.prototype.render = function(previous) {
+  if(previous && !this.vnode && eqIds(this.ids, previous.ids)) {
+    if(previous.hst) {
+      this.hst = previous.hst;
+    } else {
+      this.hst   = null;
+      this.vnode = previous.vnode;
+    }
+  }
+  return this.vnode ? null : this;
+}
+
+/** @constructor */
+function HSComponent(r, mnt, unmnt, key) {
+  this._key      = ++VRenderableN;
+  this.hsRender  = r;   // IO action that produces a vdom tree
+  this.hsMount   = mnt;
+  this.hsUnmount = unmnt;
+  this.key       = key || this._key;
+  this.vnode     = this.initialVnode = new VText("");
+  this.pending   = [];
+  this.mounts    = {};
+  this.version   = 0;
+  this.latest    = 0;
+}
+
+HSComponent.prototype.type = 'Widget';
+
+HSComponent.prototype.init = function() {
+  var n = document.createTextNode('');
+  if(this.vnode !== this.initialVnode) {
+    var thunks = [];
+    var p = diff(this.initialVnode, this.vnode, thunks);
+    if(thunks.length !== 0) {
+      throw new Error("HSComponent vnode contains unevaluated thunks");
+    }
+    n = vdomPatch(n, p);
+  }
+  var m = new HSComponentMount(n);
+  n._key = m._key;
+  n._widget = this;
+  this.mounts[m._key] = m;
+  if(this.hsMount) {
+    h$vdomMountComponentCallback(this.hsMount, m._key, this);
+  }
+  return n;
+}
+
+HSComponent.prototype.destroy = function(domNode) {
+  delete this.mounts[domNode._key];
+  if(this.hsUnmount) {
+    h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode);
+  }
+}
+
+HSComponent.prototype.diff = function(v, thunks) {
+  var vn = this.vnode;
+  if(this.pending.length > 0) {
+    vn = this.pending[this.pending.length-1].vnode;
+  }
+  return new HSPatch( diff(vn, v, thunks)
+		    , vn
+		    , v
+		    , this);
+}
+
+HSComponent.prototype.addPatch = function(p) {
+  var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]
+                                    : this.vnode;
+  if(p.old === cur) this.pending.push(p);
+}
+
+HSComponent.prototype.patch = function(p) {
+  if(this.pending.length > 0) {
+    var pnd = this.pending;
+    this.pending = [];
+    for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);
+  }
+  if(!p) return;
+  if(p.parent !== this || p.old !== this.vnode) {
+    return false;
+  }
+  for(var k in this.mounts) {
+    var m = this.mounts[k];
+    m.node = vdomPatch(m.node, p.patch);
+  }
+  this.vnode = p.vnode;
+  return true;
+}
+
+// only use this for manually updated components (i.e. no diff/patch)
+HSComponent.prototype.updateMount = function(mnt, node) {
+  var m = this.mounts[mnt];
+  node._key = mnt;
+  node._widget = this;
+  m.node.parentNode.replaceChild(node, m.node);
+  m.node = node;
+}
+
+HSComponent.prototype.update = function(leftVNode, node) {
+  if(node._widget) {
+    if(node._widget == this) return node;
+    node._widget.destroy(node);
+  }
+  return this.init();
+}
+
+var HSComponentMountN = 0;
+function HSComponentMount(domNode) {
+  this._key = ++HSComponentMountN;
+  this.node = domNode;
+}
+
+/** @constructor */
+function HSMount(domNode) {
+  this._key       = ++VRenderableN;
+  // this.version    = 0;
+  this.vnode      = new VText(""); // currently rendered vdom tree
+  this.pending    = [];            // pending patches, not yet applied
+  this.node       = document.createTextNode("");
+  this.parentNode = domNode;
+}
+
+HSMount.prototype.diff = function(v, thunks) {
+  var vn = this.vnode;
+  if(this.pending.length > 0) {
+    vn = this.pending[this.pending.length-1].vnode;
+  }
+  return new HSPatch( diff(vn, v, thunks)
+		    , vn
+		    , v
+		    , this);
+}
+
+HSMount.prototype.addPatch = function(p) {
+  var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]
+                                    : this.vnode;
+  if(p.old === cur) this.pending.push(p);
+}
+
+// HSMount.patch(null) to flush pending list
+HSMount.prototype.patch = function(p) {
+  if(this.pending.length > 0) {
+    var pnd = this.pending;
+    this.pending = [];
+    for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);
+  }
+  if(!p) return;
+  if(p.parent !== this || p.old !== this.vnode) {
+    return false;
+  }
+  this.node  = vdomPatch(this.node, p.patch);
+  this.vnode = p.vnode;
+  return true;
+}
+  
+/* mount a vdom tree, making it visible to extensible retention */
+function mount(domNode) {
+  while(domNode.firstChild) domNode.removeChild(domNode.firstChild);
+  var m = new HSMount(domNode);
+  domNode.appendChild(m.node);
+  vdomMounts.add(m);
+  return m;
+}
+
+/* unmount a tree, removing all child nodes. */
+function unmount(vmount) {
+  var n = vmount.parentNode;
+  while(n.firstChild) n.removeChild(n.firstChild);
+  vdomMounts.remove(vmount);
+}
+
+/*
+   Compare lists of object identifiers associated with a thunk node. If the lists are equal,
+   the subtree does not have to be recomputed.
+*/
+function eqIds(ids1, ids2) {
+  if(!ids1 || !ids2 || ids1.length != ids2.length) return false;
+  for(var i=ids1.length-1;i>=0;i--) {
+    var id1 = ids1[i], id2 = ids2[i];
+    if(typeof id1 === 'number') {
+      if(typeof id2 !== 'number') return false;
+      if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false;
+    } else {
+      if(id1 !== id2) return false;
+    }
+  }
+  return true;
+}
+
+function forcePatch(p) {
+  var thunks = [], i, j, pi;
+  for(i in p) {
+    var pi = p[i];
+    if(isArray(pi)) {
+      for(j=pi.length-1;j>=0;j--) {
+	forceTree(pi[j].patch, thunks);
+      }
+    }
+    else if(pi.patch) forceTree(pi.patch, thunks);
+    else forceTree(pi, thunks);
+  }
+  return thunks;
+}
+
+function forceTree(n, t) {
+  if(isThunk(n)) {
+    if(n.vnode) forceTree(n.vnode, t);
+    else t.push(n);
+  } else if(isVirtualNode(n) && n.hasThunks) {
+    for(var i=n.children.length-1;i>=0;i--) {
+      forceTree(n.children[i], t);
+    }
+  }
+}
+
+/*
+   scan all mounted virtual-dom trees
+ */
+function scanMounts(currentMark) {
+  var i = vdomMounts.iter(), m, res = [];
+  while((m = i.next()) !== null) {
+    scanTreeRec(m.vnode, res, currentMark);
+    if(m.pending.length > 0) {
+      scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark);
+    }
+  }
+  return res;
+}
+
+/*
+   scan a tree (extensible retention callback).
+
+   returns:
+     - an array of haskell items if any
+     - true if no haskell items have been found
+     - false if the object is not a ghcjs-vdom tree
+         (fallthrough to other extensible retention scanners)
+ */
+var scanTreeRes = [];
+function scanTree(o, currentMark) {
+  if(isVirtualNode(o) || isThunk(o) || isWidget(o) ||
+     o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) {
+    var r = scanTreeRes;
+    scanTreeRec(o, r, currentMark);
+    if(r.length > 0) {
+      scanTreeRes = [];
+      return r;
+    } else {
+      return true;
+    }
+  } else { // not a ghcjs-vdom object, fall through
+    return false; 
+  }
+}
+
+function scanTreeRec(o, r, currentMark) {
+  if(o instanceof HSPatch) {
+    scanTreeRec(o.vnode, r, currentMark);
+    scanTreeRec(o.parent);
+  } else if(o instanceof HSThunk) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      if(o.hst) r.push(o.hst);
+      if(o.vnode) scanTreeRec(o.vnode, r, currentMark);
+    }
+  } else if(o instanceof HSComponent) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      if(o.hsRender) r.push(o.hsRender);
+      if(o.hsMount) r.push(o.hsMount);
+      if(o.hsUnmount) r.push(o.hsUnmount);
+      if(o.vnode) scanTreeRec(o.vnode, r, currentMark);
+      if(o.pending.length > 0) {
+	scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark);
+      }
+    }
+  } else if(isVirtualNode(o)) {
+    if(o._ghcjsMark !== currentMark) {
+      o._ghcjsMark = currentMark;
+      // collect event handlers
+      var hooks = o.hooks;
+      for(var p in hooks) {
+	if(p.indexOf('ev-') === 0) {
+	  var handler = hooks[p];
+	  if(handler.value && handler.value.hsAction) {
+	    r.push(handler.value.hsAction);
+	  }
+	}
+      }
+      // recurse if any of the children may have thunks, components or handlers
+      if(o.hasWidgets || o.hasThunks || o.descendantHooks) {
+	for(var i=o.children.length-1;i>=0;i--) {
+          scanTreeRec(o.children[i], r, currentMark);
+	}
+      }
+    }
+  }
+}
+
+function setThunkPatch(n, p) {
+  if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p);
+}
+
+function hasPatches(patch) {
+  for (var index in patch) {
+    if (index !== "a") {
+      return true;
+    }
+  }
+  return false;
+}
+
+function initDelegator(evTypes) {
+  var d = DomDelegator();
+  var l = evTypes.length;
+  for(var i = 0; i < l; i++) {
+    d.listenTo(evTypes[i]);
+  }
+}
+
+function v(tag, props, children) {
+  return h(tag, props, children);
+}
+
+function t(text) {
+  return new VText(text);
+}
+
+function th(t, ids, key) {
+  return new HSThunk(t, ids, key);
+}
+
+function c(r, m, u, key) {
+  return new HSComponent(r, m, u, key);
+}
+
+function makeHandler(action, async) {
+  var f = function(ev) {
+    return h$vdomEventCallback(async, action, ev);
+  }
+  f.hsAction = action;
+  return f;
+}
+
+var vdomMounts = new h$Set();
+
+module.exports = { setThunkPatch: setThunkPatch
+                 , forceTree:     forceTree
+                 , forcePatch:    forcePatch
+                 , diff:          diff
+                 , mount:         mount
+                 , unmount:       unmount
+                 , initDelegator: initDelegator
+                 , v:             v
+                 , th:            th
+                 , t:             t
+                 , c:             c
+                 , makeHandler:   makeHandler
+                 };
+
+// the global variable we're using in the bindings
+h$vdom = module.exports;
+
+h$registerExtensibleRetention(scanTree);
+h$registerExtensibleRetentionRoot(scanMounts);
+
+
diff --git a/virtual-dom/package.json b/virtual-dom/package.json
new file mode 100644
--- /dev/null
+++ b/virtual-dom/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "ghcjs-vdom-support",
+  "version": "0.0.0",
+  "description": "Support lib for ghcjs-vdom",
+  "devDependencies": {
+    "grunt": "^0.4.5",
+    "grunt-browserify": "^2.1.4",
+    "grunt-contrib-watch": "^0.6.1"
+  },
+  "dependencies": {
+    "is-object": "^0.1.2",
+    "virtual-dom": "^2.0.1",
+    "x-is-array": "^0.1.0",
+    "dom-delegator": "^13.1.0"
+  }
+}
