           things (local-get-item prefix form-type)]
       (dispatch [:response/query {:results things} 200 tap]))))
 
-(comment
-
-  (local-get-item "bones" "todos")
-  (str "bones" (name "todos"))
-  (str "bones" "-" (name :todos))
-
-
-
-
-  )
+
+;; forms
+(defn detect-controls [{:keys [enter escape]}]
+  (fn [keypress]
+    (case (.-which keypress)
+      13 (enter)
+      ;; chrome won't fire 27, so use on-blur instead
+      27 (escape)
+      nil)))
+
+(defn field [form-type identifier attr html-attrs]
+  (let [path [:editable form-type identifier :inputs attr]
+        value (subscribe path)
+        input-type (or (:input-type html-attrs) :input)
+        value-attr (or (:value-attr html-attrs) :value)
+        opts (dissoc html-attrs :value-attr :input-type)]
+    (fn []
+      [input-type (merge {:on-change #(dispatch-sync (conj path (-> % .-target .-value)))
+                          value-attr @value}
+                         opts)])))
+
+(defn checkbox [form-type identifier attr & {:as html-attrs}]
+  (field form-type identifier attr (merge {:type "checkbox"
+                                           :value-attr :checked}
+                                          html-attrs)))
+
+(defn input [form-type identifier attr & {:as html-attrs}]
+  (field form-type identifier attr html-attrs))
+
+(defn calculate-command [form-type action]
+  (keyword (name form-type) (name action)))
+
+(defn save-fn
+  ([[form-type id]]
+   ;; call it, save all the inputs. this way it can be used without parens, which
+   ;; is kind of neat
+   ((apply save-fn [form-type id] {} {})))
+  ([[form-type id] args]
+   ;; in case you don't want to pass opts
+   (save-fn [form-type id] args {}))
+  ([[form-type id] args opts]
+   ;; opts can be options like :solo "don't merge values from :inputs"
+   (fn []
+     (let [new-args (if (fn? args) (args) args)
+           action (if (= :new id) :new :update)
+           ;; args are attributes/inputs of the thing
+           more-args (merge (if (= :new id) nil {:id id}) new-args)
+           ;; opts are logistical things like how to update the form from the
+           ;; response
+           ;; we can't send :id equal to :new because :id is probably an
+           ;; attribute that will be generated
+           more-opts (merge (if (= :new id) {:identifier :new} ) opts)
+           ;; convention for commands here e.g.: :todos/update
+           calculated-command (calculate-command form-type action)]
+       (dispatch [:request/command calculated-command more-args more-opts])))))
+
+(defn form
+  "returns function as closures around subscriptions to a single 'editable' thing in the
+  db. The thing has attributes, whose current value is accessed by calling `inputs' e.g., with arguments. No arguments will return all the attributes"
+  [form-type identifier]
+  (let [inputs-atom (subscribe [:editable form-type identifier :inputs])
+        state-atom (subscribe [:editable form-type identifier :state])
+        errors-atom (subscribe [:editable form-type identifier :errors])
+        defaults-atom (subscribe [:editable form-type identifier :defaults])
+        inputs (fn [& args] (get-in @inputs-atom args))
+        state (fn [& args] (get-in @state-atom args))
+        errors (fn [& args] (get-in @errors-atom args))
+        defaults (fn [& args] (get-in @defaults-atom args))]
+    {:inputs inputs
+     :state  state
+     :errors errors
+     :defaults defaults
+
+     :save (partial save-fn [form-type identifier])
+     :delete #(dispatch [:request/command (calculate-command form-type :delete) {:id identifier}])
+     :reset  #(dispatch (e/editable-reset :todos identifier (state :reset)))
+     :edit   #(dispatch [:editable
+                         [:editable :todos identifier :state :reset (inputs)]
+                         [:editable :todos identifier :state :editing true]])
+     }))
+
+
 ;; helpers
 (defn editable-reset [etype identifier inputs]
   [:editable

                         (:identifier opts)
                         ;; must be an :identifier like :new or just an id
                         partial-args)
          ;; form-type and identifier are needed by the response handler to
          ;; update the thing in the db
          more-tap (merge tap {:form-type form-type :identifier identifier})
          ;; this is the specification for the editable thing:
          {:keys [spec outgoing-spec]
           :or {outgoing-spec spec}

          ;; partial-args will needlessly overwrite the :id in :inputs, but oh well
          ;; partial-args should overwrite what is in the db, and update the db
          ;; in the response handler
          ;; don't merge inputs from the if :solo - only send the given
          ;; attributes in the event vector
          inputs-defaults (merge defaults (if solo {} inputs) (if (map? partial-args) partial-args))
          ]
      ;; it seems weird but a request with empty args is possible
      ;; both outgoing-spec and inputs can be nil
