pending...

pending...

--- components/artwork_filter/test/view.coffee
+++ components/artwork_filter/test/view.coffee
@@ -182,7 +182,7 @@
     beforeEach ->
       Backbone.sync.args[0][2].success fabricate 'artist_filtered_search_suggest'

-    it 'fetches the artworks, toggling the boolean filter criteria', ->
+    xit 'fetches the artworks, toggling the boolean filter criteria', ->
       console.log 'toogle boolean', @view.$el.html()
       @view.$('input[type="checkbox"]').first().click()
       console.log '@view.filter.selected.attributes', @view.filter.selected.attributes


[atom] hide linter panel by default

[atom] hide linter panel by default

--- .atom/config.cson
+++ .atom/config.cson
@@ -12,8 +12,7 @@
     userId: "424d8951-d72d-4084-ad4e-00c911987c60"
   "file-icons":
     coloured: false
-  "linter-ui-default":
-    showPanel: true
+  "linter-ui-default": {}
   "tree-view":
     hideIgnoredNames: true
     squashDirectoryNames: true


fix scope selection for JSON snippets

fix scope selection for JSON snippets

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -268,11 +268,14 @@
     unless atom.config.get('atomizr.warnAboutMissingScope') is false
       atom.notifications.addWarning("Atomizr", detail: "Could not determine scope automatically, using placeholder", dismissable: false)

-      # Select placeholder scope
-      rangeStart = 1
+      if atom.config.get('atomizr.atomDefaultSyntax') is "CSON"
+        rangeStart = 1
+      else
+        rangeStart = 3
       rangeEnd = ".source".length + rangeStart
       range = [[1, rangeStart], [1, rangeEnd]]

+      # Select placeholder scope
       editor.selectMarker(editor.markBufferRange(range, {invalidate: 'never'}))

   vsCodeToSubl: ->


change dismissable state

change dismissable state

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -65,7 +65,7 @@
     try
       obj = parseCson.parse(text)
     catch e
-      @atom.notifications.addError("Atomizr", detail: e, dismissable: false)
+      @atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

     # Conversion
@@ -110,7 +110,7 @@
     try
       obj = parseJson(text)
     catch e
-      @atom.notifications.addError("Atomizr", detail: e, dismissable: false)
+      @atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

     # Minimum requirements
@@ -212,7 +212,7 @@
     try
       input = parseCson.parse(text)
     catch e
-      @atom.notifications.addError("Atomizr", detail: e, dismissable: false)
+      @atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

     output = CSON.createJSONString(input)
@@ -232,7 +232,7 @@
     try
       input = parseJson(text)
     catch e
-      @atom.notifications.addError("Atomizr", detail: e, dismissable: false)
+      @atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

     output = CSON.createCSONString(input)


fix small typo

fix small typo

