             [chessground.data :as data]
             [chessground.api :as api]
             [chessground.show :as show]
-            [chessground.ctrl :as ctrl])
-  (:require-macros [cljs.core.async.macros :as am]))
+            [chessground.ctrl :as ctrl]
+            [chessground.schemas :refer [AnyMap]]
+            [schema.core :as s]
+            [cljs.core.async.impl.channels :refer [ManyToManyChannel]])
+  (:require-macros [cljs.core.async.macros :as am]
+                   [schema.macros :as sm :refer [defschema]]))
 
 (extend-type js/NodeList ISeqable (-seq [array] (array-seq array 0)))
 
-(defn load-app
+(defschema AppMap
+  {:element   js/HTMLDivElement
+   :state     Atom
+   :channels  {s/Keyword ManyToManyChannel}
+   :consumers {s/Keyword js/Function}})
+
+(sm/defn load-app :- AppMap
   "Return a map containing the initial application"
-  [element config]
+  [element :- js/HTMLDivElement
+   config :- js/Object]
   {:element element
    :state (atom (data/make config))
    :channels {:toggle-orientation (a/chan)

              :set-pieces (a/chan)
              :drop-off (a/chan)
              :api-move (a/chan)
              :show-moved (a/chan)
              }
   :consumers {:toggle-orientation ctrl/toggle-orientation
               :set-orientation ctrl/set-orientation
               :set-fen ctrl/set-fen

               :set-pieces ctrl/set-pieces
               :drop-off ctrl/drop-off
               :api-move ctrl/api-move
               :show-moved ctrl/show-moved}}) 

(sm/defn init-updates
  "For every entry in a map of channel identifiers to consumers,
   initiate a channel listener which will update the application state
   using the appropriate function whenever a value is received"
  [app :- AppMap]
  (doseq [[ch update-fn] (:consumers app)]
    (am/go (while true
             (let [val (a/<! (get (:channels app) ch))
