       state =
         description: scratch.plunk.description
         tags: scratch.plunk.tags
-        files: []
-      
+        files: {}
+      ###
       for buffer in scratch.buffers.queue
-        state.files.push
+        buffer.channel ||= uid(10)
+        state.files[buffer.channel] =
+          channel_id: channel
+          filename: buffer.filename
           content: buffer.content
-          filename: buffer.filename
-    
+      ###
       state
       
     join: (id = uid()) ->
-      console.log "Stream::join", arguments...
       self = @
-      
-      scratch.loading = true
+      deferred = $q.defer()
       
       @getConnection().open "stream:#{id}", "json", (err, doc) ->
-        if err then return console.error "message", "Connection error", """
-          Failed to join the stream #{id}. Please double-check that you entered
-          the right stream id. If the problem persists, please contact the
-          administrator.
-        """
+        if err then return deferred.reject "Stream error: Unable to join stream"
         
-        if doc.created is true
+        stream.id = id
+        stream.doc = doc
+        stream.keep = doc.created is true
+        stream.streaming = true
+
+        if stream.keep
           # Reset the channel to the current local state
           doc.submitOp [ { p: [], od: doc.snapshot, oi: self.getLocalState() } ], (err) ->
-            if err then console.error "error", "Error setting initial state"
-            else self.start(id, doc, true)
-            self.scope.$apply -> scratch.loading = false
-        else $timeout ->
-          self.start(id, doc)
-          scratch.loading = false
-    
-    start: (id, @doc, keep = false) ->
-      console.log "Stream::start", arguments...
+            self.scope.$apply ->
+              if err
+                doc.close()
+                deferred.reject("Stream error: Error setting initial state")
+              else
+                deferred.resolve(stream)
+        else
+          self.scope.$apply -> synchro.handleRemoteEvent ->
+            deferred.resolve(stream)
+      
+      deferred.promise
+    
+    start: (stream) ->
       self = @
-      
-      unless keep
-        state = @doc.get()
-        
-        console.log "State is", state
-
-        # We don't want to reset the *whole* scratch here as that would result
-        # in changing the active plunk to a blank one. We will just adjust the
-        # plunk's description and tags and reset the buffers.
-        scratch.plunk.description = state.description
-        scratch.plunk.tags = state.tags
-        
-        scratch.buffers.reset(state.files)
-        scratch.buffers.activate(index) if index = scratch.buffers.findBy("filename", "index.html")
-        
-      self.scope.$apply ->
-        stream.streaming = true
-        stream.doc = self.doc
-        stream.keep = keep
-        
-        # Assign the scratch to the local scope which will trigger creation of
-        # custom directives for each channel
-        self.scope.scratch = scratch
-      
-      @doc.at("files").on "insert", (pos, data) ->
-        console.log "sharejs::insert", arguments...
-        $timeout -> scratch.buffers.add(data)
-      
-      @doc.at("files").on "delete", (pos, data) ->
-        console.log "sharejs::delete", arguments...
-        $timeout -> scratch.buffers.remove(buffer) if buffer = scratch.buffers.findBy("filename", data.filename)
-        
-      @doc.on "change", (events) ->
-        console.log "sharejs::change", arguments...
-        ###
-        angular.forEach events, (e) ->
-          if e.p.length then switch e.p[0]
-            when "description" then self.remote.trigger "description:change", e.oi or ""
-            when "channels"
-              # TODO: This first comparison is VERY inefficient
-              if e.p.length == 1 and not _.isEqual(self.getLocalState().channels, e.oi) then self.remote.trigger "channels:reset", e.oi
-              else if e.p.length == 2
-                if e.od? then self.remote.trigger "channels:remove", e.od
-                else if e.oi? then self.remote.trigger "channels:add", e.oi
-              else if e.p.length == 3
-                self.remote.trigger "channels:rename", e.oi, e.od, e.p[2]
-          else
-            self.remote.trigger "description:change", e.oi.description
-            self.remote.trigger "channels:reset", e.oi.channels, keep: false
-        ###
-          
+      self.doc = stream.doc
+      
+      resetScratch(scratch, stream.doc.get()) unless stream.keep
+
+      # Assign the scratch to the local scope which will trigger creation of
+      # custom directives for each channel
+      self.scope.scratch = scratch
+      
+      files = @doc.at("files")
+      
+      files.on "insert", (pos, data) ->
+        synchro.handleRemoteEvent ->
+          unless scratch.buffers.findBy("channel", pos)
+            self.scope.$apply -> scratch.buffers.add angular.extend(data, channel: pos)
+      
+      files.on "delete", (pos, data) ->
+        synchro.handleRemoteEvent ->
+          self.scope.$apply -> scratch.buffers.remove(buffer) if buffer = scratch.buffers.findBy("channel", pos)
+      
     link: ($scope, el, attrs) ->
       self = @
       self.scope = $scope
       
+      synchro.scope = $scope
+      
       $scope.stream = stream
       
       $scope.startStream = (id) ->
-        self.join(id)
+        scratch.loading = true
+        self.join(id).then (id, doc, keep) ->
+          self.start(id, doc, keep)
+          scratch.loading = false
+        , (error) ->
+          alert(error)
+          scratch.loading = false
         
       
     deactivate: ($scope, el, attrs) ->

# with the document's contents unless keepEditorContents is true. (In which case the document's
# contents are nuked and replaced with the editor's).
window.sharejs.extendDoc 'attach_ace', (session, keepEditorContents) ->
  console.log "attach_ace, this", session

  doc = this
  editorDoc = session.getDocument()
  editorDoc.setNewLineMode 'unix'


# Convert an ace delta into an op understood by share.js
applyToShareJS = (editorDoc, delta, doc) ->
  # Get the start position of the range, in no. of characters
  getStartOffsetPosition = (range) ->
    # This is quite inefficient - getLines makes a copy of the entire
