       version : STORAGE_VERSION
       fetch   : no
 
-    # make sure we are not accessing storage until its ready.
-    # this also updates kb bindings which are initially listening
-    # for default combos.
     @_store.once 'ready', _.bind @_handleStoreReady, this
 
-    # add valid kb event listeners and invalidate obsolete upon
-    # front application is set and ready
+    # Add valid keyboard event listeners and invalidate obsolete upon
+    # front application is set and ready.
     appManager.on 'FrontAppChange', _.bind @_handleFrontAppChange, this
 
     @_store.fetchStorage null, yes
 
 
-  _save: _.throttle (collection, model) ->
+  # Persists changes to app storage.
+  #
+  # This is a throttled method and will only be called per every
+  # _THROTTLE_WAIT_ milliseconds.
+  #
+  _save: _.throttle ->
 
     throw 'could not persist changes'  unless @_store.isReady
-    # also means: do not call this method explicitly, use #update
-
-    throw 'not implemented :trollface:'
+
+    for key, models of @_buffer
+
+      set = []
+
+      while (model = @_buffer[key].pop())?
+        value =
+        _
+          .chain(model.toJSON())
+          # since every platform has independent storages, we are only
+          # saving this platform's bindings
+          .extend binding: klass._getPlatformBinding model
+          .pick 'binding', 'options', 'name' # and nothing more
+          .tap (obj) -> # to validate
+            if _.isObject obj.options
+              # the only whitelisted options prop is _enabled_
+              obj.options = _.pick obj.options, 'enabled'
+              delete obj.options  \
+                if (_.isEmpty obj.options) or (not _.isBoolean obj.options.enabled)
+            if (not _.isArray obj.binding) or _.isEmpty obj.binding
+              delete obj.binding
+          .value()
+
+        set.push value  unless _.isEmpty value
+
+      unless _.isEmpty set
+        @_store.setValue key, set,
+          _
+            .chain (collectionName, modelNames) ->
+              _.each modelNames, (modelName) =>
+                collection = @shortcuts.get collectionName
+                @emit 'change', collection, collection.find name: modelName
+            .partial key, _.pluck set, 'name'
+            .bind @
+            .value()
 
   , THROTTLE_WAIT,
     leading  : no
     trailing : yes
 
 
+  # Buffers up changed models.
+  #
+  # This is necessary for not to exhaust given resources, since
+  # we are listening changes in _keyconfig#Model_ level.
+  #
+  # That is to say, each _keyconfig#Model_ change dispatches this.
+  #
+  _handleShortcutsChange: (collection, model) ->
+
+    queue = @_buffer[collection.name]
+    idx   = _.findWhere queue, name: model.name
+
+    queue[((~idx and idx) or queue.length)] = model
+
+    @_save()
+
+
+  # Restores overrides when storage is ready, and starts listening for changes.
+  #
   _handleStoreReady: ->
-
-    osIndex = if globals.os is 'mac' then 1 else 0
 
     @shortcuts.config.each (collection) =>
       collectionName = collection.name

          model = @shortcuts.get collectionName, obj.name
          return  unless model

          silent = if model.options?.custom then yes else no

          # -------------------------------
          # about custom shortcuts (eg ace)
          # -------------------------------
          #
          # i don't really want to embed such logic into _shortcuts_; it should be
          # totally 'shortcut type' agnostic and api provides the necessity to deal
          # with such cases indeed.
          #
          # passing 'silent' as the 4th argument to shortcuts#update makes sure its
          # internal keyconfig#change listener won't get dispatched anyhow, thus
          # rendering the model in question unbound. plus, there is the _options_
          # object you can use to denote such weirdos.
          #
          # just make sure you don't put any 'custom shortcut' set names
          # into a bant.json file, since these sets are automatically thought to
          # be _shortcuts_ compatible.

          binding = null

          if _.isArray obj.binding
            binding = cloneArray model.binding
            binding[osIndex] = [].concat(obj.binding).filter _.isString

          @emit 'change', collection,
            shortcuts.update collectionName, obj.name,
              binding: binding
              options: options
            , silent

    # persist to app storage whenever a shortcut is changed.
    # this should handled with some care to avoid exhausting
    # resources, since _shortcuts_ emits all changes.
    @shortcuts.on 'change', _.bind @_save, this


  _handleFrontAppChange: (app, prevApp) ->
    # invalidates previous app's keyboard events and adds new
    # listeners for the current one

    appId     = app.canonicalName
    prevAppId = prevApp?.canonicalName

    if opts.shortcuts instanceof Shortcuts
      @shortcuts = opts.shortcuts
    else
      @shortcuts = new Shortcuts _.keys(defaults).reduce (acc, key) ->
          acc[key] = defaults[key].data
          return acc
        , {}

    @_buffer = @shortcuts.config.reduce (acc, collection) ->
      acc[collection.name] = []
      return acc
    , {}


  # Party starts here.
  #
  # This adds the necessary event listeners, and fetches changes.
  # Make sure to call this after _appStorageController_ & _appManager_
  # singletons are set.
  #
  addEventListeners: ->

    { appManager, appStorageController } = kd.singletons