--- lib/compile.coffee
+++ lib/compile.coffee
@@ -317,7 +317,7 @@
       .replace(/\scomponent="([\s\S]*?)"/g, (match, componentName) ->
         helpers.logVerbose 'match 12'
         updated = true
-        ctrlName = _.str.classify componentName
+        ctrlName = _str.classify componentName
         componentName = componentName.replace '{{', "'+"
         componentName = componentName.replace '}}', "+'"
         templateName = "modules/components/#{componentName}/#{componentName}.tpl.html"


added comment

added comment

--- src/processors/module/css.coffee
+++ src/processors/module/css.coffee
@@ -51,6 +51,7 @@
 	# @param {String} id
 	# @return {String}
 	wrapModuleContents: (content, id) ->
+		# No op
 		return content

 	# Recursively inline dependency file content


smiley support

smiley support

--- client.coffee
+++ client.coffee
@@ -258,6 +258,7 @@
 		send = !->
 			log 'send', inputE.value(), inputE
 			if msg = inputE.value()
+				msg = Form.smileyToEmoji msg
 				Server.sync 'msg', msg, !->
 					id = shared.modify "maxId", (v) -> (v||0)+1
 					shared.set 0|id/100, id%100,


Another fix to the ES aliases - forgot to add the alias to the brain! doh

--- src/scripts/elasticsearch.coffee
+++ src/scripts/elasticsearch.coffee
@@ -69,8 +69,6 @@
     cluster_health msg, msg.match[1], (text) ->
       msg.send text

-
-
   robot.respond /(elasticsearch|es) show aliases/i, (msg) ->
     if msg.message.user.id is robot.name
       return
@@ -106,5 +104,5 @@

   setAlias = (msg, alias, url) ->
     _esAliases[alias] = url
-    robot.brain.data._esAliases = _esAliases
+    robot.brain.data.elasticsearch_aliases = _esAliases
     msg.send("The alias #{alias} for #{url} has been added to the brain")


:bug: Allow stringish errors

:bug: Allow stringish errors

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -5,7 +5,7 @@

 Helpers = module.exports =
   error: (e) ->
-    atom.notifications.addError(e.message, {detail: e.stack, dismissable: true})
+    atom.notifications.addError(e.toString(), {detail: e.stack or '', dismissable: true})
   shouldTriggerLinter: (linter, bufferModifying, onChange, scopes) ->
     # Trigger lint-on-Fly linters on both events but on-save linters only on save
     # Because we want to trigger onFly linters on save when the


Expand list of reserved keywords

Expand list of reserved keywords

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -339,7 +339,24 @@
 ###

 exports.reservedWords = [
-  'class'
+  # taken from COFFEE_KEYWORDS (lexer.coffee)
+  # (also, don't check for 'undefined' because it's already explicitly
+  # accounted for elsewhere)
+  'then'
+  'unless'
+  'until'
+  'loop'
+  'of'
+  'by'
+  'when'
+  # taken from COFFEE_ALIAS_MAP (lexer.coffee)
+  'and'
+  'or'
+  'is'
+  'isnt'
+  'not'
+  'yes'
+  'no'
   'on'
   'off'
 ]


fix #2072, maybe

fix #2072, maybe

--- src/smc-webapp/smc-dropzone.cjsx
+++ src/smc-webapp/smc-dropzone.cjsx
@@ -156,7 +156,9 @@
             @_destroy()
         else
             @_create_dropzone()
-            @dropzone.options = $.extend(true, {}, @dropzone.options, @get_djs_config())
+            if @dropzone?
+                # see https://github.com/sagemathinc/cocalc/issues/2072
+                @dropzone.options = $.extend(true, {}, @dropzone.options, @get_djs_config())

     preview_template: ->
         <div className='dz-preview dz-file-preview'>


Increase spec timeout

Increase spec timeout

--- src/spec/integration.spec.coffee
+++ src/spec/integration.spec.coffee
@@ -292,3 +292,4 @@

     .fail (error) -> done _.prettify error
     .done()
+  , 30000 # 30sec


Added transposition and alternates to tutorial

--- public/scripts/TutorialModal.cjsx
+++ public/scripts/TutorialModal.cjsx
@@ -87,6 +87,11 @@
         Use the left and right arrow keys to navigate between chords or click a chord to jump to it.
         Markato makes it easy to play through your song the way you would on a piano or a guitar.

+
Transposition

+

+        Click the blue arrows next to your song title to transpose it up or down or click the key itself to select a new key.
+        Note: transposing will not change the actual key your song is written in, just the key it is outputted in.
+

Comments

         Comments let you add information about your song that will not be displayed in the output.  Comment lines begin with ##.
@@ -110,6 +115,23 @@
         ##ALBUM  A Hard Day's Night
         ##KEY    G

+
Alternates

+

+        Alternates allow you to provide multiple options for a particular chord that someone viewing your song can select among.
+        Alternates are written at the end of a song, after a line containing ###.
+        Specifying an alternate for a particular chord will apply to all instances of that chord throughout the song.
+        To single out particular instances of the chord to specify alternates for, denote those instances with a '.
+        If there are multiple conflicts on the same chord, a '' or even triple ''' can be used.
+        For example:
+

+

+        :G' G C G
+        ^ It's been a ^hard ^day's ^night
+
+        ###
+        G  => G7
+        G' => G7sus4, D7sus4, Dm11
+

   render: ->


Extend test case in order to explain return.

If the `return` is present this test case will end with:
```
Error: Error on retrieving access_token after 10 attempts.
Error: Error: DEPTH_ZERO_SELF_SIGNED_CERT
```

If the `return` is NOT present the code execution fails with:
```
TypeError: Cannot read property 'statusCode' of undefined
    at Request._callback (/Users/heichler/dev/3rd/sphere-node-connect/lib/rest.js:101:21)
```

You can simply test this by changing the default host name in line 15 of auth2.coffee to some stupid value.

--- src/spec/integration.spec.coffee
+++ src/spec/integration.spec.coffee
@@ -31,8 +31,11 @@

     it "should get access token", (done)->
       @oa.getAccessToken (error, response, body)->
-        data = JSON.parse(body)
-        expect(data.access_token).toBeDefined()
+        if error
+          expect(error).toBeUndefined()
+        else
+          data = JSON.parse(body)
+          expect(data.access_token).toBeDefined()
         done()

     it "should get products", (done)->


implementing content collection from `<mkts-table-content><cell>`

--- src/tex-writer-mkts-table.coffee
+++ src/tex-writer-mkts-table.coffee
@@ -31,6 +31,7 @@
 select                    = MD_READER.select.bind      MD_READER
 is_hidden                 = MD_READER.is_hidden.bind   MD_READER
 is_stamped                = MD_READER.is_stamped.bind  MD_READER
+jr                        = JSON.stringify
 #...........................................................................................................
 MKTS_TABLE                = require './mkts-table'
 MKTS.MACRO_ESCAPER.register_raw_tag 'mkts-table-description'
@@ -44,7 +45,7 @@
   return D.TEE.from_pipeline [
     @$parse_description               S
     @$render_description              S
-    @$dump_table_description          S
+    # @$dump_table_description          S
     ]

 #===========================================================================================================
@@ -78,11 +79,61 @@

 #-----------------------------------------------------------------------------------------------------------
 @$render_description = ( S ) ->
+  ### TAINT should allow to name tables in description and content tags ###
+  prv_description       = null
+  within_table_content  = false
+  within_cell           = false
+  cells                 = {}
+  current_cell          = null
+  #.........................................................................................................
   return $ ( event, send ) =>
-    return send event unless select event, '.', 'MKTS/TABLE/description'
-    [ type, name, description, meta, ] = event
-    send sub_event for sub_event from MKTS_TABLE._walk_events description
-    send stamp event
+    if select event, '.', 'MKTS/TABLE/description'
+      [ type, name, description, meta, ]  = event
+      prv_description                     = description
+      return send stamp event
+    #.......................................................................................................
+    if select event, '(', 'mkts-table-content'
+      within_table_content = true
+      return send stamp event
+    #.......................................................................................................
+    if select event, ')', 'mkts-table-content'
+      within_table_content = false
+      # send sub_event for sub_event from MKTS_TABLE._walk_events description
+      ### TAINT render table now ###
+      debug '66522', cells
+      return send stamp event
+    #.......................................................................................................
+    if within_table_content
+      #.....................................................................................................
+      if select event, '(', 'cell'
+        within_cell = true
+        [ type, name, Q, meta, ]  = event
+        if not Q? and Q.key?
+          throw new Error "need key for cell"
+        ### TAINT must validate key at some point; like this, content with an unknown key will just vanish ###
+        current_cell = cells[ Q.key ] = []
+        return send stamp event
+      #.....................................................................................................
+      if select event, ')', 'cell'
+        within_cell   = false
+        current_cell  = null
+        return send stamp event
+      #.....................................................................................................
+      if within_cell
+        current_cell.push event
+        urge '27762', jr event
+        return send stamp event
+      #.....................................................................................................
+      if ( select event, '.', 'text' ) and ( event[ 2 ].match /^\s*$/ )?
+        whisper '27762', jr event
+        return send stamp event
+      #.....................................................................................................
+      # throw new Error "detected illegal content: #{rpr event}"
+      warn '27762', jr event
+      return
+    #.......................................................................................................
+    send event
+    #.......................................................................................................
     return null

 #-----------------------------------------------------------------------------------------------------------


Deal with rows instead of points in LineTopIndex

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -1286,13 +1286,13 @@
   spliceBlockDecorationsInRange: (start, end, screenDelta) ->
     return if screenDelta is 0

-    oldExtent = Point(end - start, Infinity)
-    newExtent = Point(end - start + screenDelta, 0)
-    invalidatedBlockDecorationIds = @lineTopIndex.splice(Point(start, 0), oldExtent, newExtent)
+    oldExtent = end - start
+    newExtent = end - start + screenDelta
+    invalidatedBlockDecorationIds = @lineTopIndex.splice(start, oldExtent, newExtent)
     invalidatedBlockDecorationIds.forEach (id) =>
       decoration = @model.decorationForId(id)
       newScreenPosition = decoration.getMarker().getHeadScreenPosition()
-      @lineTopIndex.moveBlock(id, newScreenPosition)
+      @lineTopIndex.moveBlock(id, newScreenPosition.row)
       @invalidatedDimensionsByBlockDecoration.add(decoration)

   didAddBlockDecoration: (decoration) ->
@@ -1308,7 +1308,7 @@
       didDestroyDisposable.dispose()
       @didDestroyBlockDecoration(decoration)

-    @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition(), true, 0)
+    @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0)

     @observedBlockDecorations.add(decoration)
     @invalidateBlockDecorationDimensions(decoration)
@@ -1322,7 +1322,7 @@
     # change.
     return if markerEvent.textChanged

-    @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition())
+    @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
     @shouldUpdateDecorations = true
     @emitDidUpdateState()


Tagging feedback

Tagging feedback

--- getFeedback.coffee
+++ getFeedback.coffee
@@ -7,14 +7,92 @@
 ObjectId = mongoose.Types.ObjectId

 getFeedback = (obj, callback) ->
+  # Call type-specific method
   switch obj.hit.type
     when 'relevance judgments'
       return getRelevanceFeedback(obj, callback)
     when 'image tagging'
-      return callback("No image tagging feedback yet")
+      return getTaggingFeedback(obj, callback)
     else
       return callback("No feedback logic for type: #{obj.hit.type}", null)
   callback("No Feedback yet", null)
+
+# Get feedback for Image-Tagging
+# Relies on manual annotations saved to db.tagqualities
+getTaggingFeedback = (obj, callback) ->
+  TagQuality = require './models/tagquality'
+
+  async.parallel({
+    qJudgments: (cb) -> TagQuality.find().exec(cb)
+    # Get a list with user:user, tags:[{item,tag}] per row
+    byUser: (cb) -> TaskSet.aggregate(
+      {$match:{hit_id:{$in:obj.allHitType}}},
+      {$unwind: '$tasks'}, {$unwind:'$tasks.contribution.tags'},
+      {$project:{
+        item:'$tasks.item.id',
+        user:1,
+        tag: $toLower: '$tasks.contribution.tags'
+      }},
+      {$group:{_id:{user:'$user'}, tags:{$push:{item:'$item', tag:'$tag'}}}}
+    ).exec(cb)
+    # REMOVING FOR A HARDCODED VALUE
+    #singleCountTags: (cb) -> TaskSet.aggregate(
+    #  {$match:{hit_id:{$in:obj.allHitType}}},
+    #  {$unwind: '$tasks'},
+    #  {$unwind:'$tasks.contribution.tags'},
+    #  {$project:{
+    #    item:'$tasks.item.id',
+    #    tag:{$toLower:'$tasks.contribution.tags'}}},
+    #  {$group:{_id:{item:'$item', tag:'$tag'}, count:{$sum:1}}},
+    #  {$match:{count:1}},
+    #  {$group:{_id:null, tags:{$push:'$_id'}}}
+    #).exec(cb)
+  }, (err, results) ->
+    if (err)
+      return console.error err
+    # Make quality judgements into an associative array for quick lookup
+    qual = results.qJudgments.reduce(((map, doc)->
+      item = String(doc.item_id)
+      if not map[item]
+        map[item] = {}
+      map[item][doc.tag] = doc.quality
+      return map
+    ),{})
+
+    # REMOVING FOR A HARDCODED VALUE
+    # For unseen items, set a default score based on the average rating of
+    # items that have only been seen once
+    #singleItemJudgments = _.map(results.singleCountTags[0].tags, (tag) ->
+    #  return qual[tag.item][tag.tag]
+    #)
+    #unseenScore = _.sum(singleItemJudgments)/singleItemJudgments.length
+
+    # Hardcoded. The average for once tagged items is 2.5, so round down
+    unseenScore = 2
+
+    byWorker = _.map(results.byUser, (user) ->
+      scores =_.map(user.tags, (row) ->
+        quality = parseInt(qual[String(row.item)][row.tag])
+        if quality not in [0,1,2,3,4]
+          quality = unseenScore
+        quality
+      )
+      avgScore = _.sum(scores)/scores.length
+      {user:user._id.user, avgScore:avgScore}
+    )
+
+    allScores = _.map(byWorker, (w) -> w.avgScore).sort((a,b)-> a-b)
+    worker = _.find(byWorker, (w) -> w.user is obj.opts.user)
+    performanceFeedback =
+      numWorkers: allScores.length
+      min: _.min(allScores)
+      max: _.max(allScores)
+      worker:
+        performance: worker.avgScore
+        rank: 1+allScores.indexOf(worker.avgScore)
+
+    return callback(null, performanceFeedback)
+  )

 getRelevanceFeedback = (obj, callback) ->
   # This part of the aggregation pipeline will return unwound rows of
@@ -29,7 +107,7 @@
       item_id:'$tasks.item.id'
     }},
     {$unwind: '$judgment'}]
-
+
   # This will return all the judgments per result
   # i.e. {_id:QUERY,ITEM_ID, judgments;['Not', 'Somewhat','Not', ...]}
   byItemQuery = unwindJudgments.concat([
@@ -73,14 +151,14 @@
       {user:user, scores: scores, avgScore:avgScore}
     )
     allScores = _.map(byWorker, (w) -> w.avgScore).sort((a,b)-> a-b)
-    worker = _.find(byWorker, (w) -> w.user is "PETER")
+    worker = _.find(byWorker, (w) -> w.user is obj.opts.user)
     performanceFeedback =
       numWorkers: allScores.length
       min: _.min(allScores)
       max: _.max(allScores)
       worker:
         performance: worker.avgScore
-        rank: allScores.indexOf(worker.avgScore)
+        rank: 1+allScores.indexOf(worker.avgScore)

     callback(null, performanceFeedback)
   )


Add pattern-matching for manpage paragraph macros

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -967,6 +967,47 @@
 				2: {name: "entity.name.function.roff"}
 		},{

+			# .LP/.PP/.P: Paragraph
+			name: "meta.function.paragraph.man.macro.roff"
+			begin: "^([.'])[ \t]*(LP|PP?)\\b"
+			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+			patterns: [include: "#params"]
+		},{
+
+			# .IP: Indented paragraph
+			name: "meta.function.indented-paragraph.man.macro.roff"
+			begin: "^([.'])[ \t]*(IP)\\b"
+			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+			patterns: [include: "#param-group"]
+		},{
+
+			# .TP: Titled paragraph
+			name: "meta.function.titled-paragraph.man.macro.roff"
+			begin: "^([.'])[ \t]*(TP)\\b"
+			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+			patterns: [include: "#param-group"]
+		},{
+
+			# .HP: Hanging paragraph (deprecated)
+			name: "meta.deprecated.function.hanging-paragraph.man.macro.roff"
+			begin: "^([.'])[ \t]*((HP))\\b"
+			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+				3: {name: "invalid.deprecated.roff"}
+			patterns: [include: "#param-group"]
+		},{
+
 			# .B/.SB: Bold
 			contentName: "markup.bold.roff"
 			begin: "^([.'])[ \t]*(S?B)(\\s*\\\\\".*$)?(?=$|[ \t]+|\\\\)"


Tweak scopes of angle-bracket notation

Tweak scopes of angle-bracket notation

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -373,7 +373,7 @@
 		}]

 	supportType:
-		name: "support.type.viml"
+		name: "entity.tag.name.viml"
 		match: "(<).*?(>)"
 		captures:
 			1: name: "punctuation.definition.bracket.angle.begin.viml"


implement fundamental game system

implement fundamental game system

--- source/javascripts/all.js.coffee
+++ source/javascripts/all.js.coffee
@@ -2,82 +2,149 @@
 #= require_tree .

 $ ->
+  materials = {
+    images: {
+      inactive:   "http://dummyimage.com/100x30.png&text=inac"
+      raiseLeft:  "http://dummyimage.com/100x30.png&text=left"
+      raiseRight: "http://dummyimage.com/100x30.png&text=right"
+      enemy:      "http://dummyimage.com/30x30.png&text=e"
+    }
+    fonts: {
+      google: {
+        families: ["Share"]
+      }
+    }
+  }
+
   # the canvas size should be (480, 480)
-  canvas = CE.defines("canvas").extend([Animation, Text, Input]).ready ->
+  canvas = CE.defines("canvas").extend([Animation, Hit, Input]).ready ->
     canvas.Scene.call("Title")

   canvas.Scene.New {
     name: "Title"
-
-    materials: {
-      images: {
-        static: "http://dummyimage.com/100x30.png",
-        leftUpper:  "http://dummyimage.com/100x30.png",
-        rightUpper: "http://dummyimage.com/100x30.png"
-      }
-      fonts: {
-        google: {
-          families: ["Share"]
-        }
-      }
-    }
+    materials: materials

     ready: (stage) ->
       # message "Press Enter"
       pressEnter = @createElement()
       pressEnter.font = '16pt "Share"'
-      pressEnter.fillText("Press Enter", 195, 350)
+      pressEnter.fillText("PRESS ENTER", 185, 350)
       stage.append(pressEnter)

       # watch keyboard
       canvas.Input.keyDown Input.Enter, () ->
         canvas.Input.clearKeys Input.Enter
-        canvas.Scene.call("Game")
+        canvas.Scene.call("Wait", {params: {score: 0}})

     render: (stage) ->
       stage.refresh()
   }

   canvas.Scene.New {
-    name: "Game"
+    name: "Wait"
+    materials: materials

-    materials: {
-      images: {
-        inactive:   "http://dummyimage.com/100x30.png&text=inac",
-        raiseLeft:  "http://dummyimage.com/100x30.png&text=left",
-        raiseRight: "http://dummyimage.com/100x30.png&text=right"
-      }
-    }
+    called: (stage) ->
+      # draw character
+      @me = @createElement()
+      @me.drawImage "inactive", 190, 225
+      stage.append(@me)
+
+    ready: (stage, params) ->
+      @params = params
+      @time  = Math.floor(Math.random() * 50) + 100
+
+    render: (stage) ->
+      isTimeToFight = () =>
+        if @time == 0
+          true
+        else
+          @time -= 1
+          false
+
+      if isTimeToFight()
+        canvas.Scene.call("Fight", {params: @params})
+      else
+        stage.refresh()
+  }
+
+  canvas.Scene.New {
+    name: "Fight"
+    materials: materials
+
+    called: (stage) ->
+      # draw character first to avoid glitch
+      @me = Class.New("Entity", [stage])
+      @me.rect(100, 30)
+      @me.position(190, 225)
+      @me.el.drawImage("inactive")
+      stage.append(@me.el)

     ready: (stage, params) ->
       @state = "inactive"
-      @lock = @time = 0
+      @score = params.score
+      direction = [1, -1][Math.round(Math.random())]
+      @speed = direction * (@score + 2)
+      @stateToWin = ["raiseRight", "raiseLeft"][(direction+1)/2]
+      @lock  = 0

-      # draw character
-      @me = @createElement()
-      @me.drawImage @state, 190, 225
-      stage.append(@me)
+      # summon enemy
+      @enemy = Class.New("Entity", [stage])
+      @enemy.rect(30)
+      @enemy.position(270*(1 - direction) - 30, 225)
+      @enemy.el.drawImage("enemy")
+      stage.append(@enemy.el)

       canvas.Input.keyDown Input.Left, () =>
         if @lock == 0
           @state = "raiseLeft"
-          @lock = 50
+          @lock = 60
       canvas.Input.keyDown Input.Right, () =>
         if @lock == 0
           @state = "raiseRight"
-          @lock = 50
+          @lock = 60

     render: (stage) ->
-      if @lock == 0
-        @state = "inactive"
-      else
-        @lock -= 1
+      processLock = () =>
+        if @lock == 0
+          @state = "inactive"
+        else
+          @lock -= 1

-      if @time < 300
-        @time += 1
-      else
-        console.log "hey"
+      judgeAndCall = () =>
+        @enemy.hit [@me], (el) =>
+          if @state == @stateToWin
+            canvas.Scene.call("Wait",  {params: {score: @score+1}})
+          else
+            canvas.Scene.call("Score", {params: {score: @score}})

-      @me.drawImage @state, 190, 225
+      processLock()
+      judgeAndCall()
+
+      @me.el.drawImage(@state)
+      @enemy.move(@speed)
       stage.refresh()
   }
+
+  canvas.Scene.New {
+    name: "Score"
+    materials: materials
+
+    ready: (stage, params) ->
+      score = params.score
+      result = @createElement()
+      result.font = '16pt "Share"'
+      result.fillText("SCORE: #{score}", 200, 200)
+      stage.append(result)
+
+      escToTitle = @createElement()
+      escToTitle.font = '16pt "Share"'
+      escToTitle.fillText("ESC TO TITLE", 185, 350)
+      stage.append(escToTitle)
+
+      canvas.Input.keyDown Input.Esc, () ->
+        canvas.Scene.call("Title")
+
+    render: (stage) ->
+      stage.refresh()
+  }


Adjusted speed limit zones a bit

Adjusted speed limit zones a bit

--- dev/europe.cson
+++ dev/europe.cson
@@ -723,8 +723,8 @@
   elements: [
     { type: 'square-rounded', color: 'black' }
     { type: 'square-rounded', color: 'white', transform: 'scale(.95)' }
-    { type: 'circle-bg', color: 'red', transform: 'translate(0,-8%) scale(.7)' }
-    { type: 'circle-bg', color: 'white', transform: 'translate(0,-8%) scale(.7) {fit_border_circle}' }
+    { type: 'circle-bg', color: 'red', transform: 'translate(0,-10%) scale(.65)' }
+    { type: 'circle-bg', color: 'white', transform: 'translate(0,-10%) scale(.65) {fit_border_circle}' }
     { type: 'speed_zone_value', color: 'black', transform: 'translate(0,-30%) scale(.7)' }
     { type: 'content-4', content: 'ZONE', color: 'black', transform: 'translate(0,150%) scale(.8)'}
   ]


test adding to collection after rendering

--- spec/client/view/BaseSpec.coffee
+++ spec/client/view/BaseSpec.coffee
@@ -109,7 +109,6 @@
             bindings: { "model.name": { type: "text" } }
         Lanes.Views.Base.extend(LowerView)
         collection = new Collection
-        collection.add([{ name: "One"}, {name: "Two"},{name:"Three"}])
         view = makeView({
             subviews:
                 lower:
@@ -119,6 +118,8 @@
             template: "
text,text,text
unmodified
More text
"
         }, { collection: collection})
         view.render()
+        expect(view.$('h1')).toHaveLength(0)
+        collection.add([{ name: "One"}, {name: "Two"},{name:"Three"}])
         expect(view.$('h1')).toHaveLength(3)
         expect(view.$('h1:first')).toContainText('One')

@@ -150,3 +151,16 @@

         model.name="Ralph"
         expect(nameSpy).toHaveBeenCalled()
+
+
+    it "invokes collection events", ->
+        eventSpy = jasmine.createSpy('event')
+        view = makeView({
+            collectionEvents:
+                all: 'onEvent'
+            onEvent: eventSpy
+            initialize: -> this.collection = new Collection
+        })
+        expect(eventSpy).not.toHaveBeenCalled()
+        view.collection.add(new Model)
+        expect(eventSpy).toHaveBeenCalled()


Fixed typo

Fixed typo


OX-142 exports

OX-142 exports

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -30,6 +30,7 @@
       exports : "THREE"
     "three.trackball" :
       deps : ["three"]
+      exports : "THREE.TrackballControls"
     "stats" :
       exports : "Stats"
     "v3" :


Update specs TODO

Update specs TODO

--- specs/examples/todo.specs.coffee
+++ specs/examples/todo.specs.coffee
@@ -36,3 +36,6 @@

 describe 'Accepting or canceling changes with intermediate models', ->
   it 'should be implemented'
+
+describe 'Collection with uniquely named elements', ->
+  it 'should be implemented'


Using new Sky Server query as data source

--- app/models/SkyServerSubject.coffee
+++ app/models/SkyServerSubject.coffee
@@ -5,15 +5,13 @@
   @fetch: (count = 1) ->
     params =
       dataType: 'jsonp',
-      url: "http://skyserver.herokuapp.com/skyserver.json"
+      url: "http://skyserver.herokuapp.com/skyserver/random.json"
       data:
-        number: count
-        test:   'blah'
+        count: count
       callback: 'givemegalaxies'
       success: (data) =>
-        console.log data
-
-    console.log 'here'
+        console.log object['objID'], object['ra'], object['dec'] for object in data
+
     $.ajax(params)

 module.exports = SkyServerSubject


JUser: getOdeskAuthorizeUrl shared method re-exposed to client after master merge

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -92,6 +92,7 @@
         'fetchRelatedUsersFromGraph', 'fetchDomains', 'fetchDomains',
         'unlinkOauth', 'changeUsername', 'fetchOldKodingDownloadLink',
         'markUserAsExempt', 'checkFlag', 'userIsExempt', 'checkGroupMembership',
+        'getOdeskAuthorizeUrl'
       ]
     schema                  :
       foreignAuth           :
@@ -383,7 +384,7 @@
   @fetchCachedUserCount: (callback)->
     if (Date.now() - @lastUserCountFetchTime)/1000 < 60
       return callback null, @cachedUserCount
-    JAccount.count type:'registered', (err, count)=>
+    JAccount.count type:'registered', (err, count)=>
       return callback err if err
       @lastUserCountFetchTime = Date.now()
       @cachedUserCount = count


take into account scroller bar width while resizing window

--- app/assets/javascripts/views/grid-view.js.coffee
+++ app/assets/javascripts/views/grid-view.js.coffee
@@ -11,14 +11,20 @@

   adjustLayout: ->
-    #todo take into account scroller bar width
-    @set('width', Ember.$('.files-panel').width())
-    @set('height', Ember.$(window).height() - 41)
+    #take into account scroller bar width
+    old_overflow = document.body.style.overflow
+    document.body.style.overflow = "hidden"
+    curr_width = Ember.$('.files-panel').width()
+    curr_height = Ember.$(window).height() - 41
+    document.body.style.overflow = old_overflow
+
+    @set('width', curr_width)
+    @set('height', curr_height)
     @get('context').set('cols', @get('columnCount'))

   didInsertElement: ->
     @adjustLayout()
-    Ember.$(window).on 'resize', @debouncer((=> @adjustLayout()), 200)
+    Ember.$(window).on 'resize', @debouncer((=> @adjustLayout()), 100)
     @_super()

   willDestroyElement: -> Ember.$(window).off('resize')


protocol: Set responseTo when requestId is provided

Also centralizes the sending of response to
clarify the basic 1-request-1-response model.
Events can still be emitted on the side

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -68,12 +68,12 @@

         connection.send resp

-    connection.send
+    ack =
         protocol: 'component'
         command: 'componentsready'
         payload: Object.keys(components).length

-    return resolve null
+    return resolve ack

 sendExportedPorts = (connection, runtime) ->
     # go over runtime.graph and expose exported ports
@@ -94,10 +94,14 @@
             type: 'any' # FIXME
             addressable: false # FIXME
             required: false # FIXME
-    connection.send
-        protocol: 'runtime'
-        command: 'ports'
-        payload: ports
+
+    # XXX: avoid being sent before response, confuses clients not supporting requestId/responseTo
+    setTimeout () ->
+      connection.send
+          protocol: 'runtime'
+          command: 'ports'
+          payload: ports
+    , 100

 sendPacketCmd = (runtime, port, event, payload) ->
     return console.log "WARN: sendPacket, unknown event #{event}" if event is not 'data'
@@ -139,20 +143,21 @@
           r.id = runtime.options.id

         return runtime.device.ping().then () ->
-          connection.send
+          response =
             protocol: "runtime"
             command: "runtime"
             payload: r
           sendExportedPorts connection, runtime
+          return response

     else if command is 'packet'
         if payload.event is 'data'
             buffer = sendPacketCmd runtime, payload.port, payload.event, payload.payload
             runtime.device.sendMany(buffer)
             .then () ->
-                sendAck connection, { protocol: 'runtime', command: 'packetsent', payload: payload }
+                return { protocol: 'runtime', command: 'packetsent', payload: payload }
         else
-            return Promise.resolve()
+            return Promise.resolve(request)

     else
         return Promise.reject("Unknown FBP command runtime:#{command}: #{payload}")
@@ -192,14 +197,11 @@
         isMainGraph = payload.name == runtime.namespace + '/' + runtime.graph.name
         p = if isMainGraph then getGraphSource(runtime) else getComponentSource(runtime, payload.name)
         return p.then (payload) ->
-          sendAck connection, { protocol: 'component', command: 'source', payload: payload }
+          return { protocol: 'component', command: 'source', payload: payload }
     else
-        return Promise.reject("Unknown FBP command component:#{command}: #{payload}")
+        return Promise.reject(new Error("Unknown FBP command component:#{command}: #{payload}"))
     return

-sendAck = (connection, msg) ->
-    delete msg.payload.secret
-    connection.send msg

 handleGraphCommand = (command, payload, connection, runtime) ->
     request = { protocol: 'graph', command: command, payload: payload }
@@ -217,66 +219,57 @@
         runtime.exportedEdges = []
         runtime.edgesForInspection = []

-        return sendMessage(runtime, request).then (response) ->
-          sendAck connection, response
+        return sendMessage(runtime, request)
     else if command is "addnode"
         graph.processes[payload.id] = payload
+        graph.componentMap[payload.id] = payload.component
+        # TODO: respect nodeId from runtime
         graph.nodeMap[payload.id] = { id: graph.currentNodeId++ }
-        graph.componentMap[payload.id] = payload.component
-        # TODO: wait for nodeId from runtime, update nodeMap then
         return sendMessage(runtime, request).then (response) ->
-          sendAck connection, response
+          return response

     else if command is "removenode"
         return sendMessage(runtime, request).then (response) ->
           delete graph.processes[payload.id]
           delete graph.nodeMap[payload.id]
           delete graph.componentMap[payload.id]
-          sendAck connection, response
+          return response

     else if command is "renamenode"
         node = graph.processes[payload.from]
         graph.processes[payload.to] = node
-        return sendMessage(runtime, request).then (response) ->
-          sendAck connection, response
+        return sendMessage(runtime, request)
     else if command is "changenode"
         # FIXME: ignored
-        sendAck connection, request
-        return Promise.resolve()
+        return Promise.resolve(request)
     else if command is "addedge"
         graph.connections.push protocol.wsConnectionFormatToFbp(payload)
-        return sendMessage(runtime, request).then (response) ->
-          sendAck connection, response
+        return sendMessage(runtime, request)
     else if command is "removeedge"
         graph.connections = connectionsWithoutEdge(graph.connections, protocol.wsConnectionFormatToFbp(payload))
-        return sendMessage(runtime, request).then (response) ->
-          sendAck connection, response
+        return sendMessage(runtime, request)
     else if command is "changeedge"
         # FIXME: ignored
-        sendAck connection, { protocol: 'graph', command: command, payload: payload }
-        return Promise.resolve()
+        return Promise.resolve { protocol: 'graph', command: command, payload: payload }
     else if command is "addinitial"
         graph.connections.push protocol.wsConnectionFormatToFbp(payload)
         return sendMessage(runtime, request).then (response) ->
-          sendAck connection, request # TODO: use response from RT
+          return Promise.resolve(request) # TODO: use response from RT
     else if command is "removeinitial"
         # TODO: send to runtime side, wait for response
         graph.connections = connectionsWithoutEdge(graph.connections, protocol.wsConnectionFormatToFbp(payload))
-        sendAck connection, request
-        return Promise.resolve()
+        return Promise.resolve(request)

     else if command is "addinport"
         graph.inports[payload.public] =
             process: payload.node
             port: payload.port
         sendExportedPorts connection, runtime
-        sendAck connection, request
-        return Promise.resolve()
+        return Promise.resolve(request)
     else if command is "removeinport"
         delete graph.inports[payload.public]
         sendExportedPorts connection, runtime
-        sendAck connection, request
-        return Promise.resolve()
+        return Promise.resolve(request)
     else if command is "addoutport"
         graph.outports[payload.public] =
             process: payload.node
@@ -291,13 +284,12 @@
         # update subscriptions on device side
         edges = runtime.exportedEdges.concat runtime.edgesForInspection
         return subscribeEdges(runtime, edges).then () ->
-          sendAck connection, request
+          return request

     else if command is "removeoutport"
         delete graph.outports[payload.public]
         sendExportedPorts connection, runtime
-        sendAck connection, request
-        return Promise.resolve()
+        return Promise.resolve(request)
     else
         return Promise.reject(new Error("Unknown FBP command graph:#{command} : #{payload}"))

@@ -469,7 +461,7 @@
     if command in ['start', 'stop', 'getstatus']
         req = { protocol: 'network', command: command, payload: payload }
         return sendMessage(runtime, req).then (resp) ->
-          sendAck connection, resp
+          return resp
     else if command in ['debug']
         # does nothing relevant, response not expected
         return Promise.resolve()
@@ -477,10 +469,22 @@
         runtime.edgesForInspection = payload.edges
         edges = runtime.edgesForInspection.concat runtime.exportedEdges
         return subscribeEdges(runtime, edges).then () ->
-          sendAck connection, { protocol: 'network', command: command, payload: payload }
+          return { protocol: 'network', command: command, payload: payload }
     else
         e = new Error "Unknown FBP runtime command on protocol 'network':#{command}: #{payload}"
         return Promise.reject(e)
+
+sendResponse = (connection, request, response) ->
+    response = JSON.parse(JSON.stringify(response)) # in case its a copy of request
+    response.responseTo = request.requestId
+    delete response.payload.secret
+    delete response.requestId
+    connection.send response
+
+validResponse = (m) ->
+  # basic sanity check
+  hasAll = m.protocol? and m.command? and m.payload?
+  return hasAll

 handleMessage = (runtime, contents) ->
     connection = runtime.conn
@@ -504,9 +508,17 @@
     if not p
       console.error("handler for #{contents.protocol}:#{contents.command} did not return a Promise")

-    p.catch (err) ->
-        payload = { message: err.message }
-        connection.send { protocol: contents.protocol, command: 'error', payload: payload }
+    p.then (response) ->
+      if not validResponse(response)
+        throw new Error("Invalid FBP response to #{contents} : #{response}")
+      sendResponse connection, contents, response
+    .catch (err) ->
+        errorResponse =
+          responseTo: contents.requestId
+          protocol: contents.protocol,
+          command: 'error',
+          payload: { message: err.message }
+        connection.send errorResponse

         console.error('FBP PROTOCOL error:', err)
         console.error(err.stack)


Request frame at the end

Request frame at the end

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -186,16 +186,15 @@

     handleScroll: =>
         return if @renderProgress >= @collection.length
-        window.requestAnimationFrame @handleScroll

         $table = @$ "tbody"
-        return if $table.length is 0
-
         tableBottom = $table.height() + $table.offset().top
         $window = $(window)
         scrollBottom = $window.scrollTop() + $window.height()
         if scrollBottom >= tableBottom
             @renderTableChunk()
+
+        window.requestAnimationFrame @handleScroll

     updateUrl: =>
         app.router.navigate "/requests/#{ @requestsFilter }/#{ @requestsSubFilter }/#{ @searchFilter }", { replace: true }


Fix possible maximum call stack error

Fix possible maximum call stack error

--- lib/session.coffee
+++ lib/session.coffee
@@ -639,6 +639,20 @@
       return rpcRequest(sessionData, null, callback)
     )

+  if not sessionData.declarations[0].length
+    return callback()
+
+  if sessionData.rpcCount >= 255 or
+      sessionData.revisions.length >= 8 or
+      sessionData.iteration >= 128
+
+    fault = {
+      code: 'endless_cycle'
+      message: 'The provision seems to be repeating indefinitely'
+      timestmap: sessionData.timestamp
+    }
+    return callback(null, fault)
+
   if (sessionData.syncState?.virtualParameterDeclarations?.length or 0) < sessionData.declarations.length
     inception = sessionData.syncState?.virtualParameterDeclarations?.length or 0
     vpd = runDeclarations(sessionData, sessionData.declarations[inception])
@@ -671,17 +685,6 @@

   if not sessionData.syncState?
     return callback()
-
-  if sessionData.rpcCount >= 255 or
-      sessionData.revisions.length >= 8 or
-      sessionData.iteration >= 64
-
-    fault = {
-      code: 'endless_cycle'
-      message: 'The provision seems to be repeating indefinitely'
-      timestmap: sessionData.timestamp
-    }
-    return callback(null, fault)

   inception = sessionData.declarations.length - 1


Fix bug in passing provision arguments

Fix bug in passing provision arguments

--- lib/session.coffee
+++ lib/session.coffee
@@ -346,7 +346,7 @@

     counter += 2
     sandbox.run(sessionData.cache.provisions[provision[0]].script,
-      {args: provision[1]}, sessionData.timestamp, sessionData.deviceData,
+      {args: provision.slice(1)}, sessionData.timestamp, sessionData.deviceData,
       sessionData.extensionsCache, startRevision, endRevision,
       (err, _fault, _clear, _declarations, _done) ->
         if err or _fault


Add route to editor

Add route to editor

--- app/router.cjsx
+++ app/router.cjsx
@@ -117,6 +117,7 @@
       <Route path="data-exports" component={require './pages/lab/data-dumps'} />
       <Route path="tutorial" component={require './pages/lab/tutorial'} />
       <Route path="guide" component={require './pages/lab/field-guide'} />
+      <Route path="mini-course" component={require './pages/lab/mini-course'} />
     </Route>
     <Route path="lab-policies" component={require './pages/lab/lab-policies'} />
     <Route path="lab-how-to" component={require './pages/lab/how-to-page'} />


Cleaned up formatting in client tests

Cleaned up formatting in client tests

--- test/client.coffee
+++ test/client.coffee
@@ -10,450 +10,449 @@

 {makePassPart, expectCalls} = require('./helpers')

-genTests = (client) ->
-  testCase
-    setUp: (callback) ->
-      @name = 'testingdoc'
-
-      @auth = (client, action) -> action.accept()
-
-      options =
-        socketio: {}
-        rest: null
-        db: {type: 'memory'}
-        auth: (client, action) => @auth client, action
-
-      @model = server.createModel options
-      @server = server options, @model
-
-      @server.listen =>
-        @port = @server.address().port
-        @c = new client.Connection "http://localhost:#{@port}/sjs"
-        @c.on 'connect', callback
+genTests = (client) -> testCase
+  setUp: (callback) ->
+    @name = 'testingdoc'
+
+    @auth = (client, action) -> action.accept()
+
+    options =
+      socketio: {}
+      rest: null
+      db: {type: 'memory'}
+      auth: (client, action) => @auth client, action
+
+    @model = server.createModel options
+    @server = server options, @model

-    tearDown: (callback) ->
-      @c.disconnect()
-
-      @server.on 'close', callback
-      @server.close()
-
-    'open using the bare API': (test) ->
-      client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
-        test.ok doc
+    @server.listen =>
+      @port = @server.address().port
+      @c = new client.Connection "http://localhost:#{@port}/sjs"
+      @c.on 'connect', callback
+
+  tearDown: (callback) ->
+    @c.disconnect()
+
+    @server.on 'close', callback
+    @server.close()
+
+  'open using the bare API': (test) ->
+    client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
+      test.ok doc
+      test.ifError error
+
+      test.strictEqual doc.snapshot, ''
+      test.strictEqual doc.name, @name
+      test.strictEqual doc.type.name, types.text.name
+      test.strictEqual doc.version, 0
+
+      doc.close()
+      test.done()
+
+  'open multiple documents using the bare API on the same connection': (test) ->
+    client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc1, error) =>
+      test.ok doc1
+      test.ifError error
+
+      client.open @name + 2, 'text', "http://localhost:#{@port}/sjs", (doc2, error) ->
+        test.ok doc2
         test.ifError error

-        test.strictEqual doc.snapshot, ''
-        test.strictEqual doc.name, @name
-        test.strictEqual doc.type.name, types.text.name
-        test.strictEqual doc.version, 0
-
-        doc.close()
-        test.done()
-
-    'open multiple documents using the bare API on the same connection': (test) ->
-      client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc1, error) =>
-        test.ok doc1
-        test.ifError error
-
-        client.open @name + 2, 'text', "http://localhost:#{@port}/sjs", (doc2, error) ->
-          test.ok doc2
-          test.ifError error
-
-          doc2.submitOp {i:'hi'}, ->
-            test.strictEqual doc2.snapshot, 'hi'
-            doc1.submitOp {i:'booyah'}, ->
-              test.strictEqual doc1.snapshot, 'booyah'
-              doc2.close ->
-                doc1.submitOp {i:'more text '}, ->
-                  test.strictEqual doc1.snapshot, 'more text booyah'
-
-                  doc1.close()
-                  doc2.close()
-                  test.done()
-
-    'create connection': (test) ->
-      test.ok @c
-      test.done()
-
-    'create a new document': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.ok doc
-        test.ifError error
-
-        test.strictEqual doc.name, @name
-        test.strictEqual doc.type.name, types.text.name
-        test.strictEqual doc.version, 0
-        test.done()
-
-    'open a document that is already open': (test) ->
-      @c.open @name, 'text', (doc1, error) =>
-        test.ifError error
-        test.ok doc1
-        test.strictEqual doc1.name, @name
-        @c.open @name, 'text', (doc2, error) =>
-          test.strictEqual doc1, doc2
-          test.done()
-
-    'open a document that already exists': (test) ->
-      @model.create @name, 'text', =>
-        @c.open @name, 'text', (doc, error) =>
-          test.ifError error
-          test.ok doc
-
-          test.strictEqual doc.type.name, 'text'
-          test.strictEqual doc.version, 0
-          test.done()
-
-    'open a document with a different type': (test) ->
-      @model.create @name, 'simple', =>
-        @c.open @name, 'text', (doc, error) =>
-          test.ok error
-          test.strictEqual doc, null
-          test.done()
-
-    'submit an op to a document': (test) ->
+        doc2.submitOp {i:'hi'}, ->
+          test.strictEqual doc2.snapshot, 'hi'
+          doc1.submitOp {i:'booyah'}, ->
+            test.strictEqual doc1.snapshot, 'booyah'
+            doc2.close ->
+              doc1.submitOp {i:'more text '}, ->
+                test.strictEqual doc1.snapshot, 'more text booyah'
+
+                doc1.close()
+                doc2.close()
+                test.done()
+
+  'create connection': (test) ->
+    test.ok @c
+    test.done()
+
+  'create a new document': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ok doc
+      test.ifError error
+
+      test.strictEqual doc.name, @name
+      test.strictEqual doc.type.name, types.text.name
+      test.strictEqual doc.version, 0
+      test.done()
+
+  'open a document that is already open': (test) ->
+    @c.open @name, 'text', (doc1, error) =>
+      test.ifError error
+      test.ok doc1
+      test.strictEqual doc1.name, @name
+      @c.open @name, 'text', (doc2, error) =>
+        test.strictEqual doc1, doc2
+        test.done()
+
+  'open a document that already exists': (test) ->
+    @model.create @name, 'text', =>
       @c.open @name, 'text', (doc, error) =>
         test.ifError error
+        test.ok doc
+
+        test.strictEqual doc.type.name, 'text'
+        test.strictEqual doc.version, 0
+        test.done()
+
+  'open a document with a different type': (test) ->
+    @model.create @name, 'simple', =>
+      @c.open @name, 'text', (doc, error) =>
+        test.ok error
+        test.strictEqual doc, null
+        test.done()
+
+  'submit an op to a document': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ifError error
+      test.strictEqual doc.name, @name
+
+      doc.submitOp [{i:'hi', p:0}], =>
+        test.deepEqual doc.snapshot, 'hi'
+        test.strictEqual doc.version, 1
+        test.done()
+
+      # The document snapshot should be updated immediately.
+      test.strictEqual doc.snapshot, 'hi'
+      # ... but the version tracks the server version, so thats still 0.
+      test.strictEqual doc.version, 0
+
+  'submit an op to a document using the API works': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      doc.insert 0, 'hi', =>
+        test.strictEqual doc.snapshot, 'hi'
+        test.strictEqual doc.getText(), 'hi'
+        @model.getSnapshot @name, (error, {snapshot}) ->
+          test.strictEqual snapshot, 'hi'
+          test.done()
+
+  'submitting an op while another op is inflight works': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ifError error
+
+      doc.submitOp [{i:'hi', p:0}], ->
+        test.strictEqual doc.version, 1
+      doc.flush()
+
+      doc.submitOp [{i:'hi', p:2}], ->
+        test.strictEqual doc.version, 2
+        test.done()
+
+  'compose multiple ops together when they are submitted together': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ifError error
+      test.strictEqual doc.name, @name
+
+      doc.submitOp [{i:'hi', p:0}], ->
+        test.strictEqual doc.version, 1
+
+      doc.submitOp [{i:'hi', p:0}], ->
+        test.strictEqual doc.version, 1
+        test.expect 4
+        test.done()
+
+  'compose multiple ops together when they are submitted while an op is in flight': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ifError error
+
+      doc.submitOp [{i:'hi', p:0}], ->
+        test.strictEqual doc.version, 1
+      doc.flush()
+
+      doc.submitOp [{i:'hi', p:2}], ->
+        test.strictEqual doc.version, 2
+      doc.submitOp [{i:'hi', p:4}], ->
+        test.strictEqual doc.version, 2
+        test.expect 4
+        test.done()
+
+  'Receive submitted ops': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.ifError error
+      test.strictEqual doc.name, @name
+
+      doc.on 'remoteop', (op) ->
+        test.deepEqual op, [{i:'hi', p:0}]
+
+        test.expect 3
+        test.done()
+
+      @model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (error, version) ->
+        test.fail error if error
+
+  'get a nonexistent document passes null to the callback': (test) ->
+    @c.openExisting @name, (doc) ->
+      test.strictEqual doc, null
+      test.done()
+
+  'get an existing document returns the document': (test) ->
+    @model.create @name, 'text', =>
+      @c.openExisting @name, (doc) =>
+        test.ok doc
+
         test.strictEqual doc.name, @name
-
-        doc.submitOp [{i:'hi', p:0}], =>
-          test.deepEqual doc.snapshot, 'hi'
-          test.strictEqual doc.version, 1
+        test.strictEqual doc.type.name, 'text'
+        test.strictEqual doc.version, 0
+        test.done()
+
+  'client transforms remote ops before applying them': (test) ->
+    # There's a bit of magic in the timing of this test. It would probably be more consistent
+    # if this test were implemented using a stubbed out backend.
+
+    clientOp = [{i:'client', p:0}]
+    serverOp = [{i:'server', p:0}]
+    serverTransformed = types.text.transform(serverOp, clientOp, 'right')
+
+    finalDoc = types.text.create() # v1
+    finalDoc = types.text.apply(finalDoc, clientOp) # v2
+    finalDoc = types.text.apply(finalDoc, serverTransformed) #v3
+
+    @c.open @name, 'text', (doc, error) =>
+      opsRemaining = 2
+
+      onOpApplied = ->
+        opsRemaining--
+        unless opsRemaining
+          test.strictEqual doc.version, 2
+          test.strictEqual doc.snapshot, finalDoc
           test.done()

-        # The document snapshot should be updated immediately.
-        test.strictEqual doc.snapshot, 'hi'
-        # ... but the version tracks the server version, so thats still 0.
-        test.strictEqual doc.version, 0
-
-    'submit an op to a document using the API works': (test) ->
+      doc.submitOp clientOp, onOpApplied
+      doc.on 'remoteop', (op) ->
+        test.deepEqual op, serverTransformed
+        onOpApplied()
+
+      @model.applyOp @name, {v:0, op:serverOp}, (error) ->
+        test.fail error if error
+
+  'doc fires both remoteop and change messages when remote ops are received': (test) ->
+    passPart = makePassPart test, 2
+    @c.open @name, 'text', (doc, error) =>
+      sentOp = [{i:'asdf', p:0}]
+      doc.on 'change', (op) ->
+        test.deepEqual op, sentOp
+        passPart()
+      doc.on 'remoteop', (op) ->
+        test.deepEqual op, sentOp
+        passPart()
+
+      @model.applyOp @name, {v:0, op:sentOp}, (error) ->
+        test.fail error if error
+
+  'doc only fires change ops from locally sent ops': (test) ->
+    passPart = makePassPart test, 2
+    @c.open @name, 'text', (doc, error) ->
+      sentOp = [{i:'asdf', p:0}]
+      doc.on 'change', (op) ->
+        test.deepEqual op, sentOp
+        passPart()
+      doc.on 'remoteop', (op) ->
+        throw new Error 'Should not have received remoteOp event'
+
+      doc.submitOp sentOp, (error, v) ->
+        passPart()
+
+  'doc does not receive ops after close called': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      doc.on 'change', (op) ->
+        throw new Error 'Should not have received op when the doc was unfollowed'
+
+      doc.close =>
+        @model.applyOp @name, {v:0, op:[{i:'asdf', p:0}]}, =>
+          test.done()
+
+  'created locally is set on new docs': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      test.strictEqual doc.created, true
+      test.done()
+
+  'created locally is not set on old docs': (test) ->
+    @model.create @name, 'text', =>
       @c.open @name, 'text', (doc, error) =>
-        doc.insert 0, 'hi', =>
-          test.strictEqual doc.snapshot, 'hi'
-          test.strictEqual doc.getText(), 'hi'
-          @model.getSnapshot @name, (error, {snapshot}) ->
-            test.strictEqual snapshot, 'hi'
-            test.done()
-
-    'submitting an op while another op is inflight works': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.ifError error
-
-        doc.submitOp [{i:'hi', p:0}], ->
-          test.strictEqual doc.version, 1
-        doc.flush()
-
-        doc.submitOp [{i:'hi', p:2}], ->
-          test.strictEqual doc.version, 2
-          test.done()
-
-    'compose multiple ops together when they are submitted together': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.ifError error
-        test.strictEqual doc.name, @name
-
-        doc.submitOp [{i:'hi', p:0}], ->
-          test.strictEqual doc.version, 1
-
-        doc.submitOp [{i:'hi', p:0}], ->
-          test.strictEqual doc.version, 1
-          test.expect 4
-          test.done()
-
-    'compose multiple ops together when they are submitted while an op is in flight': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.ifError error
-
-        doc.submitOp [{i:'hi', p:0}], ->
-          test.strictEqual doc.version, 1
-        doc.flush()
-
-        doc.submitOp [{i:'hi', p:2}], ->
-          test.strictEqual doc.version, 2
-        doc.submitOp [{i:'hi', p:4}], ->
-          test.strictEqual doc.version, 2
-          test.expect 4
-          test.done()
-
-    'Receive submitted ops': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.ifError error
-        test.strictEqual doc.name, @name
-
-        doc.on 'remoteop', (op) ->
-          test.deepEqual op, [{i:'hi', p:0}]
-
-          test.expect 3
-          test.done()
-
-        @model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (error, version) ->
-          test.fail error if error
-
-    'get a nonexistent document passes null to the callback': (test) ->
-      @c.openExisting @name, (doc) ->
-        test.strictEqual doc, null
-        test.done()
-
-    'get an existing document returns the document': (test) ->
-      @model.create @name, 'text', =>
-        @c.openExisting @name, (doc) =>
-          test.ok doc
-
-          test.strictEqual doc.name, @name
-          test.strictEqual doc.type.name, 'text'
-          test.strictEqual doc.version, 0
-          test.done()
-
-    'client transforms remote ops before applying them': (test) ->
-      # There's a bit of magic in the timing of this test. It would probably be more consistent
-      # if this test were implemented using a stubbed out backend.
-
-      clientOp = [{i:'client', p:0}]
-      serverOp = [{i:'server', p:0}]
-      serverTransformed = types.text.transform(serverOp, clientOp, 'right')
-
-      finalDoc = types.text.create() # v1
-      finalDoc = types.text.apply(finalDoc, clientOp) # v2
-      finalDoc = types.text.apply(finalDoc, serverTransformed) #v3
-
-      @c.open @name, 'text', (doc, error) =>
-        opsRemaining = 2
-
-        onOpApplied = ->
-          opsRemaining--
-          unless opsRemaining
-            test.strictEqual doc.version, 2
-            test.strictEqual doc.snapshot, finalDoc
-            test.done()
-
-        doc.submitOp clientOp, onOpApplied
-        doc.on 'remoteop', (op) ->
-          test.deepEqual op, serverTransformed
-          onOpApplied()
-
-        @model.applyOp @name, {v:0, op:serverOp}, (error) ->
-          test.fail error if error
-
-    'doc fires both remoteop and change messages when remote ops are received': (test) ->
-      passPart = makePassPart test, 2
-      @c.open @name, 'text', (doc, error) =>
-        sentOp = [{i:'asdf', p:0}]
-        doc.on 'change', (op) ->
-          test.deepEqual op, sentOp
-          passPart()
-        doc.on 'remoteop', (op) ->
-          test.deepEqual op, sentOp
-          passPart()
-
-        @model.applyOp @name, {v:0, op:sentOp}, (error) ->
-          test.fail error if error
-
-    'doc only fires change ops from locally sent ops': (test) ->
-      passPart = makePassPart test, 2
-      @c.open @name, 'text', (doc, error) ->
-        sentOp = [{i:'asdf', p:0}]
-        doc.on 'change', (op) ->
-          test.deepEqual op, sentOp
-          passPart()
-        doc.on 'remoteop', (op) ->
-          throw new Error 'Should not have received remoteOp event'
-
-        doc.submitOp sentOp, (error, v) ->
-          passPart()
-
-    'doc does not receive ops after close called': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        doc.on 'change', (op) ->
-          throw new Error 'Should not have received op when the doc was unfollowed'
-
-        doc.close =>
-          @model.applyOp @name, {v:0, op:[{i:'asdf', p:0}]}, =>
-            test.done()
-
-    'created locally is set on new docs': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        test.strictEqual doc.created, true
-        test.done()
-
-    'created locally is not set on old docs': (test) ->
-      @model.create @name, 'text', =>
-        @c.open @name, 'text', (doc, error) =>
-          test.strictEqual doc.created, false
-          test.done()
-
-    'new Connection emits errors if auth rejects you': (test) ->
-      @auth = (client, action) -> action.reject()
-
-      c = new client.Connection "http://localhost:#{@port}/sjs"
-      c.on 'connect', ->
-        test.fail 'connection shouldnt have connected'
-      c.on 'connect failed', (error) ->
-        test.strictEqual error, 'forbidden'
-        test.done()
-
-    '(new Connection).open() fails if auth rejects the connection': (test) ->
-      @auth = (client, action) -> action.reject()
-
-      passPart = makePassPart test, 2
-      c = new client.Connection "http://localhost:#{@port}/sjs"
-
-      # Immediately opening a document should fail when the connection fails
+        test.strictEqual doc.created, false
+        test.done()
+
+  'new Connection emits errors if auth rejects you': (test) ->
+    @auth = (client, action) -> action.reject()
+
+    c = new client.Connection "http://localhost:#{@port}/sjs"
+    c.on 'connect', ->
+      test.fail 'connection shouldnt have connected'
+    c.on 'connect failed', (error) ->
+      test.strictEqual error, 'forbidden'
+      test.done()
+
+  '(new Connection).open() fails if auth rejects the connection': (test) ->
+    @auth = (client, action) -> action.reject()
+
+    passPart = makePassPart test, 2
+    c = new client.Connection "http://localhost:#{@port}/sjs"
+
+    # Immediately opening a document should fail when the connection fails
+    c.open @name, 'text', (doc, error) =>
+      test.fail doc if doc
+      test.strictEqual error, 'forbidden'
+      passPart()
+
+    c.on 'connect failed', =>
+      # The connection is now in an invalid state. Lets try and open a document...
       c.open @name, 'text', (doc, error) =>
         test.fail doc if doc
+        test.strictEqual error, 'connection closed'
+        passPart()
+
+  '(new Connection).open() fails if auth disallows reads': (test) ->
+    @auth = (client, action) ->
+      if action.type == 'read' then action.reject() else action.accept()
+
+    c = new client.Connection "http://localhost:#{@port}/sjs"
+    c.open @name, 'text', (doc, error) =>
+      test.fail doc if doc
+      test.strictEqual error, 'forbidden'
+      test.done()
+
+  'client.open fails if auth rejects the connection': (test) ->
+    @auth = (client, action) -> action.reject()
+
+    client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
+      test.fail doc if doc
+      test.strictEqual error, 'forbidden'
+      test.done()
+
+  'client.open fails if auth disallows reads': (test) ->
+    @auth = (client, action) ->
+      if action.type == 'read' then action.reject() else action.accept()
+
+    client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
+      test.fail doc if doc
+      test.strictEqual error, 'forbidden'
+      test.done()
+
+  "Can't submit an op if auth rejects you": (test) ->
+    @auth = (client, action) ->
+      if action.name == 'submit op' then action.reject() else action.accept()
+
+    @c.open @name, 'text', (doc, error) =>
+      doc.insert 0, 'hi', (op, error) =>
         test.strictEqual error, 'forbidden'
-        passPart()
-
-      c.on 'connect failed', =>
-        # The connection is now in an invalid state. Lets try and open a document...
-        c.open @name, 'text', (doc, error) =>
-          test.fail doc if doc
-          test.strictEqual error, 'connection closed'
-          passPart()
-
-    '(new Connection).open() fails if auth disallows reads': (test) ->
-      @auth = (client, action) ->
-        if action.type == 'read' then action.reject() else action.accept()
-
-      c = new client.Connection "http://localhost:#{@port}/sjs"
-      c.open @name, 'text', (doc, error) =>
-        test.fail doc if doc
-        test.strictEqual error, 'forbidden'
-        test.done()
-
-    'client.open fails if auth rejects the connection': (test) ->
-      @auth = (client, action) -> action.reject()
-
-      client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
-        test.fail doc if doc
-        test.strictEqual error, 'forbidden'
-        test.done()
-
-    'client.open fails if auth disallows reads': (test) ->
-      @auth = (client, action) ->
-        if action.type == 'read' then action.reject() else action.accept()
-
-      client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
-        test.fail doc if doc
-        test.strictEqual error, 'forbidden'
-        test.done()
-
-    "Can't submit an op if auth rejects you": (test) ->
-      @auth = (client, action) ->
-        if action.name == 'submit op' then action.reject() else action.accept()
-
-      @c.open @name, 'text', (doc, error) =>
-        doc.insert 0, 'hi', (op, error) =>
+        test.strictEqual doc.getText(), ''
+        # Also need to test that ops sent afterwards get sent correctly.
+        # because that behaviour IS CURRENTLY BROKEN
+
+        @model.getSnapshot @name, (error, {snapshot}) ->
+          test.strictEqual snapshot, ''
+          test.done()
+
+  'If an operation is rejected, the undo is applied as if auth did it': (test) ->
+    @auth = (client, action) ->
+      if action.name == 'submit op' then action.reject() else action.accept()
+
+    @c.open @name, 'text', (doc, error) =>
+      doc.on 'delete', (pos, text) ->
+        test.strictEqual text, 'hi'
+        test.strictEqual pos, 0
+        test.done()
+
+      doc.insert 0, 'hi'
+
+  'If auth rejects your op, other transforms work correctly': (test) ->
+    # This should probably have a randomized tester as well.
+    @auth = (client, action) ->
+      if action.name == 'submit op' and action.op[0].d == 'cC'
+        action.reject()
+      else
+        action.accept()
+
+    @c.open @name, 'text', (doc, error) =>
+      doc.insert 0, 'abcCBA', =>
+        e = expectCalls 3, =>
+          # The b's are successfully deleted, the ds are added by the server and the
+          # op to delete the cs is denied.
+          @model.getSnapshot @name, (error, {snapshot}) ->
+            test.deepEqual snapshot, 'acdDCA'
+            test.done()
+
+        doc.del 2, 2, (op, error) => # Delete the 'cC', so the document becomes 'abBA'
+          # This op is denied by the auth code
           test.strictEqual error, 'forbidden'
-          test.strictEqual doc.getText(), ''
-          # Also need to test that ops sent afterwards get sent correctly.
-          # because that behaviour IS CURRENTLY BROKEN
-
-          @model.getSnapshot @name, (error, {snapshot}) ->
-            test.strictEqual snapshot, ''
-            test.done()
-
-    'If an operation is rejected, the undo is applied as if auth did it': (test) ->
-      @auth = (client, action) ->
-        if action.name == 'submit op' then action.reject() else action.accept()
-
-      @c.open @name, 'text', (doc, error) =>
-        doc.on 'delete', (pos, text) ->
-          test.strictEqual text, 'hi'
-          test.strictEqual pos, 0
-          test.done()
-
-        doc.insert 0, 'hi'
-
-    'If auth rejects your op, other transforms work correctly': (test) ->
-      # This should probably have a randomized tester as well.
-      @auth = (client, action) ->
-        if action.name == 'submit op' and action.op[0].d == 'cC'
-          action.reject()
-        else
-          action.accept()
-
-      @c.open @name, 'text', (doc, error) =>
-        doc.insert 0, 'abcCBA', =>
-          e = expectCalls 3, =>
-            # The b's are successfully deleted, the ds are added by the server and the
-            # op to delete the cs is denied.
-            @model.getSnapshot @name, (error, {snapshot}) ->
-              test.deepEqual snapshot, 'acdDCA'
-              test.done()
-
-          doc.del 2, 2, (op, error) => # Delete the 'cC', so the document becomes 'abBA'
-            # This op is denied by the auth code
-            test.strictEqual error, 'forbidden'
-            e()
-
-          test.strictEqual doc.getText(), 'abBA'
-          doc.flush()
-
-          # Simultaneously, we'll apply another op locally:
-          doc.del 1, 2, -> # Delete the 'bB'
-            e()
-          test.strictEqual doc.getText(), 'aA'
-
-          # ... and yet another op on the server. (Remember, the server hasn't seen either op yet.)
-          @model.applyOp @name, {op:[{i:'dD', p:3}], v:1, meta:{}}, =>
-            @model.getSnapshot @name, e
-
-    'Text API is advertised': (test) ->
-      @c.open @name, 'text', (doc, error) ->
-        test.strictEqual doc.provides?.text, true
-        doc.close()
-        test.done()
-
-    'Text API can be used to insert into the document': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        doc.insert 0, 'hi', =>
-          test.strictEqual doc.getText(), 'hi'
-
-          @model.getSnapshot @name, (error, data) ->
-            test.strictEqual data.snapshot, 'hi'
-            doc.close()
-            test.done()
-
-    'Text documents emit high level editing events': (test) ->
-      @c.open @name, 'text', (doc, error) =>
-        doc.on 'insert', (pos, text) ->
-          test.strictEqual text, 'hi'
-          test.strictEqual pos, 0
+          e()
+
+        test.strictEqual doc.getText(), 'abBA'
+        doc.flush()
+
+        # Simultaneously, we'll apply another op locally:
+        doc.del 1, 2, -> # Delete the 'bB'
+          e()
+        test.strictEqual doc.getText(), 'aA'
+
+        # ... and yet another op on the server. (Remember, the server hasn't seen either op yet.)
+        @model.applyOp @name, {op:[{i:'dD', p:3}], v:1, meta:{}}, =>
+          @model.getSnapshot @name, e
+
+  'Text API is advertised': (test) ->
+    @c.open @name, 'text', (doc, error) ->
+      test.strictEqual doc.provides?.text, true
+      doc.close()
+      test.done()
+
+  'Text API can be used to insert into the document': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      doc.insert 0, 'hi', =>
+        test.strictEqual doc.getText(), 'hi'
+
+        @model.getSnapshot @name, (error, data) ->
+          test.strictEqual data.snapshot, 'hi'
           doc.close()
           test.done()
-
-        @model.applyOp @name, {op:[{i:'hi', p:0}], v:0, meta:{}}
-
-    'Works with an externally referenced type (like JSON)': (test) ->
-      @c.open @name, 'json', (doc, error) ->
-        test.ifError error
-        test.strictEqual doc.snapshot, null
-        doc.submitOp [{p:[], od:null, oi:[1,2,3]}], ->
-          test.deepEqual doc.snapshot, [1,2,3]
-          doc.close()
-          test.done()
-
-    '.open() throws an exception if the type is missing': (test) ->
-      test.throws ->
-        @c.open @name, 'does not exist', ->
-      test.done()
-
-    'Submitting an op and closing straight after works': (test) ->
-      # This catches a real bug.
-      client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
-        doc.insert 0, 'hi'
-        doc.close ->
-          test.done()
-
-  # This isn't working yet. I might have to rethink it.
-  #  'opening a document with a null name will open a new document with a random document name': (test) ->
-  #    client.open null, 'text', {host:'localhost', port:@port}, (doc, error) ->
-  #      console.log doc.name
-  #
-  #      test.strictEqual doc.snapshot, ''
-  #      test.strictEqual doc.type.name, 'text'
-  #      test.strictEqual doc.created, true
-  #      test.done()
+
+  'Text documents emit high level editing events': (test) ->
+    @c.open @name, 'text', (doc, error) =>
+      doc.on 'insert', (pos, text) ->
+        test.strictEqual text, 'hi'
+        test.strictEqual pos, 0
+        doc.close()
+        test.done()
+
+      @model.applyOp @name, {op:[{i:'hi', p:0}], v:0, meta:{}}
+
+  'Works with an externally referenced type (like JSON)': (test) ->
+    @c.open @name, 'json', (doc, error) ->
+      test.ifError error
+      test.strictEqual doc.snapshot, null
+      doc.submitOp [{p:[], od:null, oi:[1,2,3]}], ->
+        test.deepEqual doc.snapshot, [1,2,3]
+        doc.close()
+        test.done()
+
+  '.open() throws an exception if the type is missing': (test) ->
+    test.throws ->
+      @c.open @name, 'does not exist', ->
+    test.done()
+
+  'Submitting an op and closing straight after works': (test) ->
+    # This catches a real bug.
+    client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
+      doc.insert 0, 'hi'
+      doc.close ->
+        test.done()
+
+# This isn't working yet. I might have to rethink it.
+#  'opening a document with a null name will open a new document with a random document name': (test) ->
+#    client.open null, 'text', {host:'localhost', port:@port}, (doc, error) ->
+#      console.log doc.name
+#
+#      test.strictEqual doc.snapshot, ''
+#      test.strictEqual doc.type.name, 'text'
+#      test.strictEqual doc.created, true
+#      test.done()

 exports.native = genTests nativeclient
 exports.webclient = genTests webclient


Remove .only

Remove .only

--- test/expect.coffee
+++ test/expect.coffee
@@ -7,7 +7,7 @@
 describe 'expect', ->

   describe '#new', ->
-    context.only 'with a spy', ->
+    context 'with a spy', ->
       it 'should extend expect when called with a spy', ->
         spy = sinon.spy()
         spy('foo')


node width

node width

--- app/assets/javascripts/directives.coffee
+++ app/assets/javascripts/directives.coffee
@@ -5,10 +5,17 @@
     scope:
       nodes: '=',
     link: ($scope, $el, $attrs) ->
+
+
+
       # Helpers.
       # --------

       buildGraph = (data) ->
+
+        flare = get_flare data.links
+
+
         elements = []
         links = []
         _.each data.nodes, (node) ->
@@ -136,12 +143,13 @@
         new joint.shapes.basic.Rect(
           id: node.id
           size:
-            width: 100
+            width: 200
             height: height
           attrs:
-            label: node.label
-            hits:  node.hits
-            message: node.text
+            name:      node.name
+            label:     node.label
+            hits:      node.hits
+            message:   node.text
             responses: node.responses
             text:
               text: (node.label || ( if node.text then node.text[0..6] + "...") || "") + " " + node.hits
@@ -169,7 +177,7 @@

       paper.on "cell:mouseover", (cellView, evt, x, y) ->
         $scope.$apply("")
-
+        $scope.$parent.node    = cellView.model.attributes.attrs.name
         $scope.$parent.message = cellView.model.attributes.attrs.message
         $scope.$parent.label   = cellView.model.attributes.attrs.label
         $scope.$parent.hits    = cellView.model.attributes.attrs.hits
@@ -186,8 +194,8 @@
         joint.layout.DirectedGraph.layout graph,
           setLinkVertices: false
           rankDir: 'lr'
-          nodeSep: 50
-          rankSep: 50
+          nodeSep: 150
+          rankSep: 150
         return

       V(paper.viewport).translate 20, 20
@@ -198,4 +206,31 @@

       #layout($scope.xxx)
       # ---
-      # generated by js2coffee 2.0.1+      # generated by js2coffee 2.0.1
+
+      get_flare = (links) ->
+        source_h = {} # hash of source nodes
+        target_h = {} # hash of target nodes
+
+
+        _.each links, (l) ->
+          target_h[l.target] = 1
+          if source_h[l.source]
+            source_h[l.source].push l.target
+          else
+            source_h[l.source] = [l.target]
+
+        root = get_root(source_h, target_h)
+
+        flare = {name: root, children: [_.uniq source_h[root]]}
+        console.log flare
+        # _.each _.keys(source_h), (k) ->
+        #   _.each _.keys(source_h), (k2) ->
+        #     #console.log $.inArray(k, source_h[k2])        console.log root
+
+
+      get_root = (sources, targets) ->
+        root = String
+        _.each _.keys(sources), (k) ->
+          root = k unless targets[k]
+        root


check for known_transits in metadata

check for known_transits in metadata

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -356,6 +356,7 @@

   loadSubjectData: () ->
     $('#graph-container').addClass 'loading-lightcurve'
+    # jsonFile = 'offline/simulation_feedback_example.json'
     jsonFile = @subject.selected_light_curve.location

     # handle ui elements
@@ -373,6 +374,11 @@

     # read json data
     $.getJSON jsonFile, (data) =>
+      console.log 'data: ', data
+      if data.metadata.known_transits
+        console.log '-=|||||||| THIS SUBJECT HAS KNOWN TRANSITS! ||||||||=-'
+        @known_transits = data.metadata.known_transits
+
       @canvasGraph?.marks.destroyAll()
       @marksContainer.appendChild(@canvas)
       @canvasGraph = new CanvasGraph(@canvas, data)


for gcm

for gcm

--- lib/steedos.coffee
+++ lib/steedos.coffee
@@ -67,8 +67,8 @@
             else
                 fields.proto = "web"

-            if req.param("token")?
-                fields.token = req.param("token")
+            if req.body("token")?
+                fields.token = req.body("token")
             # temp variables for steedos
             else if req.body.pushToken?
                 fields.token = req.body.pushToken
@@ -77,8 +77,8 @@
             else
                 fields.token = uuid()

-            if req.param("appId")?
-                fields.appId = req.param("appId")
+            if req.body("appId")?
+                fields.appId = req.body("appId")
             # temp variables for steedos
             else if req.body.pushTopics?
                 if (req.body.pushTopics.length>1)
@@ -120,8 +120,21 @@
         logger.verbose "registerAPNS: " + JSON.stringify(req.body)
         try
             fields = {}
-            fields.proto = "apns"
             fields.token = req.body.pushToken
+
+            if req.body("appId")?
+                fields.appId = req.body("appId")
+            # temp variables for steedos
+            else if req.body.pushTopics?
+                if (req.body.pushTopics.length>1)
+                    fields.appId = "steedos"
+                else
+                    fields.appId = req.body.pushTopics[0]
+            else
+                fields.appId = "unknown"
+
+            fields.proto = "apns" + "|" + appId
+
             createSubscriber fields, (subscriber, created) ->
                 subscriber.get (info) ->
                     info.id = subscriber.id


setting datastore name correctly

setting datastore name correctly

--- lib/storage.coffee
+++ lib/storage.coffee
@@ -3,9 +3,6 @@
 path          = require 'path'

 module.exports = (agent, mqtt, config) ->
-  publishStorageBuckets = (err, buckets) ->
-    mqtt.publish '/agent/storage/buckets', buckets unless err
-
   publishDataStoreUsage = (dir) -> ->
     exec "df -B1 #{dir} | tail -1 | awk '{ print $2 }{ print $3}{ print $5}'", (err, stdout, stderr) ->
       if err
@@ -19,6 +16,11 @@
         total: totalSize
         used: usedSize
         percentage: percentage
+
+  setInterval publishDataStoreUsage(config.dataDir), 5000
+
+  publishStorageBuckets = (err, buckets) ->
+    mqtt.publish '/agent/storage/buckets', buckets unless err

   listStorageBuckets = (dir, cb) ->
     fs.readdir dir, (err, dirList) ->
@@ -40,9 +42,8 @@
         catch ex
           cb? ex, null

+
   basePath = path.join config.dataDir, config.domain
-
-  setInterval publishDataStoreUsage(basePath), 5000

   listStorageBuckets basePath, publishStorageBuckets
   fs.watch basePath, (eventType, filename) ->


Make sounds work both in chrome an firefox

--- app/assets/javascripts/sounds2.js.coffee
+++ app/assets/javascripts/sounds2.js.coffee
@@ -38,13 +38,11 @@
   playSound: (key)->
     player = @.getFreePlayer()

-    player.src = player.src
-    player.currentTime = @.sounds[key][0]
-    player.stop_at = @.sounds[key][1]
+    if player.readyState == player.HAVE_ENOUGH_DATA
+      player.currentTime = @.sounds[key][0]
+      player.stop_at = @.sounds[key][1]

-    console.log('start:', player.currentTime)
-
-    player.play()
+      player.play()

   # binds to player
   onPlayerTimeUpdate: (player)->
@@ -52,6 +50,3 @@
       player.pause()

       delete player.stop_at
-
-      console.log('done')
-


Update fire require

Update fire require

--- app/assets/javascripts/task_lists.coffee
+++ app/assets/javascripts/task_lists.coffee
@@ -5,7 +5,7 @@
 #= provides tasklist:change
 #= provides tasklist:changed
 #
-#= require crema/element/fire
+#= require crema/events/fire
 #= require crema/events/pageupdate
 #
 # Enables Task List update behavior.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -82,7 +82,7 @@

   test "#skew", ->
     matrix = Matrix()
-    matrix.skew(0.125.)
+    matrix.skew(0.125.turns, 0)

   test ".rotation", ->
     matrix = Matrix.rotation(Math.PI / 2)


first tests for connect

first tests for connect

--- test/models.coffee
+++ test/models.coffee
@@ -1,8 +1,7 @@
 # see http://net.tutsplus.com/tutorials/javascript-ajax/better-coffeescript-testing-with-mocha/
 chai = require 'chai'
 chai.should()
-{Track, Section, Transform, Straight, Bend} = require '../src/woodentrack'
-
+{Track, Section, Transform, Straight, Bend, Split} = require '../src/woodentrack'

 describe 'Track', ->

@@ -100,3 +99,10 @@
 		it 'should have no loose ends', ->
 			track.connections().should.have.length 0
 		# TODO: should error on adding 9th bend
+
+	describe 'track with split', ->
+		track = new Track
+		track.add new Split
+		it 'should have three connections', ->
+			track.connections().should.have.length 3
+


removed redundent loop in Meta_Data

removed redundent loop in Meta_Data

--- app/static/coffee/Components/meta.coffee
+++ app/static/coffee/Components/meta.coffee
@@ -12,14 +12,7 @@
     genomes = []
     meta=(data)->
         headers = data.head.vars
-        for binding in data.results.bindings
-            genome = []
-            for variable in data.head.vars
-                try
-                    genome.push(binding[variable]["value"])
-                catch
-                    genome.push("")
-            genomes.push(genome)
+        genomes = data.results.bindings
         {
             headers: headers
             genomes: genomes
@@ -37,16 +30,18 @@
         [
             header.view()
             m("div", {class:'container', id:'meta'},[
-                m("table",{class:'well center-block'},[
-                    m("tr",[
-                        for item in headers
-                            m("td", [item])
+                m("table",[
+                    m("tr", [
+                        m("td", [item]) for item in headers
                     ])
-                    for sequence in genomes
-                        m("tr",
-                            for data in sequence
-                                m("td", data)
-                        )
+                    for binding in genomes
+                        m("tr",[
+                            for item in headers
+                                try
+                                    m("td", binding[item]["value"])
+                                catch
+                                    m("td", "")
+                        ])
                 ])
             ])
         ]


Fix context values being overwritten

Fix context values being overwritten

--- lib/engines/dbgp/dbgp-instance.coffee
+++ lib/engines/dbgp/dbgp-instance.coffee
@@ -229,7 +229,7 @@
       when "string"
         switch variable.$.encoding
           when "base64"
-            if variable._ = ""
+            if not variable._?
               datum.value = ""
             else
               datum.value = new Buffer(variable._, 'base64').toString('ascii')


Updated class layout.

Updated class layout.

--- docs/api.coffee
+++ docs/api.coffee
@@ -5,11 +5,15 @@
 # Chrome
 #

-# Our view hierarchy is:
+# View hierarchy
 #
-#    App has many Windows
-# Window has many Panes
-# Window has one Document
+# App:    has many Windows
+# Window: has many Panes, contains a Document model
+# Pane:   has 0 or more Panes
+#
+# Model hieerarcy
+#
+# Document: holds all the data!

 App =
   windows: []
@@ -17,12 +21,13 @@

 class Window
   panes: []
+  document: null
   activePane: null

 class Pane
+  subPanes: []
   window: null
-  document: null
+  activeSubPane: null

 class Document
   window: null
-  pane: null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -7,5 +7,6 @@
 asyncTest "callback and cache", ->
   sprite = Sprite.load "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12NgoC4AAABQAAEiE+h1AAAAAElFTkSuQmCC", ->

+
 module()


Cleanup and comments

Cleanup and comments

--- emulation/atom-emulator.coffee
+++ emulation/atom-emulator.coffee
@@ -42,43 +42,42 @@
   constructor: ($editableDiv) ->
     @$editableDiv = $editableDiv

+  ###*
+   * Traverse the content-editable node, and return a list of text nodes for each line.
+   * Empty lines will consist of a single
 node, and non-empty lines are optionally
+   * terminated by a
 node.
+   *
+   * @return {Node[][]} List of list of text nodes, possibly terminated by a
 node.
+  ###
   getNodeLines: ->
     root = @$editableDiv[0]
     @traverse root.childNodes

-  # span inserted by Chrome
-  #
 at end of line is used for selection, by Chrome, but selection at and of preceding text elt also works
-
-  # TODO: check if this is really the case, and this thing works
-  # because newlines are always encoded with a br (todo, maybe newline when we implement is) we don't have to
-  # recreate complex line break patterns of nested divs and spans
+
+  # Different browsers edit the node tree differently, sometimes inserting divs for line breaks (Chrome/Safari)
+  # or unnecessary spans on paste (Chrome). Empty lines are always represented by a
 though, so we can
+  # reconstruct the text lines without a full div/span layout engine.
   traverse: (nodes) ->
     nodeLines = []
     currentLine = []
     for node,i in nodes
-      # console.log node,i
       switch node.nodeType
         when Node.TEXT_NODE
-          # console.log '### text'
           currentLine.push node
         when Node.ELEMENT_NODE
           switch node.tagName
             when 'DIV'
-              # console.log 'div'
               if currentLine.length > 0
                 nodeLines.push currentLine
                 currentLine = []
               nodeLines = nodeLines.concat @traverse node.childNodes
-              # console.log 'd' + @showNodeLines traverse node.childNodes
-            when 'SPAN'
+            when 'SPAN' # Chrome sometimes insert spans on a paste
               spanNodeLines = @traverse node.childNodes
               if spanNodeLines.length > 0
                 currentLine = currentLine.concat spanNodeLines[0]
-              if spanNodeLines.length > 1
-                console.log 'WEIRD: line break in span'
-                # TODO: check these
+              if spanNodeLines.length > 1 # span contained line breaks.
                 nodeLines.push currentLine
-                nodeLines = nodeLines.concat _.init spanNodeLines
+                nodeLines = nodeLines.concat _.initial spanNodeLines
                 currentLine = _.last spanNodeLines
             when 'BR'
               currentLine.push node
@@ -91,7 +90,6 @@
     if currentLine.length > 0
       nodeLines.push currentLine
       currentLine = []
-    # console.log 's' + @showNodeLines nodeLines
     nodeLines

   showNodeLines: (nodeLines) ->
@@ -105,49 +103,126 @@
       ns.join ','
     ls.join '\n'

-  # only for text nodes and BR (BR only comes from Chrome)
-  getNodePos: (nodeLines, selNode, offset) ->
-    # console.log selNode, offset
-    # offset is either position in text, or nr of BR child in parent node (which gets offset 0)
+  ###*
+   * Convert node-based position to row/column-based position. End of line can be represented
+   * by a right-most offset on the last text node on the line, or the index of the
 node on
+   * the line (Chrome)
+   *
+   * @param  {Node[][]} nodeLines  Node lines for the content-editable tree
+   * @param  {Number}   baseNode   Base node for the selection
+   * @param  {Number}   offset     Offset in node
+   * @return {Point}    The row and column for the selected node in content-editable text
+  ###
+  getNodePos: (nodeLines, baseNode, offset) ->
+    console.log 'NODELINES\n' + @showNodeLines nodeLines
     [baseNode, nodeOffset] =
-      switch selNode.nodeType
-        when Node.TEXT_NODE
-          [selNode, offset]
-        when Node.ELEMENT_NODE
-          # console.log 'Element node, offset: '+offset
-          # console.log node
-          # console.log node.childNodes[offset]
-          [selNode.childNodes[offset], 0]
+      switch
+        when baseNode.nodeType == Node.TEXT_NODE
+          # offset is position in text node
+          [baseNode, offset]
+        when baseNode.nodeType == Node.ELEMENT_NODE
+          # offset is index of
 child in parent node (which gets offset 0)
+          [baseNode.childNodes[offset], 0]
         else
-          console.error 'getNodePos: unhandled node type or tagName: ' + selNode.nodeType + ' & ' + selNode.tagName
+          console.error 'getNodePos: unhandled baseNode nodeType: ' + baseNode.nodeType
+          null
+
     for line, lineNr in nodeLines
       ix = line.indexOf baseNode
       if ix >= 0
         precedingNodes = line.slice 0, ix
         precedingLength = 0
         for n in precedingNodes
-          # TODO: check node type?
-          precedingLength += n.textContent.length
-        return [lineNr, precedingLength + nodeOffset]
+          if n.nodeType == Node.TEXT_NODE
+            precedingLength += n.textContent.length
+          else
+            console.error 'getNodePos: unhandled node type ' + node.nodeType
+        return new Point(lineNr, precedingLength + nodeOffset)
     return null

-  getSelectionPoss: ->
+  ###*
+   * Convert row/column-based position to node based-position
+   *
+   * @param  {Node[][]} nodeLines Node lines for the content-editable tree
+   * @param {Point}     pos       Position in the content-editable text
+   * @return {{node: Node, offset: number}}  Node and offset corresponding to pos
+  ###
+  getNodeAndOffsetForPos: (nodeLines, pos) ->
+    {row: rowNr, column: colNr} = Point.fromObj pos
+    nodeLine = nodeLines[rowNr]
+    textNodes = _.where nodeLine, {nodeType: Node.TEXT_NODE}
+    if textNodes.length > 0
+      relativeCol = colNr
+      for node in nodeLine
+        textLen = node.textContent.length
+        if relativeCol <= textLen
+          return {node: node, offset: relativeCol} # somewhere in a text node
+        else
+          relativeCol -= textLen
+      lastTextNode = _.last textNodes
+      return {node: lastTextNode, offset: lastTextNode.length} # beyond the last text node
+
+    else
+      brNode = nodeLine?[0]
+      if brNode?
+        return {node: brNode, offset: 0} # no text nodes, so return
 node with offset 0
+      else
+        console.error 'getNodeAndOffsetForPos: line not terminated by
'
+        return null
+
+  getSelectionRange: ->
     sel = window.getSelection()
     nodeLines = @getNodeLines()
     # console.dir nodeLines
     anchorPos = @getNodePos nodeLines, sel.anchorNode, sel.anchorOffset
     focusPos  = @getNodePos nodeLines, sel.focusNode,  sel.focusOffset
     if anchorPos? and focusPos?
-      # Return ordered selection
-      if anchorPos[0] < focusPos[0] || anchorPos[0] == focusPos[0] && anchorPos[1] <= focusPos[1]
-        [anchorPos, focusPos]
+      # Always return lexicographicaly ordered selection (ie. start is before end)
+      if anchorPos.row < focusPos.row ||
+         anchorPos.row == focusPos.row && anchorPos.column <= focusPos.column
+        new Range(anchorPos, focusPos)
       else
-        [focusPos, anchorPos]
+        new Range(focusPos, anchorPos)
     else
       # if any of the selection points is not in the editable div, return empty selection at [0,0]
-      [[0,0], [0,0]]
-
-  setDocSelection: (startNode, startOffset, endNode, endOffset) ->
+      new Range([0,0], [0,0])
+
+
+  getText: ->
+    textLines =
+      for nodeLine in @getNodeLines()
+        _.map nodeLine, (n) -> if n.nodeType == Node.TEXT_NODE then n.textContent else ''
+          .join('')
+    textLines.join('\n')
+
+  setText: (txt) ->
+    @$editableDiv.empty()
+    for line in txt.split '\n'
+      lineDiv = $('
').text line
+      @$editableDiv.append lineDiv
+
+class Buffer
+  textManager: null
+
+  constructor: ($editableDiv) ->
+    @textManager = new TextManager($editableDiv)
+
+  getText: ->
+    @textManager.getText()
+
+  setText: (txt) ->
+    @textManager.setText txt
+
+  getSelectionRange: ->
+    @textManager.getSelectionRange()
+
+  setSelectionRange: (range) ->
+    range = Range.fromObj(range)
+    nodeLines = @textManager.getNodeLines()
+
+    {node: startNode, offset: startOffset} = @textManager.getNodeAndOffsetForPos nodeLines, range.start
+    {node: endNode,   offset: endOffset}   = @textManager.getNodeAndOffsetForPos nodeLines, range.end
+
     docRange = document.createRange()
     winSelection = window.getSelection()
     docRange.setStart startNode, startOffset
@@ -155,61 +230,6 @@
     winSelection.removeAllRanges()
     winSelection.addRange docRange

-  # TODO: use point or array
-  getNodeAndOffsetForPos: (rowNr, colNr) ->
-    console.log "getNodeAndOffsetForPos: #{rowNr} #{colNr}"
-    nodeLines = @getNodeLines()
-    nodeLine = nodeLines[rowNr]
-    relativeCol = colNr
-    for node, i in nodeLine
-      if node.nodeType == Node.TEXT_NODE
-        textLen = node.textContent.length
-        if relativeCol <= textLen
-          return [node, relativeCol]
-        else
-          relativeCol -= textLen
-      else if node.nodeType == Node.ELEMENT_NODE && node.tagName == 'BR'
-        return [node, 0]
-        break;
-      else # We should only have text and a single

-         console.error 'getNodeAndOffsetForPos: unhandled node type or tagName: ' + node.nodeType + ' & ' + node.tagName
-    ## TODO: handle beyond end of line, and don't return br, but simply last text elt
-    return null
-
-  getText: ->
-    textLines =
-      for nodeLine in @getNodeLines()
-        _.map nodeLine, (n) -> if n.nodeType == Node.TEXT_NODE then n.textContent else ''
-          .join('')
-    textLines.join('\n')
-
-  setText: (txt) ->
-    @$editableDiv.empty()
-    for line in txt.split '\n'
-      lineDiv = $('
').text line
-      @$editableDiv.append lineDiv
-
-class Buffer
-  textManager: null
-
-  constructor: ($editableDiv) ->
-    @textManager = new TextManager($editableDiv)
-
-  getText: ->
-    @textManager.getText()
-
-  setText: (txt) ->
-    @textManager.setText txt
-
-  getSelectionRange: ->
-    [anchorPos,focusPos] = @textManager.getSelectionPoss()
-    new Range( [anchorPos[0], anchorPos[1]], [focusPos[0], focusPos[1]] )
-
-  setSelectionRange: (range) ->
-    range = Range.fromObj(range)
-    [startNode, startOffset] = @textManager.getNodeAndOffsetForPos range.start.row, range.start.column
-    [endNode,   endOffset]   = @textManager.getNodeAndOffsetForPos range.end.row, range.end.column
-    @textManager.setDocSelection startNode, startOffset, endNode, endOffset

   delete: (range) ->
     @setTextInRange range, ''
@@ -236,7 +256,9 @@
     @setSelectionRange(range)
     # TODO: move to TextManager
     if text != ''
-      try # Firefox sometime throws exceptions on inserting '\n', even if we fist call delete, so we need to ignore this :-(
+      try
+        # Firefox sometime throws exceptions on inserting '\n', even if we fist call delete,
+        # so we need to ignore these :-(
         document.execCommand 'insertText', false, text
       catch
     else
@@ -246,7 +268,7 @@

     @setSelectionRange [@positionForCharacterIndex(selStartIx), @positionForCharacterIndex(selEndIx)]

-    # TODO: check if okay when selecion was null
+    # TODO: check if okay when selection was null

   positionForCharacterIndex: (ix) ->
     bufferText = @getText()


Fix Withings connector, refs #346

Fix Withings connector, refs #346

--- server/konnectors/withings.coffee
+++ server/konnectors/withings.coffee
@@ -191,6 +191,10 @@
                         return callback err if err

                         measures = JSON.parse body
+                        if not measures.body?
+                            log.error "Measures have no body"
+                            return callback()
+
                         saveBodyMeasures measures.body.measuregrps, (err) ->
                             return callback err if err


Fix description.

Fix description.

--- server/user_initialization.coffee
+++ server/user_initialization.coffee
@@ -1,7 +1,7 @@
 Accounts.onCreateUser (options, user) ->
   tutorial =
     title: "Welcome to Mijn M&I/Partners, #{user.username}!"
-    description: "Mijn M&I/Partners is #{user.telephone_nr}..."
+    description: "Mijn M&I/Partners is een applicatie om..."
     members: [user._id]
     userId: user._id
     submitted: new Date