Add safety when closing the cursor

Add safety when closing the cursor

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -854,7 +854,7 @@
                             return false
                     else #  Else if it's not the last query, we just execute the next query
                         try
-                            if @cursor?
+                            if @cursor?.close?
                                 @cursor.close()

                             @cursor = eval(@queries[@current_query_index])
@@ -865,7 +865,8 @@

                     return false
                 else
-                    @cursor.close()
+                    if @cursor?.close?
+                        @cursor.close()
                     return false
             return callback_multiple_queries
         # Function that execute the query
@@ -1086,7 +1087,7 @@
                 window.conn.close()
             catch err
                 #console.log 'Could not destroy connection'
-            if @cursor?
+            if @cursor?.close?
                 @cursor.close()
             clearTimeout @timeout


Ajax input fields support inside has_many forms

--- app/assets/javascripts/activeadmin-ajax_filter.js.coffee
+++ app/assets/javascripts/activeadmin-ajax_filter.js.coffee
@@ -1,91 +1,99 @@
 $ ->
-  $('.filter_ajax_select select, .ajax_select select').each (_, select) ->
-    select = $(select)
-    valueField = select.data('value-field')
-    searchFields = select.data('search-fields').split(' ')
-    staticRansack = select.data('static-ransack')
+  apply_filter_ajax_select = () ->
+    $('.filter_ajax_select select, .ajax_select select').each (_, select) ->
+      select = $(select)
+      valueField = select.data('value-field')
+      searchFields = select.data('search-fields').split(' ')
+      staticRansack = select.data('static-ransack')

-    ajaxFields = select.data('ajax-search-fields')
-    if ajaxFields
-      ajaxFields = ajaxFields.split(' ')
-    else
-      ajaxFields = []
+      ajaxFields = select.data('ajax-search-fields')
+      if ajaxFields
+        ajaxFields = ajaxFields.split(' ')
+      else
+        ajaxFields = []

-    ordering = select.data('ordering')
-    url = select.data('url')
+      ordering = select.data('ordering')
+      url = select.data('url')

-    loadOptions = (q, callback) ->
-      $.ajax
-        url: url
-        type: 'GET'
-        dataType: 'json'
-        data:
-          q: q
-          limit: select.data('limit')
-          order: ordering
-        error: ->
-          callback()
-        success: (res) ->
-          callback(res)
+      loadOptions = (q, callback) ->
+        $.ajax
+          url: url
+          type: 'GET'
+          dataType: 'json'
+          data:
+            q: q
+            limit: select.data('limit')
+            order: ordering
+          error: ->
+            callback()
+          success: (res) ->
+            callback(res)

-    relatedInput = (field) ->
-      $("[name*=#{field}]", select.parents('form'))
+      relatedInput = (field) ->
+        $("[name*=#{field}]", select.parents('form'))

-    select.selectize
-      valueField: valueField
-      labelField: searchFields[0]
-      searchField: searchFields
-      sortField: ordering.split(',').map (clause)->
-        c = clause.trim().split(' ')
-        { field: c[0], direction: c[1] }
-      options: []
-      create: false
-      render:
-        option: (item, escape) ->
-          html = searchFields.map (field, index)->
-            value = escape(item[field])
+      select.selectize
+        valueField: valueField
+        labelField: searchFields[0]
+        searchField: searchFields
+        sortField: ordering.split(',').map (clause)->
+          c = clause.trim().split(' ')
+          { field: c[0], direction: c[1] }
+        options: []
+        create: false
+        render:
+          option: (item, escape) ->
+            html = searchFields.map (field, index)->
+              value = escape(item[field])

-            if index == 0
-              klass = 'primary'
-            else
-              klass = 'secondary'
+              if index == 0
+                klass = 'primary'
+              else
+                klass = 'secondary'

-            "#{value}"
+              "#{value}"

-          "
#{html.join('')}
"
+            "
#{html.join('')}
"

-      load: (query, callback) ->
-        if query.length
-          q = {}
-          q[select.data('ransack')] = query
+        load: (query, callback) ->
+          if query.length
+            q = {}
+            q[select.data('ransack')] = query
+
+            ajaxFields.forEach (field) ->
+              q["#{field}_eq"] = relatedInput(field).val()
+              # clear cache because it wrong with changing values of ajaxFields
+              select.loadedSearches = {}
+
+            for ransack, value of staticRansack
+              q[ransack] = value
+
+            loadOptions(q, callback)
+          else
+            callback()
+
+        onInitialize: ->
+          selectize = this
+          selectedValue = select.data('selected-value')
+          selectedRansack = "#{valueField}_eq"
+
+          if selectedValue
+            q = {}
+            q[selectedRansack] = selectedValue
+
+            loadOptions(q, (res)->
+              if res.length
+                selectize.addOption(res[0])
+                selectize.addItem(res[0][valueField])
+            )

           ajaxFields.forEach (field) ->
-            q["#{field}_eq"] = relatedInput(field).val()
-            # clear cache because it wrong with changing values of ajaxFields
-            select.loadedSearches = {}
+            relatedInput(field).change ->
+              selectize.clearOptions()

-          for ransack, value of staticRansack
-            q[ransack] = value
+  # apply ajax filter to all static inputs
+  apply_filter_ajax_select()

-          loadOptions(q, callback)
-        else
-          callback()
-
-      onInitialize: ->
-        selectize = this
-        selectedValue = select.data('selected-value')
-        selectedRansack = "#{valueField}_eq"
-
-        if selectedValue
-          q = {}
-          q[selectedRansack] = selectedValue
-
-          loadOptions(q, (res)->
-            if res.length
-              selectize.addOption(res[0])
-              selectize.addItem(res[0][valueField])
-          )
-
-        ajaxFields.forEach (field) ->
-          relatedInput(field).change ->
-            selectize.clearOptions()+  # apply ajax filter on inputs inside has_many entries
+  $("form.formtastic .has_many_add").click ->
+    setTimeout((-> apply_filter_ajax_select()), 0)


Better code

Better code

--- app/assets/javascripts/backend/financial_years.js.coffee
+++ app/assets/javascripts/backend/financial_years.js.coffee
@@ -8,7 +8,6 @@

   $(document).on 'change', '#confirm-revised-accounts', ->
     lock_btn = $('#lock-btn')
-    lock_btn.attr('disabled', false) if this.checked == true
-    lock_btn.attr('disabled', true) if this.checked == false
+    lock_btn.attr('disabled', !this.checked)

 ) ekylibre, jQuery


throttle setting state onscroll of timeline

--- app/assets/javascripts/components/timeline/timeline.cjsx
+++ app/assets/javascripts/components/timeline/timeline.cjsx
@@ -60,7 +60,7 @@
     scrollHeight = (document.documentElement && document.documentElement.scrollHeight) || document.body.scrollHeight
     (scrollTop + window.innerHeight) >= scrollHeight

-  _handleScroll: ->
+  _handleScroll: _.throttle ->
     @setState unscrolled: false
     scrollTop = window.scrollTop || document.body.scrollTop
     bodyTop = document.body.getBoundingClientRect().top
@@ -76,12 +76,13 @@
           navItems[i]?.classList.add('is-current')
         else
           navItems[navItems.length - 1]?.classList.add('is-current')
+  , 150

   componentDidMount: ->
     window.addEventListener 'scroll', @_handleScroll

   componentDidUnMount: ->
-    window.removeEventListener 'scroll'
+    window.removeEventListener 'scroll', @_handleScroll

   render: ->
     unless @props.weeks?.length


Coffeescript handler for the "remove feed from folders" event retrieves the folder id in which the unsubscribed feed was by looking at a global variable, instead of inferring from attributes in the sidebar.

This makes a function redundant; it's been deleted.

--- app/assets/javascripts/feeds/folder_management.js.coffee
+++ app/assets/javascripts/feeds/folder_management.js.coffee
@@ -41,8 +41,7 @@
     remove_folder_result = (data, status, xhr) ->
       if xhr.status == 205
         # If the return status is 205, remove the folder (there are no more feeds in it)
-        old_folder_id = find_feed_folder Openreader.current_feed_id
-        Openreader.remove_folder old_folder_id
+        Openreader.remove_folder Openreader.current_folder_id
       else
         # If the return status is 204, remove the feed from the folder but not the folder itself (it has more feeds)
         remove_feed_from_folders Openreader.current_feed_id
@@ -115,12 +114,6 @@
       $(this).remove() if $(this).parent().attr("id") != "feeds-all"

   #-------------------------------------------------------
-  # Find out the folder to which a feed currently belongs
-  #-------------------------------------------------------
-  find_feed_folder = (feed_id) ->
-    return $("#sidebar a[data-sidebar-feed][data-feed-id='#{feed_id}']").attr "data-folder-id"
-
-  #-------------------------------------------------------
   # Add a new folder to the sidebar and the dropdown
   #-------------------------------------------------------
   add_folder = (folder_data) ->


Revert setting userList on server start

Revert setting userList on server start

--- server/server.coffee
+++ server/server.coffee
@@ -75,28 +75,6 @@

 port = (process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || 3000)

-setUserLists = () ->
-    process = (resultOrSubmit, userList) ->
-        if resultOrSubmit.userList and resultOrSubmit.userList != "unknown"
-            return
-        logger.info "Set userList for result/submit #{resultOrSubmit._id}, was #{resultOrSubmit.userList}, will be #{userList}"
-        await resultOrSubmit.update({$set: {"userList": userList}})
-
-    users = await User.find({userList: {$ne: "unknown"}})
-    logger.info "Found #{users.length} users"
-    promises = []
-    for user, i in users
-        logger.info "Processing user #{i} of #{users.length}"
-        results = await Result.findByUser(user._id)
-        for r in results
-            promises.push(process(r, user.userList))
-        submits = await Submit.findByUser(user._id)
-        for s in submits
-            promises.push(process(s, user.userList))
-        await Promise.all(promises)
-        promises = []
-    logger.info "Done setting userlists!"
-
 start = () ->
     try
         await logger.info("My ip is " + JSON.parse(await download 'https://api.ipify.org/?format=json')["ip"])
@@ -111,7 +89,6 @@
         for id, system of REGISTRY
             system.selfTest()
         await sleep(30 * 1000)  # wait for a bit to make sure previous deployment has been stopped
-        setUserLists()
         if not (process.env["INSTANCE_NUMBER"]?) or (process.env["INSTANCE_NUMBER"] == "0")
             logger.info("Starting jobs ", process.env["INSTANCE_NUMBER"])
             jobs.map((job) -> job.start())


Implement answerSecurityQuestions and fillDateRange directly.

--- application/chrome/content/wesabe/download/Player.coffee
+++ application/chrome/content/wesabe/download/Player.coffee
@@ -2,6 +2,8 @@

 extend         = require 'lang/extend'
 date           = require 'lang/date'
+array          = require 'lang/array'
+dateForElement = (require 'dom/date').forElement
 {trim}         = require 'lang/string'
 func           = require 'lang/func'
 type           = require 'lang/type'
@@ -241,6 +243,12 @@

     return retval

+  @::__defineGetter__ 'elements', ->
+    @constructor.elements
+
+  @::__defineGetter__ 'e', ->
+    @elements
+
   resume: (answers) ->
     if type.isArray answers
       for {key, value} in answers
@@ -316,21 +324,24 @@
           @onDownloadSuccessful browser, page

-  #
-  # Answers whatever security questions are on the page by
+  # Public: Answers whatever security questions are on the page by
   # using the xpaths given in e.security.
   #
   # NOTE: Called with magic scope!
   #
   answerSecurityQuestions: ->
-
+    @answerSecurityQuestionsWithoutMagicScope browser ? @browser, page ? @page
+
+  # Internal: Answers whatever security questions are on the page by
+  # using the xpaths given in e.security.
+  #
+  # browser - The context Browser.
+  # page - The context Page.
+  answerSecurityQuestionsWithoutMagicScope: (browser, page) ->
     # these are here because this function is called with magic scope
     # and therefore won't see the variables we defined above
-    {trim} = require 'lang/string'
-    privacy = require 'util/privacy'
-
-    questions = page.select e.security.questions
-    qanswers  = page.select e.security.answers
+    questions = page.select @e.security.questions
+    qanswers  = page.select @e.security.answers

     if questions.length isnt qanswers.length
       logger.error "Found ", questions.length, " security questions, but ",
@@ -350,13 +361,13 @@

     data = questions: []
     for question, i in questions
-      answer   = answers[question]
+      answer   = @answers[question]
       element  = qanswers[i]

       if answer
         page.fill element, answer
       else
-        log.debug "element = ", element, " -- element.type = ", element.type
+        logger.debug "element = ", element, " -- element.type = ", element.type
         data.questions.push
           key: question
           label: question
@@ -364,16 +375,16 @@
           type: privacy.untaint(element.type) or "text"

     if data.questions.length
-      job.suspend 'suspended.missing-answer.auth.security', data
+      @job.suspend 'suspended.missing-answer.auth.security', data
       return false

-    job.update 'auth.security'
+    @job.update 'auth.security'

     # choose to bypass the security questions if we can
-    page.check e.security.setCookieCheckbox if e.security.setCookieCheckbox
-    page.fill e.security.setCookieSelect, e.security.setCookieOption if e.security.setCookieSelect
+    page.check @e.security.setCookieCheckbox if @e.security.setCookieCheckbox
+    page.fill @e.security.setCookieSelect, @e.security.setCookieOption if @e.security.setCookieSelect
     # submit the form
-    page.click e.security.continueButton
+    page.click @e.security.continueButton

     return true

@@ -390,12 +401,13 @@
   # @public
   #
   fillDateRange: ->
-    date = require 'lang/date'
-    dateForElement = (require 'dom/date').forElement
-    type = require 'lang/type'
-    formatString = e.download.date.format or 'MM/dd/yyyy'
-
-    opts   = e.download.date
+    @fillDateRangeWithoutMagicScope @browser, @page
+
+  # Internal: Fills out the date form fields for a download.
+  fillDateRangeWithoutMagicScope: (browser, page) ->
+    formatString = @elements.download.date.format or 'MM/dd/yyyy'
+
+    opts   = @elements.download.date
     fromEl = privacy.untaint page.find(opts.from)
     toEl   = privacy.untaint page.find(opts.to)

@@ -577,6 +589,7 @@
       bind: (args...) => Pathway.bind(args...)
       logger: log
       log: log
+      isMagicScope: on
     }, scope or {}), args


groups: default readme synced w/ backend and frontend

--- client/landing/app/Applications/Groups.kdapplication/Views/readmeview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/readmeview.coffee
@@ -59,11 +59,24 @@
     @readmeSaveButton.show()
     @readmeCancelLink.show()

+  defaultGroupReadme = (title)->
+    """
+
Hello!

+
Welcome to the #{title} group on Koding.

+
Talk.

+
Looking for people who share your interest? You are in the right place. And you can discuss your ideas, questions and problems with them easily.

+
Share.

+
Here you will be able to find and share interesting content. Experts share their wisdom through links or tutorials, professionals answer the questions of those who want to learn.

+
Collaborate.

+
You will be able to share your code, thoughts and designs with like-minded enthusiasts, discussing and improving it with a community dedicated to improving each other's work.

+
Go ahead, the members of #{title} are waiting for you.

+    """
+
   viewAppended:->
     group = @getData()
     group.fetchReadme (err, readme)=>
-      unless err
-        partial = readme?.content or "This group does not have any associated readme data yet."
+      if not err and readme?
+        partial = readme.content or getDefaultGroupReadme group.title
         group.canEditGroup (err, allowed)=>
           @readmeEditButton.show() if allowed
       else


Fix home page when not logged in

Fix home page when not logged in

--- assets/javascripts/routers/posts.js.coffee
+++ assets/javascripts/routers/posts.js.coffee
@@ -9,7 +9,7 @@
     "posts/:post_id" : "conversation"

   index: =>
-    unless TentStatus.config.current_entity.hostname == TentStatus.config.domain_entity.hostname
+    if !TentStatus.authenticated or (TentStatus.config.current_entity.hostname != TentStatus.config.domain_entity.hostname)
       @profile(encodeURIComponent(TentStatus.domain_entity))
       return
     @view = new TentStatus.Views.Posts


improve caller name cleanup

improve caller name cleanup

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -25,9 +25,9 @@
   #       "caller" can appear as "asterisk":
   #
   sanitizeCallerName: (call) ->
-    name = app.getAgentFrom call.caller.replace('SIP/', '').replace(/@.+$/, '')
-    name = app.getAgentFrom(call.visibleNameCaller) if name == 'asterisk'
-    name
+    name = call.caller
+    name = call.visibleNameCaller if name.match(/asterisk/)
+    app.getAgentFrom(name.match(/\d+/)?[0] || name)

   setCurrentAgentForm: (el) ->


Added hydrogen gqueries to sankey.coffee (chart 123)

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -107,6 +107,7 @@
           {id: 'nuclear',                      column: 0, label: "uranium",                color: '#ff7f0e'},
           {id: 'biomass_products',             column: 0, label: "biomass_waste",          color: '#2ca02c'},
           {id: 'import',                       column: 0, label: "electricity_import",     color: '#1f77b4'},
+          {id: 'hydrogen',                     column: 0, label: "hydrogen",               color: '#416B86'},
           {id: 'electricity_prod',             column: 1, label: "electricity_production", color: '#1f77b4'},
           {id: 'chps',                         column: 1, label: "CHPs",                   color: '#9467bd'},
           {id: 'households',                   column: 2, label: "households"},
@@ -125,12 +126,14 @@
           {left: 'natural_gas',                   right: 'electricity_prod', gquery: 'natural_gas_to_electricity_prod_in_sankey', color: '#7f7f7f'},
           {left: 'nuclear',                       right: 'electricity_prod', gquery: 'nuclear_to_electricity_prod_in_sankey', color: '#ff7f0e'},
           {left: 'biomass_products',              right: 'electricity_prod', gquery: 'biomass_products_to_electricity_prod_in_sankey', color: '#2ca02c'},
+          {left: 'hydrogen',                      right: 'electricity_prod', gquery: 'hydrogen_to_electricity_prod_in_sankey', color: '#416B86'},
           {left: 'coal_and_derivatives',          right: 'chps',             gquery: 'coal_and_derivatives_to_chps_prod_in_sankey', color: '#252525'},
           {left: 'oil_and_derivatives',           right: 'chps',             gquery: 'oil_and_derivatives_to_chps_prod_in_sankey', color: '#8c564b'},
           {left: 'geo_solar_wind_water_ambient',  right: 'chps',             gquery: 'geo_solar_wind_water_ambient_to_chps_prod_in_sankey', color: '#2ca02c'},
           {left: 'natural_gas',                   right: 'chps',             gquery: 'natural_gas_to_chps_prod_in_sankey', color: '#7f7f7f'},
           {left: 'nuclear',                       right: 'chps',             gquery: 'nuclear_to_chps_prod_in_sankey', color: '#ff7f0e'},
           {left: 'biomass_products',              right: 'chps',             gquery: 'biomass_products_to_chps_prod_in_sankey', color: '#2ca02c'},
+          {left: 'hydrogen',                      right: 'chps',             gquery: 'hydrogen_to_chps_prod_in_sankey', color: '#416B86'},
           {left: 'coal_and_derivatives',          right: 'households',       gquery: 'coal_and_derivatives_to_households_in_sankey', color: '#252525'},
           {left: 'coal_and_derivatives',          right: 'buildings',        gquery: 'coal_and_derivatives_to_buildings_in_sankey', color: '#252525'},
           {left: 'coal_and_derivatives',          right: 'transport',        gquery: 'coal_and_derivatives_to_transport_in_sankey', color: '#252525'},
@@ -149,6 +152,12 @@
           {left: 'geo_solar_wind_water_ambient',  right: 'industry',         gquery: 'geo_solar_wind_water_ambient_to_industry_in_sankey', color: '#2ca02c'},
           {left: 'geo_solar_wind_water_ambient',  right: 'agriculture',      gquery: 'geo_solar_wind_water_ambient_to_agriculture_in_sankey', color: '#2ca02c'},
           {left: 'geo_solar_wind_water_ambient',  right: 'other',            gquery: 'geo_solar_wind_water_ambient_to_other_in_sankey', color: '#2ca02c'},
+          {left: 'hydrogen',                      right: 'households',       gquery: 'hydrogen_to_households_in_sankey', color: '#416B86'},
+          {left: 'hydrogen',                      right: 'buildings',        gquery: 'hydrogen_to_buildings_in_sankey', color: '#416B86'},
+          {left: 'hydrogen',                      right: 'transport',        gquery: 'hydrogen_to_transport_in_sankey', color: '#416B86'},
+          {left: 'hydrogen',                      right: 'industry',         gquery: 'hydrogen_to_industry_in_sankey', color: '#416B86'},
+          {left: 'hydrogen',                      right: 'agriculture',      gquery: 'hydrogen_to_agriculture_in_sankey', color: '#416B86'},
+          {left: 'hydrogen',                      right: 'other',            gquery: 'hydrogen_to_other_in_sankey', color: '#416B86'},
           {left: 'natural_gas',                   right: 'households',       gquery: 'natural_gas_to_households_in_sankey', color: '#7f7f7f'},
           {left: 'natural_gas',                   right: 'buildings',        gquery: 'natural_gas_to_buildings_in_sankey', color: '#7f7f7f'},
           {left: 'natural_gas',                   right: 'transport',        gquery: 'natural_gas_to_transport_in_sankey', color: '#7f7f7f'},


make deploy scroll a bit better

make deploy scroll a bit better

--- app/assets/javascripts/deploy.js.coffee
+++ app/assets/javascripts/deploy.js.coffee
@@ -1,6 +1,7 @@
 class ChunkPoller
   INTERVAL = 1000
   MAX_RETRIES = 15
+  STICKY_SCROLL_TOLERENCE = 200

   @init: ->
     $('code').each ->
@@ -39,10 +40,17 @@
     wasScrolledToBottom = @isScrolledToBottom()
     callback()
     if wasScrolledToBottom
-      @$window.scrollTop(@$body.height())
+      @$window.scrollTop(@codeBottomPosition() - @$window.height() + 50)

   isScrolledToBottom: ->
-    @$body.height() - @$window.height() == window.scrollY
+    @viewportBottomPosition() >= @codeBottomPosition() - STICKY_SCROLL_TOLERENCE and \
+    @viewportBottomPosition() < @codeBottomPosition() + STICKY_SCROLL_TOLERENCE
+
+  viewportBottomPosition: ->
+    window.scrollY + @$window.height()
+
+  codeBottomPosition: ->
+    @$code.position().top + @$code.height()

   start: ->
     setTimeout(@poll, INTERVAL)


change ajax call for add signup and use select2

--- app/assets/javascripts/events.js.coffee
+++ app/assets/javascripts/events.js.coffee
@@ -258,11 +258,11 @@

   # $("#event-signup-add").on("click", "button#add-event-signup-button", ->
   $("#event-roster").on("click", "#event-signup-add button#add-event-signup-button", ->
-    scoutId = $("select#add-event-signup-select").val()
+    userId = $("select#add-event-signup-select").val()
     eventId = $("select#add-event-signup-select").data("event-id")
-    if scoutId
+    if userId
       # console.log "s: #{scoutId}, e: #{eventId}"
-      $.getScript("/event_signups/new.js?scout_id=#{scoutId}&event_id=#{eventId}", (data, textStatus, jqxhr) ->
+      $.getScript("/event_signups/new.js?user_id=#{userId}&event_id=#{eventId}", (data, textStatus, jqxhr) ->
         $("div#event-signup-modal-form").modal()
         )
     )
@@ -279,6 +279,10 @@
     placeholder: 'Select recipients'

   $("select#event_user_ids").select2()
+
+  $("select#add-event-signup-select").select2
+    placeholder: '-- add signup --'
+    allowClear: true


Replace paragraphs containing divs

Replace paragraphs containing divs

--- app/classifier/tasks/survey/editor.cjsx
+++ app/classifier/tasks/survey/editor.cjsx
@@ -134,7 +134,7 @@
           {choice.label}
         }>
           <Markdown content={choice.description} />
-

+

             {for characteristicID in @props.task.characteristicsOrder when choice.characteristics[characteristicID]?.length isnt 0
               characteristic = @props.task.characteristics[characteristicID]

@@ -156,7 +156,7 @@
                   twinLabels.join ', '}

}
-

+

         }


randomise smoke test tag to reduce conflicts

--- app/coffee/HealthCheckController.coffee
+++ app/coffee/HealthCheckController.coffee
@@ -14,7 +14,7 @@
 	check : (callback)->
 		project_id = ObjectId()
 		user_id = ObjectId(settings.tags.healthCheck.user_id)
-		tagName = "smoke-test-tag"
+		tagName = "smoke-test-tag-#{Math.floor(Math.random() * 50)}" # use a random tag name to reduce conflicts
 		request.post {
 			url: buildUrl("/user/#{user_id}/tag"),
 			json:


Remove selection_manager test module

Remove selection_manager test module

--- bokehjs/test/core/selection_manager.coffee
+++ bokehjs/test/core/selection_manager.coffee
@@ -1,176 +0,0 @@
-{expect} = require "chai"
-utils = require "../utils"
-
-{SelectionManager} = utils.require "core/selection_manager"
-
-SomeMarker = utils.require("models/markers/index").CircleX
-NullMarker = utils.require("models/glyphs/index").MultiLine
-{GlyphRenderer} = utils.require("models/renderers/glyph_renderer")
-{GlyphRendererView} = utils.require("models/renderers/glyph_renderer")
-{ColumnDataSource} = utils.require("models/sources/column_data_source")
-{LinearScale} = utils.require("models/scales/linear_scale")
-{Range1d} = utils.require("models/ranges/range1d")
-
-hittest = utils.require "core/hittest"
-empty_selection = hittest.create_hit_test_result()
-
-describe "SelectionManager", ->
-
-  source_normal = {start: 0, end: 10}
-  source_reverse = {start: 10, end: 0}
-  target = {start: 0, end: 100}
-
-  scale_normal = new LinearScale({
-    source_range: new Range1d(source_normal)
-    target_range: new Range1d(target)
-  })
-
-  scale_reverse = new LinearScale({
-    source_range: new Range1d(source_reverse)
-    target_range: new Range1d(target)
-  })
-
-  # The objects defined below to stub out the Plot and PlotView depend on the current interfaces
-  # of Plot and PlotView and the specific implementation of GlyphRenderer and GlyphRendererView
-  # initialization. For example, the GlyphRendererView defines its x and y scale through:
-
-  # @xscale = @plot_view.frame.xscales[@model.x_range_name]
-  # @yscale = @plot_view.frame.yscales[@model.y_range_name]
-
-  # so we put the scales (scale_normal and scale_reverse, defined above)
-  # in the stub objects in the same "location".
-
-  plot_model_stub = {
-    use_map: false
-    lod_factor: 1
-    plot: {lod_factor: 0.1}
-  }
-
-  plot_view_stub_normal = {
-    frame:
-      'xscales':
-        {'default': scale_normal}
-      'yscales':
-        {'default': scale_normal}
-    canvas_view:
-      ctx: {}
-    model:
-      plot_model_stub
-  }
-
-  plot_view_stub_reverse = {
-    frame:
-      'xscales':
-        {'default': scale_reverse}
-      'yscales':
-        {'default': scale_reverse}
-    canvas_view:
-      ctx: {}
-    model:
-      plot_model_stub
-  }
-
-  column_data_source = new ColumnDataSource({
-    data:
-      x: [0, 1, 2, 3, 4]
-      y: [0, 1, 2, 3, 4]
-      z: [5, 6, 7, 8, 9]
-  })
-
-  glyph_renderer = new GlyphRenderer({
-    data_source:  column_data_source
-    glyph:        new SomeMarker({x: {field: "x"}, y: {field: "y"}})
-  })
-
-  glyph_renderer_view_normal = new GlyphRendererView({
-    plot_model: plot_model_stub
-    plot_view: plot_view_stub_normal
-    model: glyph_renderer
-  })
-
-  glyph_renderer_view_reverse = new GlyphRendererView({
-    plot_model: plot_model_stub
-    plot_view: plot_view_stub_reverse
-    model: glyph_renderer
-  })
-
-  glyph_renderer2 = new GlyphRenderer({
-    data_source:  column_data_source
-    glyph:        new SomeMarker({x: {field: "x"}, y: {field: "z"}})
-  })
-
-  glyph_renderer_view2 = new GlyphRendererView({
-    plot_model: plot_model_stub
-    plot_view: plot_view_stub_normal
-    model: glyph_renderer2
-  })
-
-  column_data_source_null = new ColumnDataSource({
-    data:
-      x: [0, 1, 2, 3, 4]
-      y: [0, 1, 2, 3, 4]
-      z: [5, 6, 7, 8, 9]
-  })
-
-  glyph_renderer_null = new GlyphRenderer({
-    data_source:  column_data_source_null
-    glyph:        new NullMarker({xs: {field: "x"}, ys: {field: "y"}})
-  })
-
-  glyph_renderer_view_null = new GlyphRendererView({
-    plot_model: plot_model_stub
-    plot_view: plot_view_stub_normal
-    model: glyph_renderer_null
-  })
-
-  sm = new SelectionManager()
-
-  describe "using rect geometry (like that from box select tool)", ->
-
-    geometry = {
-      type: 'rect'
-      vx0: 0
-      vx1: 100
-      vy0: 0
-      vy1: 100
-    }
-
-    it "should update its data source's selected property", ->
-      sm = new SelectionManager({'source': column_data_source})
-      expect(sm.source.selected).to.deep.equal empty_selection
-      sm.select([glyph_renderer_view_normal], geometry, true)
-      expect(sm.source.selected).to.not.deep.equal empty_selection
-
-    it "should update its selector", ->
-      sm = new SelectionManager({'source': column_data_source})
-      sm.select([glyph_renderer_view_normal], geometry, true)
-      expect(sm.selector).to.not.deep.equal empty_selection
-
-    it "should work when scales are reversed", ->
-      sm = new SelectionManager({'source': column_data_source})
-      sm.select([glyph_renderer_view_reverse], geometry, true)
-      expect(sm.source.selected).to.not.deep.equal empty_selection
-      expect(sm.selector).to.not.deep.equal empty_selection
-
-    geometry2 = {
-      type: 'rect'
-      vx0: 0
-      vx1: 100
-      vy0: 60
-      vy1: 100
-    }
-
-    it "should select from multiple renderers", ->
-      sm = new SelectionManager({'source': column_data_source})
-      sm.select([glyph_renderer_view_normal], geometry2, true)
-      expect(sm.source.selected).to.deep.equal empty_selection
-      sm.select([glyph_renderer_view_normal, glyph_renderer_view2], geometry2, true)
-      expect(sm.source.selected).not.to.deep.equal empty_selection
-
-    # TODO (bev) if hit tests are changed to uniformly return lists, this can be removed
-    it "should ignore null hit test results", ->
-      sm = new SelectionManager({'source': column_data_source_null})
-      expect(sm.source.selected).to.deep.equal empty_selection
-      r = sm.select([glyph_renderer_view_null], geometry, true)
-      expect(r).to.be.false
-      expect(sm.source.selected).to.deep.equal empty_selection


Add animation options to up.destroy

Add animation options to up.destroy

--- lib/assets/javascripts/up/flow.js.coffee
+++ lib/assets/javascripts/up/flow.js.coffee
@@ -231,16 +231,25 @@
   ###*
   Destroys the given element or selector.
   Takes care that all destructors, if any, are called.
+  The element is removed from the DOM.

   @method up.destroy
   @param {String|Element|jQuery} selectorOrElement
-  @param {String|Function|Object} [options.animation]
   @param {String} [options.url]
   @param {String} [options.title]
+  @param {String} [options.animation='none']
+    The animation to use before the element is removed from the DOM.
+  @param {Number} [opts.duration]
+    The duration of the animation. See [`up.animate`](/up.motion#up.animate).
+  @param {Number} [opts.delay]
+    The delay before the animation starts. See [`up.animate`](/up.motion#up.animate).
+  @param {String} [opts.easing]
+    The timing function that controls the animation's acceleration. [`up.animate`](/up.motion#up.animate).
   ###
   destroy = (selectorOrElement, options) ->
     $element = $(selectorOrElement)
     options = u.options(options, animation: 'none')
+    animateOptions = up.motion.animateOptions(options)
     $element.addClass('up-destroying')
     # If e.g. a modal or popup asks us to restore a URL, do this
     # before emitting `fragment:destroy`. This way up.navigate sees the
@@ -249,7 +258,7 @@
     document.title = options.title if u.isPresent(options.title)
     up.bus.emit('fragment:destroy', $element)
     animationPromise = u.presence(options.animation, u.isPromise) ||
-      up.motion.animate($element, options.animation)
+      up.motion.animate($element, options.animation, animateOptions)
     animationPromise.then -> $element.remove()


don't set method the HEAD, just check...

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -165,7 +165,7 @@

             res.writeHead 200, headers

-            if req.method = "HEAD"
+            if req.method == "HEAD"
                 res.end()
             else
                 # if we have an id3, write that


Add a snippet to insert a "use babel" directive

--- .config/snippets.cson
+++ .config/snippets.cson
@@ -45,9 +45,12 @@

 # JavaScript
 ".source.js":
-	"Comment":
+	Comment:
 		prefix: "/"
 		body: "/** $1 */"
+	Babel:
+		prefix: "babel"
+		body: '"use babel";\n"use strict";\n\n'

 # CoffeeScript/CSON


add check_number to finder

add check_number to finder

fixes #26

--- client/skr/screens/payments/Payments.cjsx
+++ client/skr/screens/payments/Payments.cjsx
@@ -16,7 +16,8 @@
                 syncOptions: @syncOptions
                 src: Skr.Models.Payment, fields: [
                     {id:'id',   visible: false}
-                    {id:'visible_id', label: 'Payment ID', fixedWidth: 130 },
+                    {id:'visible_id',  title: 'Payment ID', fixedWidth: 130 },
+                    {id:'check_number', title: 'Check #',  fixedWidth: 130 },
                     {id:'name', flex: 1}
                     {
                         id:'amount',  fixedWidth: 120, textAlign: 'right',


clean up code

clean up code

--- public/coffee/view/themesh.coffee
+++ public/coffee/view/themesh.coffee
@@ -3,13 +3,11 @@
       super
       @vertex = []
       @levelVertex = []
-#      @index = []
       @normals = []

    setUp: (@triangleVertex) ->
       gl = @gl
       @levelVertex = @levelVertex.concat( @triangleVertex )
-      #@vertex = @vertex.concat ( @triangleVertex )
       @addLevel(@levelVertex)

       # vertex
@@ -32,18 +30,6 @@
       @initBuffer(bufferData)

    addLevel: (triangleVertex = null) ->
-      # update old vertex position
-      ###
-      if @vertex.length >= 18*3
-         @getLastLevelVertex ((i, b) ->
-            @vertex.splice.apply( @vertex, [b,3].concat( @levelVertex.slice(i*3, i*3+3) ) )
-         ).bind(this)
-         l = @vertex.length - 18*3
-         for i in [0..2]
-            @calculateTriangleNormal(l+i*18)
-            @calculateTriangleNormal(l+i*18+9)
-      ###
-
       # duplicate level
       lastLevelVertex = triangleVertex if triangleVertex?
       lastLevelVertex = @getLevelAsTriangle() unless lastLevelVertex?
@@ -114,7 +100,6 @@

    updateVertexPos: ->
       gl = @gl
-
       # update local vertex
       @getLastLevelVertex ((i, b) ->
          @vertex.splice.apply(
@@ -157,4 +142,3 @@
    draw: () ->
       @updateVertexPos()
       super
-      #super @gl.LINE_LOOP


Update avatar.coffee

Update avatar.coffee

--- server/startup/avatar.coffee
+++ server/startup/avatar.coffee
@@ -90,7 +90,7 @@
 				position = username.length % colors.length
 				color = colors[position]
 				username = username.replace(/[^A-Za-z0-9]/g, '.').replace(/\.+/g, '.').replace(/(^\.)|(\.$)/g, '')
-				usernameParts = username.split(/[\s\.]+/)
+				usernameParts = username.split('.')
 				initials = if usernameParts.length > 1
 					_.first(usernameParts)[0] + _.last(usernameParts)[0]
 				else


bugfix to much generic route

bugfix to much generic route

--- modules/gui/platforms/node/lib/UploadContext.coffee
+++ modules/gui/platforms/node/lib/UploadContext.coffee
@@ -6,7 +6,7 @@
         configure: (config)->
             super new floyd.Config
                 data:
-                    route:'upload'
+                    route:'/upload'

                     accept: '.*'

@@ -79,7 +79,7 @@
                 done: fn

                 each: (file, next)=>
-                    process.nextTick ()=>
+                    setImmediate ()=>
                         floyd.tools.files.rm file.path

                         next()


Add test to check data updates when geojson updates

--- bokehjs/test/test_source/test_geojson_data_source.coffee
+++ bokehjs/test/test_source/test_geojson_data_source.coffee
@@ -55,6 +55,19 @@

     it "add x,y,NaN to data", ->
       expected_data = {'x': [125.6], 'y': [10.1], 'z': [NaN], 'xs': [[]], 'ys': [[]], 'zs': [[]]}
+      expect(geo.get("data")).to.be.deep.equal(expected_data)
+
+    it "updates data when geojson updates", ->
+      new_geojson = """{
+        "type": "GeometryCollection",
+        "geometries": [{
+          "type": "Point",
+          "coordinates": [125.6, 22]
+          }
+        ]
+      }"""
+      geo.set('geojson', new_geojson)
+      expected_data = {'x': [125.6], 'y': [22], 'z': [NaN], 'xs': [[]], 'ys': [[]], 'zs': [[]]}
       expect(geo.get("data")).to.be.deep.equal(expected_data)

   describe "single xyz Point", ->


refactor(HomeCtrl): remove unused dependencies

--- app/scripts/controllers/HomeCtrl.coffee
+++ app/scripts/controllers/HomeCtrl.coffee
@@ -2,14 +2,9 @@

 angular.module( "app.controllers.HomeCtrl", []).controller "HomeCtrl", [
 	"$scope"
-	"vkApi"
 	"audio"

-	( $scope, vkApi, audio ) ->
-		request = require "request"
-		fs = require "fs"
-		$scope._ = _ = require "lodash"
-
+	( $scope, audio ) ->
 		$scope.localPath = "D:/vk-music"

 		$scope.list = []


monitor: sort by startDate, fix #11

monitor: sort by startDate, fix #11

--- src/routes/monitor.coffee
+++ src/routes/monitor.coffee
@@ -68,6 +68,8 @@
 					for execution in mapped
 						if execution
 							byStatus[execution.status].push execution
+					for k,v of byStatus
+						byStatus[k] = v.sort (a,b) -> new Date(a.startTime) - new Date(b.startTime)
 					if Accepts(req).types().length > 0 and Accepts(req).types()[0] is 'text/html'
 						return res.render 'monitor', { byStatus, statuses }
 					else


increase json size to 2mb

increase json size to 2mb

--- services/spelling/app.coffee
+++ services/spelling/app.coffee
@@ -13,7 +13,7 @@

-server.use bodyParser.json()
+server.use bodyParser.json(limit: "2mb")
 server.use metrics.http.monitor(logger)

 server.post "/user/:user_id/check", SpellingAPIController.check


Adjust content check user date

Adjust content check user date

--- app/views/play/CampaignView.coco.coffee
+++ app/views/play/CampaignView.coco.coffee
@@ -1001,6 +1001,8 @@
     level = _.find _.values(@getLevels()), slug: levelSlug

     defaultAccess = if me.get('hourOfCode') or @campaign?.get('type') is 'hoc' or @campaign?.get('slug') is 'intro' then 'long' else 'short'
+    if new Date(me.get('dateCreated')) < new Date('2021-09-21')
+      defaultAccess = 'all'
     access = me.getExperimentValue 'home-content', defaultAccess
     freeAccessLevels = (fal.slug for fal in utils.freeAccessLevels when _.any [
       fal.access is 'short'


Fixed access substring error for real this time

--- src/scripts/access.coffee
+++ src/scripts/access.coffee
@@ -53,8 +53,8 @@

   # Catchall for unrecognized commands
   robot.catchAll (res) ->
-    Logger.debug "Trying to catchAll, test1: #{res.message.text.match(REGEX.ibizan)}  test2: #{res.message.room.substring(0,1)}"
-    if res.message.text.match(REGEX.ibizan) or (res.message.room and res.message.room.substring(0,1) is 'D')
+    if res.message.text.match(REGEX.ibizan) or
+       (res.message.room and res.message.room.substring(0,1) is 'D')
       res.send "_#{res.random strings.unknowncommand}
                 #{res.random strings.askforhelp}_"
       Logger.addReaction 'question', res.message


Change getId link

Change getId link

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -74,7 +74,7 @@
             logger.error "Can not log in new Informatics user #{@username}", e.message, e

     getId: () ->
-        page = await @download("https://informatics.msk.ru/course/view.php?id=1135")
+        page = await @download("https://informatics.msk.ru/")
         @name = /([^<]*)</.exec(page)?[1]
         id = /<a href="https:\/\/informatics.msk.ru\/user\/profile.php\?id=(\d+)"/.exec(page)?[1]
         if not @name or not id or id.length < 2


Bad regex :(

Bad regex :(

--- src/scripts/counts.coffee
+++ src/scripts/counts.coffee
@@ -23,7 +23,7 @@
         count = reply
       msg.send "Total rimshots played: #{count}"

-  robot.hear /(plays a rimshot)/i, (msg) ->
+  robot.hear /rimshot/i, (msg) ->
     client.hincrby "counts", "rimshots", 1, (err, reply) ->
       if err
         throw err


QA is now known as Integration.

QA is now known as Integration.

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -119,7 +119,7 @@
   # Active Network - Faith Specific
   get_all_change_requests_qa: (msg) ->
     category_number_regex = /https:\/\/activefaith.sifterapp.com\/projects\/[0-9]*\/issues\?/i
-    category_disposition_regex = /(Dropped \| QA)+/i
+    category_disposition_regex = /(Dropped \| Integration)+/i
     change_request_regex = /(Change Request)+(\-[A-Z]*)*( for Deployment of )+/i
     msg.http("#{@api_url}/categories")
       .header('X-Sifter-Token', token)
@@ -143,7 +143,7 @@
                       if reply is 0
                         console.log "Adding #{build} to qa_builds hash"
                         client.sadd "qa_builds", build, (error, reply) ->
-                          msg.send "#{build} has just been deployed to QA"
+                          msg.send "#{build} has just been deployed to Integration"

   # Active Network - Faith Specific
   get_all_change_requests_staging: (msg) ->


Speed up attribute loop in bind

Speed up attribute loop in bind

--- lib/assets/javascripts/twine.js.coffee
+++ lib/assets/javascripts/twine.js.coffee
@@ -45,12 +45,15 @@
   if node.bindingId
     Twine.unbind(node)

-  for type, binding of Twine.bindingTypes when definition = node.getAttribute(type)
-    element = {bindings: []} unless element  # Defer allocation to prevent GC pressure
-    fn = binding(node, context, definition, element)
-    element.bindings.push(fn) if fn
-
-  if newContextKey = node.getAttribute('context')
+  for attribute in node.attributes
+    if binding = Twine.bindingTypes[attribute.name.toLowerCase()]
+      element = {bindings: []} unless element  # Defer allocation to prevent GC pressure
+      fn = binding(node, context, attribute.value, element)
+      element.bindings.push(fn) if fn
+    else if attribute.name == 'context'
+      newContextKey = attribute.value
+
+  if newContextKey
     keypath = keypathForKey(newContextKey)
     if keypath[0] == '$root'
       context = rootContext
@@ -286,7 +289,7 @@
 setupAttributeBinding = (attributeName, bindingName) ->
   booleanAttribute = attributeName in ['checked', 'disabled', 'readOnly']

-  Twine.bindingTypes["bind-#{bindingName}"] = (node, context, definition) ->
+  Twine.bindingTypes["bind-#{bindingName.toLowerCase()}"] = (node, context, definition) ->
     fn = wrapFunctionString(definition, '$context,$root', node)
     lastValue = undefined
     return refresh: ->


spec for flatMapConcat

spec for flatMapConcat

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -740,6 +740,18 @@
       -> Bacon.once({ bacon: Bacon.once("sir francis")}).flatMapWithConcurrencyLimit(1, ".bacon")
       ["sir francis"])

+describe "EventStream.flatMapConcat", ->
+  describe "is like flatMapWithConcurrencyLimit(1)", ->
+    expectStreamEvents(
+      -> series(1, [1, 2]).flatMapConcat((value) ->
+        Bacon.sequentially(t(2), [value, error(), value]))
+      [1, error(), 1, 2, error(), 2], unstable)
+  describe "Respects function construction rules", ->
+    expectStreamEvents(
+      -> Bacon.once({ bacon: Bacon.once("sir francis")}).flatMapConcat(".bacon")
+      ["sir francis"])
+
+
 describe "Property.flatMap", ->
   describe "should spawn new stream for all events including Init", ->
     expectStreamEvents(


fix the test, not sure why this isn't working

--- spec/juno-spec.coffee
+++ spec/juno-spec.coffee
@@ -24,9 +24,9 @@
       path = require 'path'
       checkPath = (p) -> juno.misc.paths.getVersion p
       # waitsFor (done) ->
-      #   checkPath(path.join(__dirname, "juno-spec.coffee")).then -> done()
+      #   checkPath(path.join path.homedir, '..', '..', 'julia', 'julia').then -> done()
       # waitsFor (done) ->
-      #   checkPath(path.join(__dirname, "doesn't-exist.coffee")).catch -> done()
+      #   checkPath(path.join(__dirname, "foobar")).catch -> done()

     it "can validate the existence of a julia command", ->
       checkPath = (p) -> juno.misc.paths.getVersion p


Update packages for new naming scheme

Update packages for new naming scheme

--- spec/link-spec.coffee
+++ spec/link-spec.coffee
@@ -5,8 +5,8 @@
   [editor] = []

   beforeEach ->
-    atom.activatePackage('javascript-tmbundle', sync: true)
-    atom.activatePackage('hyperlink-helper-tmbundle', sync: true)
+    atom.activatePackage('language-javascript', sync: true)
+    atom.activatePackage('language-hyperlink', sync: true)
     window.rootView = new RootView
     rootView.openSync('sample.js')
     atom.activatePackage('link')


:lipstick:

:lipstick:

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -301,4 +301,3 @@

         expect(dragStartEvent.originalEvent.dataTransfer.getData("text/plain")).toEqual editSession1.getPath()
         expect(dragStartEvent.originalEvent.dataTransfer.getData("text/uri-list")).toEqual 'file://' + editSession1.getPath()
-


update epson bt300 to 200hz specs

update epson bt300 to 200hz specs

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -500,8 +500,8 @@
       cost: 1400
       title_product: 'Epson Moverio BT-300 Add-on'
       title_store: 'Epson Moverio BT-300 Binocular Mount Add-on'
-      description_store: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 120hz eye cameras."
-      description_cart: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 120hz eye cameras."
+      description_store: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 200hz eye cameras."
+      description_cart: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 200hz eye cameras."
       img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
     htcviveb:
       cost: 1400


add motoz2 play for pm bundle

add motoz2 play for pm bundle

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -126,7 +126,7 @@
   return ['hololens_w120_e120b','htcviveb','oculusdk2b','oculusdk2m','epsonbt300b','epsonbt200b']

 get_additional_product_ids = ->
-  return ['e120upgrade','usbcupgrade','pm_nexus5x','support6','support12']
+  return ['e120upgrade','usbcupgrade','pm_motoz2','support6','support12']

 get_product_database = ->
@@ -415,6 +415,13 @@
       description_store: "Connect your Pupil headset to the Nexus 5x. Record video locally on the Nexus 5x, or stream video over Wifi. The Nexus 5x comes with a sports arm band, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. "
       description_cart: "Nexus 5x pre-loaded with Pupil Mobile app, sports arm band, and USBC-USBC cable. Connects to Pupil headsets via USBC-USBC cable."
       img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
+    pm_motoz2:
+      cost: 350
+      title_product: 'Pupil Mobile Bundle'
+      title_store: "Pupil Mobile Bundle"
+      description_store: "Connect your Pupil headset to the Moto Z2 play. Record up to 4 hours of video locally on the phone, or stream video and sensor data over Wifi. The bundle comes with: Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app."
+      description_cart: "Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. Connects to Pupil headset via USBC cable (included)."
+      img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
     support6:
       cost: 1000
       title_product: 'Support Contract'


robot extension detection should be case insensitive

--- lib/autocomplete-plus-provider.coffee
+++ lib/autocomplete-plus-provider.coffee
@@ -17,7 +17,7 @@
 scheduleReload = false

 isRobotFile = (fileContent, filePath, settings) ->
-  ext = pathUtils.extname(filePath)
+  ext = pathUtils.extname(filePath).toLowerCase()
   if ext == '.robot'
     return true
   if ext in settings.robotExtensions and robotParser.isRobot(fileContent)
@@ -25,7 +25,7 @@
   return false

 isLibdocXmlFile = (fileContent, filePath, settings) ->
-  ext = pathUtils.extname(filePath)
+  ext = pathUtils.extname(filePath).toLowerCase()
   return ext is '.xml' and libdocParser.isLibdoc(fileContent)

 readdir = (path) ->


Use `atom.activatePackage` directly in specs

--- spec/spell-check-spec.coffee
+++ spec/spell-check-spec.coffee
@@ -7,7 +7,7 @@
     window.rootView = new RootView
     rootView.open('sample.js')
     config.set('spell-check.grammars', [])
-    window.loadPackage('spell-check', activateImmediately: true)
+    atom.activatePackage('spell-check', immediate: true)
     syntax.trigger 'grammars-loaded'
     rootView.attachToDom()
     editor = rootView.getActiveView()


Use TextEditor::moveToEndOfLine

Use TextEditor::moveToEndOfLine

--- spec/spell-check-spec.coffee
+++ spec/spell-check-spec.coffee
@@ -51,7 +51,7 @@

     runs ->
       expect(decorations.length).toBe 1
-      editor.moveCursorToEndOfLine()
+      editor.moveToEndOfLine()
       editor.insertText('a')
       advanceClock(editor.getBuffer().getStoppedChangingDelay())
       decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling')


Remove WorkspaceView references from spell-check-spec.coffee

--- spec/spell-check-spec.coffee
+++ spec/spell-check-spec.coffee
@@ -1,9 +1,9 @@
-{WorkspaceView} = require 'atom'
-
 describe "Spell check", ->
-  [editorView] = []
+  [workspaceElement, editor, editorElement] = []

   beforeEach ->
+    workspaceElement = atom.views.getView(atom.workspace)
+
     waitsForPromise ->
       atom.packages.activatePackage('language-text')

@@ -11,7 +11,6 @@
       atom.packages.activatePackage('language-javascript')

     runs ->
-      atom.workspaceView = new WorkspaceView
       atom.config.set('spell-check.grammars', [])

     waitsForPromise ->
@@ -21,118 +20,123 @@
       atom.packages.activatePackage('spell-check')

     runs ->
-      atom.workspaceView.attachToDom()
-      editorView = atom.workspaceView.getActiveView()
+      jasmine.attachToDOM(workspaceElement)
+      editor = atom.workspace.getActiveTextEditor()
+      editorElement = atom.views.getView(editor)

   it "decorates all misspelled words", ->
-    editorView.setText("This middle of thiss sentencts has issues.")
+    editor.setText("This middle of thiss sentencts has issues.")
     atom.config.set('spell-check.grammars', ['source.js'])

+    decorations = null
+
     waitsFor ->
-      editorView.find('.misspelling').length > 0
+      decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling')
+      decorations.length > 0

     runs ->
-      expect(editorView.find('.misspelling').length).toBe 2
-
-      typo1StartPosition = editorView.pixelPositionForBufferPosition([0, 15])
-      typo1EndPosition = editorView.pixelPositionForBufferPosition([0, 20])
-      expect(editorView.find('.misspelling:eq(0)').position()).toEqual typo1StartPosition
-      expect(editorView.find('.misspelling:eq(0)').width()).toBe typo1EndPosition.left - typo1StartPosition.left
-
-      typo2StartPosition = editorView.pixelPositionForBufferPosition([0, 21])
-      typo2EndPosition = editorView.pixelPositionForBufferPosition([0, 30])
-      expect(editorView.find('.misspelling:eq(1)').position()).toEqual typo2StartPosition
-      expect(editorView.find('.misspelling:eq(1)').width()).toBe typo2EndPosition.left - typo2StartPosition.left
+      expect(decorations.length).toBe 2
+      expect(decorations[0].marker.getBufferRange()).toEqual [[0, 15], [0, 20]]
+      expect(decorations[1].marker.getBufferRange()).toEqual [[0, 21], [0, 30]]

   it "hides decorations when a misspelled word is edited", ->
-    editorView.setText('notaword')
-    advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
+    editor.setText('notaword')
+    advanceClock(editor.getBuffer().getStoppedChangingDelay())
     atom.config.set('spell-check.grammars', ['source.js'])

+    decorations = null
     waitsFor ->
-      editorView.find('.misspelling').length > 0
+      decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling')
+      decorations.length > 0

     runs ->
-      expect(editorView.find('.misspelling').length).toBe 1
-      editorView.getEditor().moveCursorToEndOfLine()
-      editorView.getEditor().insertText('a')
-      advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
-      expect(editorView.find('.misspelling').length).toBe 0
+      expect(decorations.length).toBe 1
+      editor.moveCursorToEndOfLine()
+      editor.insertText('a')
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling')
+      expect(decorations.length).toBe 1
+      expect(decorations[0].marker.isValid()).toBe false

   describe "when spell checking for a grammar is removed", ->
     it "removes all the misspellings", ->
-      editorView.setText('notaword')
-      advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
+      editor.setText('notaword')
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
+      atom.config.set('spell-check.grammars', ['source.js'])
+
+      decorations = null
+      waitsFor ->
+        editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0
+
+      runs ->
+        expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 1
+        atom.config.set('spell-check.grammars', [])
+        expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 0
+
+  describe "when the editor's grammar changes to one that does not have spell check enabled", ->
+    it "removes all the misspellings", ->
+      editor.setText('notaword')
+      advanceClock(editor.getBuffer().getStoppedChangingDelay())
       atom.config.set('spell-check.grammars', ['source.js'])

       waitsFor ->
-        editorView.find('.misspelling').length > 0
+        editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0

       runs ->
-        expect(editorView.find('.misspelling').length).toBe 1
-        atom.config.set('spell-check.grammars', [])
-        expect(editorView.find('.misspelling').length).toBe 0
-
-  describe "when the editor's grammar changes to one that does not have spell check enabled", ->
-    it "removes all the misspellings", ->
-      editorView.setText('notaword')
-      advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
-      atom.config.set('spell-check.grammars', ['source.js'])
-
-      waitsFor ->
-        editorView.find('.misspelling').length > 0
-
-      runs ->
-        expect(editorView.find('.misspelling').length).toBe 1
-        atom.syntax.setGrammarOverrideForPath(editorView.getEditor().getPath(), 'text.plain')
-        editorView.getEditor().reloadGrammar()
-        expect(editorView.find('.misspelling').length).toBe 0
+        expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 1
+        atom.syntax.setGrammarOverrideForPath(editor.getPath(), 'text.plain')
+        editor.reloadGrammar()
+        expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 0

   describe "when 'spell-check:correct-misspelling' is triggered on the editor", ->
     describe "when the cursor touches a misspelling that has corrections", ->
       it "displays the corrections for the misspelling and replaces the misspelling when a correction is selected", ->
-        editorView.setText('tofether')
-        advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
+        editor.setText('tofether')
+        advanceClock(editor.getBuffer().getStoppedChangingDelay())
         atom.config.set('spell-check.grammars', ['source.js'])

         waitsFor ->
-          editorView.find('.misspelling').length > 0
+          editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0

         runs ->
-          editorView.trigger 'spell-check:correct-misspelling'
-          expect(editorView.find('.corrections').length).toBe 1
-          expect(editorView.find('.corrections li').length).toBeGreaterThan 0
-          expect(editorView.find('.corrections li:first').text()).toBe "together"
-          editorView.find('.corrections').view().confirmSelection()
-          expect(editorView.getText()).toBe 'together'
-          expect(editorView.getEditor().getCursorBufferPosition()).toEqual [0, 8]
-          advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
-          expect(editorView.find('.misspelling').length).toBe 0
-          expect(editorView.find('.corrections').length).toBe 0
+          atom.commands.dispatch editorElement, 'spell-check:correct-misspelling'
+
+          correctionsElement = editorElement.querySelector('.corrections')
+          expect(correctionsElement).toBeDefined()
+          expect(correctionsElement.querySelectorAll('li').length).toBeGreaterThan 0
+          expect(correctionsElement.querySelectorAll('li')[0].textContent).toBe "together"
+
+          atom.commands.dispatch correctionsElement, 'core:confirm'
+
+          expect(editor.getText()).toBe 'together'
+          expect(editor.getCursorBufferPosition()).toEqual [0, 8]
+          advanceClock(editor.getBuffer().getStoppedChangingDelay())
+          expect(editorElement.querySelectorAll('.spell-check-misspelling').length).toBe 0
+          expect(editorElement.querySelector('.corrections')).toBeNull()

     describe "when the cursor touches a misspelling that has no corrections", ->
       it "displays a message saying no corrections found", ->
-        editorView.setText('zxcasdfysyadfyasdyfasdfyasdfyasdfyasydfasdf')
-        advanceClock(editorView.getEditor().getBuffer().stoppedChangingDelay)
+        editor.setText('zxcasdfysyadfyasdyfasdfyasdfyasdfyasydfasdf')
+        advanceClock(editor.getBuffer().getStoppedChangingDelay())
         atom.config.set('spell-check.grammars', ['source.js'])

         waitsFor ->
-          editorView.find('.misspelling').length > 0
+          editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0

         runs ->
-          editorView.trigger 'spell-check:correct-misspelling'
-          expect(editorView.find('.corrections').length).toBe 1
-          expect(editorView.find('.corrections li').length).toBe 0
-          expect(editorView.find('.corrections').view().error.text()).toBe "No corrections"
+          atom.commands.dispatch editorElement, 'spell-check:correct-misspelling'
+          expect(editorElement.querySelectorAll('.corrections').length).toBe 1
+          expect(editorElement.querySelectorAll('.corrections li').length).toBe 0
+          expect(editorElement.querySelector('.corrections').textContent).toBe "No corrections"

   describe "when the editor is destroyed", ->
     it "destroys all misspelling markers", ->
-      editorView.setText('mispelling')
+      editor.setText('mispelling')
       atom.config.set('spell-check.grammars', ['source.js'])

       waitsFor ->
-        editorView.find('.misspelling').length > 0
+        editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0

       runs ->
-        editorView.remove()
-        expect(editorView.getModel().getMarkers().length).toBe 0
+        editor.destroy()
+        expect(editor.getMarkers().length).toBe 0


Remove bullets when collide with env.

Remove bullets when collide with env.

--- src/entities/toast.coffee
+++ src/entities/toast.coffee
@@ -20,7 +20,10 @@
     @vel.x += @accel.x * me.timer.tick
     @vel.y += @accel.y * me.timer.tick
     @computeVelocity(@vel)
+    res = @updateMovement()
+    @checkForEnvCollision(res)

-    @updateMovement()
+  checkForEnvCollision: (res) ->
+    me.game.remove(@) if res.x != 0 || res.y != 0

 )


 Merge branch 'master.activity.commentedit' into 'master'

activity: focus and resize comment edit input after view element added to DOM

--- client/Social/Activity/views/comments/newcommentform.coffee
+++ client/Social/Activity/views/comments/newcommentform.coffee
@@ -93,7 +93,12 @@

     @commentInput.setValue Encoder.htmlDecode data.body
     @commentInput.on "EscapePerformed", @bound "cancel"
-    @commentInput.setFocus()

   cancel: ->
     @getDelegate().emit "CommentUpdateCancelled"
+
+  viewAppended: ->
+    super
+    KD.utils.defer =>
+      @commentInput.setFocus()
+      @commentInput.resize()


Admin: hide Mount button if machine is not accessible

--- client/admin/lib/views/resources/resourcemachineitem.coffee
+++ client/admin/lib/views/resources/resourcemachineitem.coffee
@@ -2,6 +2,7 @@

 showError        = require 'app/util/showError'
 MachinesListItem = require 'app/environment/machineslistitem'
+Machine          = require 'app/providers/machine'

 module.exports   = class ResourceMachineItem extends MachinesListItem
@@ -23,6 +24,14 @@
     labelOptions = partial: label

     @labelLink.addSubView new kd.CustomHTMLView labelOptions
+
+    { State } = Machine
+    return  if machine.status.state in [
+      State.NotInitialized
+      State.Building
+      State.Terminating
+      State.Terminated
+    ]

     @labelLink.addSubView new kd.ButtonView
       title    : if mounted then 'Mounted' else 'Mount'


payment: PaymentController#fetchSubscriptionsWithPlans

--- client/landing/app/MainApp/payment/paymentcontroller.coffee
+++ client/landing/app/MainApp/payment/paymentcontroller.coffee
@@ -1,24 +1,12 @@
 class PaymentController extends KDController
-
-  getGroup = ->
-    KD.getSingleton('groupsController').getCurrentGroup()
-
-  getBalance: (type, callback)->
-
-    { JPaymentPlan } = KD.remote.api
-
-    if type is 'user'
-      JPaymentPlan.getUserBalance callback
-    else
-      JPaymentPlan.getGroupBalance callback

   fetchPaymentMethods: (callback) ->

     { dash } = Bongo

-    methods       = null
+    methods = null
     preferredPaymentMethod = null
-    appStorage    = new AppStorage 'Account', '1.0'
+    appStorage = new AppStorage 'Account', '1.0'
     queue = [

       -> appStorage.fetchStorage (err) ->
@@ -62,14 +50,11 @@
       { JPaymentSubscription } = KD.remote.api

       if type is 'group'
-        getGroup().checkPayment (err, subs) =>
+        KD.getGroup().checkPayment (err, subs) =>
           findActiveSubscription subs, planCode, callback
       else
         JPaymentSubscription.fetchUserSubscriptions (err, subs) ->
           findActiveSubscription subs, planCode, callback
-
-  deleteVM: (vmInfo, callback) -> debugger
-  # views

   fetchPlanByCode: (planCode, callback) ->

@@ -83,7 +68,7 @@

     switch type
       when 'group', 'expensed'
-        getGroup().fetchPaymentInfo callback
+        KD.getGroup().fetchPaymentInfo callback
       when 'user'
         JPaymentPlan.fetchAccountDetails callback

@@ -117,6 +102,24 @@

     return form

+  debitSubscription: ->
+    subscription.debit pack, (err, nonce) =>
+      return  if KD.showError err
+
+      @emit 'SubscriptionDebited', subscription
+
+      callback null, nonce
+
+  fetchSubscriptionsWithPlans: (tags, callback) ->
+    [callback, tags] = [tags, callback]  unless callback
+
+    KD.whoami().fetchPlansAndSubscriptions tags, (err, plansAndSubs) =>
+      return callback err  if err
+
+      { subscriptions } = @groupPlansBySubscription plansAndSubs
+
+      callback null, subscriptions
+
   groupPlansBySubscription: (plansAndSubscriptions = {}) ->

     { plans, subscriptions } = plansAndSubscriptions
@@ -130,4 +133,3 @@
       subscription.plan = plansByCode[subscription.planCode]

     { plans, subscriptions }
-


always show activity list w/o another accordion

--- js/inner/user.coffee
+++ js/inner/user.coffee
@@ -229,6 +229,8 @@
     VM.browser_settings.toggle_setting("aside_is_slim")
     if VM.browser_settings.settings().aside_is_slim and VM.browser_settings.settings().recent_activity_visible
       VM.browser_settings.set_setting("recent_activity_visible", false)
+    if !VM.browser_settings.settings().aside_is_slim                   #
+      VM.browser_settings.set_setting("recent_activity_visible", true) # meant to disable hiding until there's another accordion

   toggle_show_all_branches: () =>
     VM.browser_settings.toggle_setting("show_all_branches")


Add a link target to every heading on every docs page

--- js/outer/docs.coffee
+++ js/outer/docs.coffee
@@ -108,7 +108,25 @@
         return cx.redirect "/docs" + rewrite

       super cx
+      @addLinkTargets()

     catch e
       # TODO: go to 404 page
       return cx.redirect "/docs"
+
+
+  addLinkTargets: =>
+    for heading in $('article h2, article h3, article h4, article h5, article h6')
+      title = $(heading).text()
+      id = $(heading).attr("id")
+      console.log $(heading)
+      console.log heading
+      console.log id
+      console.log title
+      if not id?
+        id = title.toLowerCase()
+        id = id.replace(/^\s+/g, '').replace(/\s+$/g, '') # strip whitespace
+        id = id.replace(/\'/, '') # heroku's -> herokus
+        id = id.replace(/[^a-z0-9]+/g, '-') # dashes everywhere
+        id = id.replace(/^-/, '').replace(/-$/, '') # dont let first and last chars be dashes
+        $(heading).html("#{title}")


Add Label to models.coffee

Add Label to models.coffee

--- bokehjs/src/coffee/common/models.coffee
+++ bokehjs/src/coffee/common/models.coffee
@@ -8,6 +8,7 @@
   ToolEvents:               require './tool_events'

   BoxAnnotation:            require '../models/annotations/box_annotation'
+  Label:                    require '../models/annotations/label'
   Legend:                   require '../models/annotations/legend'
   PolyAnnotation:           require '../models/annotations/poly_annotation'
   Span:                     require '../models/annotations/span'


Fixed tests

Fixed tests

--- spec/views/select-core-view-spec.coffee
+++ spec/views/select-core-view-spec.coffee
@@ -37,7 +37,7 @@
       atom.workspaceView.trigger 'core:cancel'
       expect(atom.workspaceView.find('#spark-ide-select-core-view')).not.toExist()

-      # Test core:close
+      # # Test core:close
       atom.workspaceView.trigger 'spark-ide:select-core'
       expect(atom.workspaceView.find('#spark-ide-select-core-view')).toExist()
       atom.workspaceView.trigger 'core:close'
@@ -72,10 +72,10 @@
         expect(devices.eq(0).find('.secondary-line').text()).toEqual('51ff6e065067545724680187')
         expect(devices.eq(1).find('.secondary-line').text()).toEqual('51ff67258067545724380687')

+        selectCoreView.hide()
         SettingsHelper.clearCredentials()
-        atom.workspaceView.trigger 'core:close'

-    fit 'tests choosing core', ->
+    it 'tests choosing core', ->
       SettingsHelper.setCredentials 'foo@bar.baz', '0123456789abcdef0123456789abcdef'
       atom.workspaceView.trigger 'spark-ide:select-core'
       selectCoreView = sparkIde.selectCoreView


quick spammer countermeasure

quick spammer countermeasure

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -1222,6 +1222,10 @@
     mesg_invite_collaborator: (mesg) =>
         @touch()
         dbg = @dbg('mesg_invite_collaborator')
+        if mesg.project_id == '26b15eae-4660-4f02-980d-74cc814bfb3a'
+            # emergency SPAMMER countermeasure
+            @push_to_client(message.success(id:mesg.id))
+            return
         #dbg("mesg: #{misc.to_json(mesg)}")
         @get_project mesg, 'write', (err, project) =>
             if err


add a comment

add a comment

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -380,7 +380,7 @@
     get_cookie: (opts) ->
         opts = defaults opts,
             name : required
-            cb   : required   # cb(value)
+            cb   : required   # cb(undefined, value)
         if not @conn?.id?
             # no connection or connection died
             return
@@ -453,7 +453,7 @@

         x = @hash_session_id.split('$')    # format:  algorithm$salt$iterations$hash
         @_remember_me_value = [x[0], x[1], x[2], session_id].join('$')
-        @set_cookie
+        @set_cookie  # same name also hardcoded in the client!
             name  : base_url_lib.base_url() + 'remember_me'
             value : @_remember_me_value
             ttl   : ttl


Update documentation.

Update documentation.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -21,6 +21,8 @@
       exercise = -> parser.compile source
       expect(exercise).to.throw Error

+      # Chai doesn't provide a way to verify an error's name,
+      # so do it manually.
       try
         exercise()
       catch error


cleaned up tests

cleaned up tests

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -4,150 +4,140 @@
 else
   parser = require 'ccss-compiler'

-describe 'CCSS compiler', ->
+parse = (source, expect) ->
+  result = null
+  describe source, ->
+    it 'should do something', ->
+      result = parser.parse source
+      chai.expect(result).to.be.an 'object'
+    it 'should match expected', ->
+      chai.expect(result).to.eql expect
+
+describe 'CCSS-to-AST', ->
   it 'should provide a parse method', ->
     chai.expect(parser.parse).to.be.a 'function'
+
+  describe "/* Basics */", ->
+
+    parse """
+            10 <= 2 == 3 < 4 == 5 // chainning numbers, maybe should throw error?
+          """
+        ,
+          {
+            selectors: [],
+            vars: [],
+            constraints: [
+              ['lte', ['number', 10], ['number', 2]]
+              ['eq', ['number', 2], ['number', 3]]
+              ['lt', ['number', 3], ['number', 4]]
+              ['eq', ['number', 4], ['number', 5]]
+            ]
+          }
+
+    parse """
+            [grid-height] * #box2[width] <= 2 == 3 < 4 == 5 // w/ multiple statements containing variables and getters
+          """
+        ,
+          {
+            selectors: [
+              '#box2'
+            ],
+            vars: [
+              ['get', "[grid-height]", 'grid-height']
+              ['get', '#box2[width]','width', ['$id', 'box2']]
+            ],
+            constraints: [
+              ['lte', [
+                'multiply', ['get', '[grid-height]'], ['get', '#box2[width]']
+                ],
+                ['number', 2]
+              ]
+              ['eq', ['number', 2], ['number', 3]]
+              ['lt', ['number', 3], ['number', 4]]
+              ['eq', ['number', 4], ['number', 5]]
+            ]
+          }
+
+  describe "/* Strength */", ->

-  describe 'with multiple number statements', ->
-    source = """
-    10 <= 2 == 3 < 4 == 5
-    """
-    expect =
-      selectors: [],
-      vars: [],
-      constraints: [
-        ['lte', ['number', 10], ['number', 2]]
-        ['eq', ['number', 2], ['number', 3]]
-        ['lt', ['number', 3], ['number', 4]]
-        ['eq', ['number', 4], ['number', 5]]
-      ]
-    result = null
-    it 'should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it 'the result should match the expectation', ->
-      chai.expect(result).to.eql expect
-
-  describe 'with multiple statements containing variables and getters', ->
-    source = """
-    [grid-height] * #box2[width] <= 2 == 3 < 4 == 5
-    """
-    expect =
-      selectors: [
-        '#box2'
-      ],
-      vars: [
-        ['get', "[grid-height]", 'grid-height']
-        ['get', '#box2[width]','width', ['$id', 'box2']]
-      ],
-      constraints: [
-        ['lte', [
-          'multiply', ['get', '[grid-height]'], ['get', '#box2[width]']
-          ],
-          ['number', 2]
-        ]
-        ['eq', ['number', 2], ['number', 3]]
-        ['lt', ['number', 3], ['number', 4]]
-        ['eq', ['number', 4], ['number', 5]]
-      ]
+    parse """
+            4 == 5 == 6 !strong:10 // w/ strength and weight
+          """
+        ,
+          {
+            selectors: []
+            vars: []
+            constraints: [
+              ['eq', ['number', 4], ['number', 5], 'strong', 10]
+              ['eq', ['number', 5], ['number', 6], 'strong', 10]
+            ]
+          }
+
+  describe "/* Stays */", ->
+
+    parse """
+            @-gss-stay #box[width], [grid-height];
+          """
+        ,
+          {
+            selectors: [
+              '#box'
+            ]
+            vars: [
+              ['get', '#box[width]', 'width', ['$id', 'box']]
+              ['get', '[grid-height]', 'grid-height']
+            ]
+            constraints: [
+              ['stay', ['get', '#box[width]'], ['get', '[grid-height]']]
+            ]
+          }

-    result = null
-    it 'should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it 'the result should match the expectation', ->
-      chai.expect(result).to.eql expect
-
-  describe 'with a simple statement and a constraint strength and weight', ->
-    source = """
-    4 == 5 == 6 !strong:10
-    """
-    expect =
-      selectors: []
-      vars: []
-      constraints: [
-        ['eq', ['number', 4], ['number', 5], 'strong', 10]
-        ['eq', ['number', 5], ['number', 6], 'strong', 10]
-      ]
-    result = null
-    it 'should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it 'the result should match the expectation', ->
-      chai.expect(result).to.eql expect
-
-  describe 'with a stay rule', ->
-    source = """
-    @-gss-stay #box[width], [grid-height];
-    """
-    expect =
-      selectors: [
-        '#box'
-      ]
-      vars: [
-        ['get', '#box[width]', 'width', ['$id', 'box']]
-        ['get', '[grid-height]', 'grid-height']
-      ]
-      constraints: [
-        ['stay', ['get', '#box[width]'], ['get', '[grid-height]']]
-      ]
-    result = null
-    it 'should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it 'the result should match the expectation', ->
-      chai.expect(result).to.eql expect
-
-  describe ': with reserved pseudos', ->
-    source = """
-    ::this[right] == ::document[right] == ::viewport[right]
-    """
-    expect =
-      selectors: [
-        '::this'
-        '::document'
-        '::viewport'
-      ]
-      vars: [
-        ['get', '::this[right]', 'right', ['$reserved', 'this']]
-        ['get', '::document[right]', 'right', ['$reserved', 'document']]
-        ['get', '::viewport[right]', 'right', ['$reserved', 'viewport']]
-      ]
-      constraints: [
-        ['eq', ['get', '::this[right]'], ['get', '::document[right]']]
-        ['eq', ['get', '::document[right]'], ['get', '::viewport[right]']]
-      ]
-    result = null
-    it ': should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it ': the result should match the expectation', ->
-      chai.expect(result).to.eql expect
+  describe '/* Reserved Pseudos */', ->
+
+    parse """
+            ::this[right] == ::document[right] == ::viewport[right]
+          """
+        ,
+          {
+            selectors: [
+              '::this'
+              '::document'
+              '::viewport'
+            ]
+            vars: [
+              ['get', '::this[right]', 'right', ['$reserved', 'this']]
+              ['get', '::document[right]', 'right', ['$reserved', 'document']]
+              ['get', '::viewport[right]', 'right', ['$reserved', 'viewport']]
+            ]
+            constraints: [
+              ['eq', ['get', '::this[right]'], ['get', '::document[right]']]
+              ['eq', ['get', '::document[right]'], ['get', '::viewport[right]']]
+            ]
+          }

-  describe 'with a 2D stay, 2D constraint and measure', ->
-    source = """
-    @-gss-stay #box[size];
-    #box[position] >= measure(#box[position]) !require;
-    """
-    expect =
-      selectors: [
-        '#box'
-      ]
-      vars: [
-        ['get', '#box[width]', 'width', ['$id', '#box']]
-        ['get', '#box[height]', 'height', ['$id', '#box']]
-        ['get', '#box[x]', 'x', ['$id', '#box']]
-        ['get', '#box[y]', 'y', ['$id', '#box']]
-      ]
-      constraints: [
-        ['stay', ['get', '#box[width]']]
-        ['stay', ['get', '#box[height]']]
-        ['gte', ['get', '#box[x]'], ['measure', ['get', '#box[x]']], 'require']
-        ['gte', ['get', '#box[y]'], ['measure', ['get', '#box[y]']], 'require']
-      ]
-    result = null
-    it 'should be able to produce a result', ->
-      result = parser.parse source
-      chai.expect(result).to.be.an 'object'
-    it 'the result should match the expectation', ->
-      chai.expect(result).to.eql expect
+  describe '/* 2D */', ->
+
+  describe '/ with a 2D stay, 2D constraint and measure', ->
+    parse """
+            @-gss-stay #box[size];
+            #box[position] >= measure(#box[position]) !require;
+            // with a 2D stay, 2D constraint and measure
+          """
+        ,
+          {
+            selectors: [
+              '#box'
+            ]
+            vars: [
+              ['get', '#box[width]', 'width', ['$id', '#box']]
+              ['get', '#box[height]', 'height', ['$id', '#box']]
+              ['get', '#box[x]', 'x', ['$id', '#box']]
+              ['get', '#box[y]', 'y', ['$id', '#box']]
+            ]
+            constraints: [
+              ['stay', ['get', '#box[width]']]
+              ['stay', ['get', '#box[height]']]
+              ['gte', ['get', '#box[x]'], ['measure', ['get', '#box[x]']], 'require']
+              ['gte', ['get', '#box[y]'], ['measure', ['get', '#box[y]']], 'require']
+            ]
+          }


Use execCommands helper in checkout spec

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -223,7 +223,7 @@
       it 'does not check a branch out if the dirty tree interferes', ->
         fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'README.md'), 'great words', 'utf8')
         gitCommandHandler = jasmine.createSpy('gitCommandHandler')
-        exec "cd #{repoDirectory} && git add . && git commit -m 'update README'", gitCommandHandler
+        execCommands ["cd #{repoDirectory}", "git add .", "git commit -m 'update README'"], gitCommandHandler

         waitsFor ->
           gitCommandHandler.callCount is 1


set minimal amount of gas to avoid issues with slighter bigger contracts; to be configurable in 0.2.0

--- tasks/deploy.coffee
+++ tasks/deploy.coffee
@@ -30,7 +30,7 @@
       #TODO: refactor this into a common method
       if compiled_contracts.info is undefined
         for className, contract of compiled_contracts
-          contractAddress = web3.eth.sendTransaction({from: primaryAddress, data: contract.code})
+          contractAddress = web3.eth.sendTransaction({from: primaryAddress, data: contract.code, gas: 100000})
           grunt.log.writeln("deployed #{className} at #{contractAddress}")

           abi = JSON.stringify(contract.info.abiDefinition)


`bundle` now runs `bundle-builder` with Neft modules

--- bundle.coffee
+++ bundle.coffee
@@ -1,10 +1,14 @@
 fs = require 'fs'
-bundle = require 'bundle-builder'
 utils = require 'utils'
 Mustache = require 'mustache'
 coffee = require 'coffee-script'

-modulePackage = require './package.json'
+global.Neft =
+	utils: require 'utils'
+	log: require 'log'
+	assert: require './node_modules/assert'
+
+bundle = require 'bundle-builder'

 createBundle = (opts, callback) ->
 	bundle {
@@ -52,4 +56,4 @@
 stack.add createBundle, null, [platform: 'ios', release: false]
 stack.add createBundle, null, [platform: 'ios', release: true]

-stack.runAll ->+stack.runAll ->


Update unit tests

Update unit tests

--- test/unit/coffee/RedisBackend/RedisBackendTests.coffee
+++ test/unit/coffee/RedisBackend/RedisBackendTests.coffee
@@ -368,15 +368,8 @@

 			it "should return the primary result", ->
 				@result.should.deep.equal [@doclines, @version]
-
-			it "should log out the secondary error", ->
-				@logger.error
-					.calledWith({
-						err: @error
-					}, "error in redis backend")
-					.should.equal true
-
-		describe "when the secondary errors", ->
+
+		describe "when the primary errors", ->
 			beforeEach (done) ->
 				@rclient_redis.get = sinon.stub()
 				@rclient_redis.exec = sinon.stub().yields(@error = new Error("oops"))
@@ -391,13 +384,6 @@

 			it "should return the error", ->
 				@returned_error.should.equal @error
-
-			it "should log out the error", ->
-				@logger.error
-					.calledWith({
-						err: @error
-					}, "error in redis backend")
-					.should.equal true

 		describe "when the secondary takes longer than SECONDARY_TIMEOUT", ->
 			beforeEach (done) ->


Rename build_errors() to build_all_errors().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -86,10 +86,10 @@
   all_expectations.length = 0

 verify_all_expectations = ->
-  errors = build_errors()
+  errors = build_all_errors()
   throw new Error( errors ) unless errors == ""

-build_errors = ->
+build_all_errors = ->
   (build_not_called_error(expectation) for expectation in all_expectations when not expectation.called).join("")

 build_not_called_error = (expectation) ->


Minor fix in GOST style IC symbol generator

--- src/symbol/gost/ic.coffee
+++ src/symbol/gost/ic.coffee
@@ -46,11 +46,11 @@

   left = symbol.left
   if symbol.top.length # Move top pins to the left side
-    left.push '-'
+    if left.length then left.push '-'
     left = left.concat symbol.top
   right = symbol.right
   if symbol.bottom.length # Move bottom pins to the right side
-    right.push '-'
+    if right.length then right.push '-'
     right = right.concat symbol.bottom

   pins = element.pins


Better Flow loops detected

Better Flow loops detected

--- impl/base/level1/flow.coffee
+++ impl/base/level1/flow.coffee
@@ -6,7 +6,7 @@

 log = log.scope 'Renderer', 'Flow'

-MAX_LOOPS = 50
+MAX_LOOPS = 150

 min = (a, b) ->
 	if a < b
@@ -314,16 +314,26 @@
 		item._impl.updatePending = false
 	return

+cleanPending = false
+cleanQueue = []
+clean = ->
+	cleanPending = false
+	while data = cleanQueue.pop()
+		if data.loops < MAX_LOOPS
+			data.loops = 0
+	return
+
 update = ->
 	data = @_impl

 	if data.pending or not @_effectItem?._visible
 		return

-	if data.updatePending
-		data.loops++
-	else
-		data.loops = 0
+	unless data.loops++
+		cleanQueue.push data
+		unless cleanPending
+			setTimeout clean
+			cleanPending = true

 	data.pending = true
 	queue.push @


Move filters

Move filters

--- vendor/assets/javascripts/brainstem/data-loader.coffee
+++ vendor/assets/javascripts/brainstem/data-loader.coffee
@@ -8,15 +8,14 @@
   _parseLoadOptions: (loadOptions) ->
     @loadOptions = $.extend {}, loadOptions
     @loadOptions.only = if @loadOptions.only then _.map((Brainstem.Utils.extractArray "only", @loadOptions), (id) -> String(id)) else null
-
+    @loadOptions.filters ?= {}
   load: (loadOptions) ->
     @_parseLoadOptions(loadOptions)

     options = @loadOptions
     include = _(options.include).map((i) -> _.keys(i)[0]) # pull off the top layer of includes
-    filters = options.filters || {}
     order = options.order || "updated_at:desc"
-    filterKeys = _.map(filters, (v, k) -> "#{k}:#{v}").join(',')
+    filterKeys = _.map(@loadOptions.filters, (v, k) -> "#{k}:#{v}").join(',')
     cacheKey = [order, filterKeys, options.page, options.perPage, options.limit, options.offset].join('|')

     cachedCollection = @storageManager.storage @loadOptions.name
@@ -74,7 +73,7 @@
     syncOptions.data.include = include.join(",") if include.length
     syncOptions.data.only = _.difference(@loadOptions.only, alreadyLoadedIds).join(",") if @loadOptions.only?
     syncOptions.data.order = options.order if options.order?
-    _.extend(syncOptions.data, _(filters).omit('include', 'only', 'order', 'per_page', 'page', 'limit', 'offset', 'search')) if _(filters).keys().length
+    _.extend(syncOptions.data, _(@loadOptions.filters).omit('include', 'only', 'order', 'per_page', 'page', 'limit', 'offset', 'search')) if _(@loadOptions.filters).keys().length

     unless @loadOptions.only?
       if options.limit? && options.offset?


(feature) Deactivate console.log for tests.

--- src/test_processor.coffee
+++ src/test_processor.coffee
@@ -46,7 +46,13 @@
           _.each finishedCallbacks, (c) -> c()
           done()

-        runner.run flavoredCode, customApi
+        deactivateConsoleAPI = """
+          console = {log: function(){}};
+        """
+
+        fullCode = deactivateConsoleAPI + "\n" + flavoredCode
+
+        runner.run fullCode, customApi

       return {
         dom: config.templates.tests id: id


Add EventListener interface support

Add EventListener interface support

--- src/util/primitive.coffee
+++ src/util/primitive.coffee
@@ -21,13 +21,21 @@
     a.splice(i, 1) if i >= 0
     return
   @prototype.dispatchEvent or= (event) ->
+    stop = false
     e = {}
-    e[k] = v for k, v of event
-    e.type = e.type.toLowerCase()
-    e.target = this
+    Object.defineProperty(e, "type", {value: event.type.toLowerCase()})
+    Object.defineProperty(e, "target", {value: this})
+    Object.defineProperty(e, "stopPropagation", {value: -> stop = true; return})
+    for k, v of event
+      Object.defineProperty(e, k, {value: v}) unless e.hasOwnProperty(k)
     l = (@__listeners__ or {})
     a = (l[e.type] or= [])
     for listener in a
-      return false if listener() == false
+      if typeof(listener) == "function"
+        fn = listener
+      else if typeof(listener?.handleEvent) == "function"
+        fn = listener.handleEvent.bind(listener)
+      fn?(e)
+      return false if stop
     return true


Fixing tests. Decimal numbers now display to correct number of decimal places

--- math/expression_to_string_spec.coffee
+++ math/expression_to_string_spec.coffee
@@ -1,13 +1,14 @@
 #= require lib/math/expression_to_string

 describe "expression to string conversion", ->
+  beforeEach ->
+    @to_string = ttm.require("lib/math/expression_to_string").toString
+    @builder = ttm.require("lib/math/build_expression_from_javascript_object").buildExpression
   it "converts a simple addition expression", ->
-    to_string = ttm.require("lib/math/expression_to_string").toString
-    builder = ttm.require("lib/math/build_expression_from_javascript_object").buildExpression
-    expect(to_string(builder(1, '+', 2))).toEqual "1 + 2"
+    expect(@to_string(@builder(1, '+', 2))).toEqual "1 + 2"

   it "converts an exponentiation to a string", ->
-    to_string = ttm.require("lib/math/expression_to_string").toString
-    builder = ttm.require("lib/math/build_expression_from_javascript_object").buildExpression
-    expect(to_string(builder('^': [1, 2]))).toEqual "1^2"
+    expect(@to_string(@builder('^': [1, 2]))).toEqual "1^2"

+  it "correctly displays a complicated decimal number", ->
+    expect(@to_string(@builder(Math.PI))).toEqual "3.1416"


Cursor.getBeginningOfCurrentWordBufferPosition only uses the previous 2 lines for its scan range.

previous: editor.9000-line-file.at-end.move-to-beginning-of-word: 4695 / 100 = 46.95ms
new:      editor.9000-line-file.at-end.move-to-beginning-of-word: 1618 / 100 = 16.18ms

--- src/app/cursor.coffee
+++ src/app/cursor.coffee
@@ -106,12 +106,16 @@
   getBeginningOfCurrentWordBufferPosition: (options = {}) ->
     allowPrevious = options.allowPrevious ? true
     currentBufferPosition = @getBufferPosition()
+
+    previousRow = Math.max(0, currentBufferPosition.row - 1)
+    previousLinesRange = [[previousRow, 0], currentBufferPosition]
     beginningOfWordPosition = currentBufferPosition
-    range = [[0,0], currentBufferPosition]
-    @editor.backwardsScanInRange @wordRegex, range, (match, matchRange, { stop }) =>
+
+    @editor.backwardsScanInRange @wordRegex, previousLinesRange, (match, matchRange, { stop }) =>
       if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious
         beginningOfWordPosition = matchRange.start
       stop()
+
     beginningOfWordPosition

   getEndOfCurrentWordBufferPosition: (options = {}) ->


Use window.extension to load extensions from .atom file

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -55,6 +55,14 @@
     return if $("head style[path='#{fullPath}']").length
     $('head').append "<style path='#{fullPath}'>#{content}</style>"

+  extension: (name) ->
+    extensionPath = require.resolve name
+    extension = require extensionPath
+    rootView.activateExtension(extension)
+
+    extensionKeymapPath = fs.join(fs.directory(extensionPath), "keymap.coffee")
+    require extensionKeymapPath if fs.exists(extensionKeymapPath)
+
   reload: ->
     if rootView.getModifiedBuffers().length > 0
       message = "There are unsaved buffers, reload anyway?"


fixed a bug from last patch where /help was displaying incorrectly

--- src/chat/window_message_renderer.coffee
+++ src/chat/window_message_renderer.coffee
@@ -28,21 +28,23 @@
     @displayEmptyLine()
     @message '', "Type /help <command> to see details about a specific command.",
         'notice help'
+    @displayEmptyLine()
     @_displayWikiUrl('notice help')

   _displayWikiUrl: (style) ->
     @message '', "Visit #{MessageRenderer.WIKI_URL} to read " +
-        "documentation or give feedback.", style
+        "documentation and give feedback.", style

   _printCommands: (commands) ->
-    maxWidth = $('.text').width()
+    maxWidth = 40
     widthPerCommand = @_getMaxCommandWidth commands
     commandsPerLine = maxWidth / Math.floor widthPerCommand
     line = []
     for command, i in commands
       line.push @_fillWithWhiteSpace command, widthPerCommand
       if line.length >= commandsPerLine or i >= commands.length - 1
-        @message '', line.join ' ', 'notice help'
+        @message '', line.join('  '), 'notice help'
+        line = []

   _getMaxCommandWidth: (commands) ->
     maxWidth = 0


Auto-heal a little bit more slowly.

Auto-heal a little bit more slowly.

--- src/challenger.coffee
+++ src/challenger.coffee
@@ -39,7 +39,7 @@

   # Public: time, in milliseconds, that must elapse before automatically respawning.
   #
-  reviveTime: -> 5000 # 900000 # 15 minutes
+  reviveTime: -> 900000 # 15 minutes

   damage: (amount) -> @storage.hp -= amount

@@ -76,7 +76,7 @@
     fn = =>
       @storage.hp = Math.min(@maxHP(), @hp() + @healingRate())
       @healOverTime()
-    healTimeouts[@id()] = setTimeout fn, 1000
+    healTimeouts[@id()] = setTimeout fn, 5000

   # Public: Halt the healing process begun with `healOverTime`. Important so you don't heal during
   # a Match!


Ignore empty targets, add rebuild option.

--- src/coffeemake.coffee
+++ src/coffeemake.coffee
@@ -215,11 +215,13 @@
         Options:
         --watch    Rebuild the current directory for changes
         --test     Test run: print shell commands instead of executing them
+        --rebuild  Rebuild targets ignoring modification times
         --quiet    Do not show messages
         --verbose  Increase verboseness
         --help     Show this help
         """
       @process.exit()
+    rebuild: (args)-> @ignoreMtime = true
     watch: (args)-> @action = 'watchAction'
     verbose: (args)-> @verboseness++
     quiet: (args)-> @verboseness = -1
@@ -274,6 +276,7 @@
     s.mtime

   isTargetNewer: (target, deps)->
+    return true if @ignoreMtime
     return false unless (t = @fileTime target)?
     @debug "deps: " + target
     for d in deps
@@ -298,6 +301,7 @@
       @debug "Try to build #{target}"
       @debug "Complain if some dependencies are missing."
       for xx in deps
+        continue if xx is ""
         unless @depExists(xx)
           (if soft then @trace else @error).call @, "A required dependency is missing: #{xx}"
           return


Removed error loading plugin.json where there's none.

--- src/core/pluginsEngine/plugins.coffee
+++ src/core/pluginsEngine/plugins.coffee
@@ -44,8 +44,8 @@
 		try
 			plugin_data = require(env.pluginsEngine.cwd + '/plugins/' + plugin_name + '/plugin.json')
 		catch e
-			env.debug 'Error loading plugin.json (' + plugin_name + ')'
-			env.debug e.message.yellow
+			# env.debug 'Error loading plugin.json (' + plugin_name + ')'
+			# env.debug e.message.yellow
 			plugin_data = {
 				name: plugin_name
 			}
@@ -78,7 +78,7 @@
 				pluginsEngine.plugin[plugin_data.name]?.plugin_config = plugin_data
 		catch e
 			env.debug "Error while loading plugin " + plugin_data.name
-			env.debug e.message.yellow + ' at line ' + e.lineNumber?.red
+			env.debug e.stack.yellow # + ' at line ' + e.lineNumber?.red

 	pluginsEngine.init = (cwd, callback) ->
 		env.pluginsEngine.cwd = cwd


introduce cache in networking routers

introduce cache in networking routers

--- plugins/networking/app/assets/javascripts/networking/topology.coffee
+++ plugins/networking/app/assets/javascripts/networking/topology.coffee
@@ -131,6 +131,7 @@
           @getNodeDetails d.type,d.id, (html,status) ->
             if status==404
               $element.attr('class',$element.attr('class')+' not-found')
+              html = "This #{d.type} does not exist anymore."

             $popover.find('.popover-content').html(html)
             $popover.data("currentNode",d.id)
@@ -184,18 +185,22 @@

   getNodeDetails: (type,id,callback) ->
     @nodeDetails ||= {}
+    key = "#{type}_#{id}"
+    return callback(@nodeDetails[key].content,@nodeDetails[key].status) if @nodeDetails[key]
+
     $.get("#{@options.details_url}", {type: "#{type}", id: "#{id}"})
-      .error ( jqXHR, textStatus, errorThrown) ->
+      .error ( jqXHR, textStatus, errorThrown) =>
+        @nodeDetails[key] = {status: jqXHR.status, content: ''}
         callback("Loading error.",jqXHR.status)
-      .done (data, status, xhr)->
+      .done (data, status, xhr) =>
         url = xhr.getResponseHeader('Location')
-
         # got a redirect response
         if url
           # close modal window
           $('#modal-holder').find('.modal').modal('hide')
           window.location = url
         else
+          @nodeDetails[key] = {status: 200, content: data}
           callback(data,200)

   isConnected: (a, b) ->


setPaletteWidth

setPaletteWidth

--- src/controller.coffee
+++ src/controller.coffee
@@ -3592,6 +3592,10 @@
   hook 'resize', 0, ->
     @gutter.style.height = "#{Math.max @iceElement.offsetHeight, @view.getViewNodeFor(@tree).totalBounds?.height ? 0}px"

+  Editor::setPaletteWidth = (width) ->
+    @paletteWrapper.style.width = width + 'px'
+    @resize()
+
   # OVRFLOW BIT
   # ================================


Fixed error log function

Fixed error log function

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -15,9 +15,9 @@
     else
       log = console.log.bind console
     if typeof errFunc is 'function'
-      error = logFunc
+      error = errFunc
     else
-      error = console.error.bind console
+      error ?= console.error.bind console
     log 'Debugging Flash Loader'
   else
     log = error = doNothing


Center background icon on annotation lines

--- src/controller.coffee
+++ src/controller.coffee
@@ -4321,6 +4321,7 @@
   # and graphics
   if @annotations[line]?
     lineDiv.className += ' droplet_' + getMostSevereAnnotationType(@annotations[line])
+    lineDiv.style.backgroundPosition = "2px #{treeView.distanceToBase[line].above - @view.opts.textHeight - @fontAscent}px"
     lineDiv.title = @annotations[line].map((x) -> x.text).join('\n')

   # Add breakpoint graphics


Scoping bug fix

Scoping bug fix

--- src/controller.coffee
+++ src/controller.coffee
@@ -4235,7 +4235,7 @@
   @annotations = {}
   @breakpoints = {}

-  @aceEditor.on 'guttermousedown', (e) ->
+  @aceEditor.on 'guttermousedown', (e) =>
     # Ensure that the click actually happened
     # on a line and not just in gutter space.
     target = e.domEvent.target


Remove timing code

Remove timing code

--- lib/fuzzy-finder.coffee
+++ lib/fuzzy-finder.coffee
@@ -12,10 +12,7 @@

     if atom.project.getPath()?
       PathLoader = require './path-loader'
-      s = Date.now()
-      @loadPathsTask = PathLoader.startTask (paths) =>
-        @projectPaths = paths
-        console.log @projectPaths.length, Date.now() - s
+      @loadPathsTask = PathLoader.startTask (paths) => @projectPaths = paths

     for editor in atom.project.getEditors()
       editor.lastOpened = state[editor.getPath()]


Test for undefinedness instead of 0.

Test for undefinedness instead of 0.

--- src/git-repository.coffee
+++ src/git-repository.coffee
@@ -369,7 +369,10 @@
     @getCachedRelativePathStatus(relativePath)

   getCachedRelativePathStatus: (relativePath) ->
-    @statusesByPath[relativePath] ? @async.getCachedPathStatuses()[relativePath]
+    cachedStatus = @statusesByPath[relativePath]
+    return cachedStatus if cachedStatus?
+
+    @async.getCachedPathStatuses()[relativePath]

   # Public: Returns true if the given status indicates modification.
   #


fix logout issue

fix logout issue

--- src/master/worker_manager.coffee
+++ src/master/worker_manager.coffee
@@ -165,7 +165,8 @@
                 console.log "cannot find instance for request #{path}"
                 return {worker : @getMostFreeWorker() , redirect : requestAppInfo.mountPoint}
         else
-            return {worker : @getMostFreeWorker() , redirect : requestAppInfo.mountPoint}
+            #if no appInstanceId in the url, map to any worker, using the original url
+            return {worker : @getMostFreeWorker()}