adds gallery and institutions to dropdowns

--- apps/artist_new/aggregations.coffee
+++ apps/artist_new/aggregations.coffee
@@ -1 +1 @@
-module.exports = ['price_range', 'medium', 'period', 'total']+module.exports = ['price_range', 'medium', 'period', 'total', 'gallery', 'institution']


client: support: dont open support on each stack editor visit

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -29,14 +29,11 @@
     { mainController, groupsController, computeController } = kd.singletons
     { setSelectedMachineId, setSelectedTemplateId } = EnvironmentFlux.actions

-    groupsController.ready ->
-      if groupsController.canEditGroup()
-        Intercom?('show')
-      else
-        mainController.tellChatlioWidget 'isShown', {}, (err, isShown) ->
-          return if err
-          return if isShown
-          mainController.tellChatlioWidget 'show', { expanded: no }
+    unless groupsController.canEditGroup()
+      mainController.tellChatlioWidget 'isShown', {}, (err, isShown) ->
+        return if err
+        return if isShown
+        mainController.tellChatlioWidget 'show', { expanded: no }

     setSelectedMachineId null
     if stackTemplateId


add missing hidden answer types

add missing hidden answer types

--- app/assets/javascripts/hanuman/models/answer_type.js.coffee
+++ app/assets/javascripts/hanuman/models/answer_type.js.coffee
@@ -12,6 +12,10 @@
     if @.get('name') == 'checkboxlist'
       return true
   ).property('name')
+  chosenmultiselect: (->
+    if @.get('name') == 'chosenmultiselect'
+      return true
+  ).property('name')
   chosenmultiselectgrouped: (->
     if @.get('name') == 'chosenmultiselectgrouped'
       return true
@@ -24,8 +28,8 @@
     if @.get('name') == 'helperabove'
       return true
   ).property('name')
-  hiddendate: (->
-    if @.get('name') == 'hiddendate'
+  helperbelow: (->
+    if @.get('name') == 'helperbelow'
       return true
   ).property('name')
   line: (->
@@ -56,4 +60,56 @@
     if @.get('name') == 'textarea'
       return true
   ).property('name')
+  hiddendate: (->
+    if @.get('name') == 'hiddendate'
+      return true
+  ).property('name')
+  hiddensection: (->
+    if @.get('name') == 'hiddensection'
+      return true
+  ).property('name')
+  hiddenstatic: (->
+    if @.get('name') == 'hiddenstatic'
+      return true
+  ).property('name')
+  hiddenline: (->
+    if @.get('name') == 'hiddenline'
+      return true
+  ).property('name')
+  hiddenhelperabove: (->
+    if @.get('name') == 'hiddenhelperabove'
+      return true
+  ).property('name')
+  hiddenhelperbelow: (->
+    if @.get('name') == 'hiddenhelperbelow'
+      return true
+  ).property('name')
+  hiddencheckboxlist: (->
+    if @.get('name') == 'hiddencheckboxlist'
+      return true
+  ).property('name')
+  hiddenchosenmultiselect: (->
+    if @.get('name') == 'hiddenchosenmultiselect'
+      return true
+  ).property('name')
+  hiddenchosenmultiselectgrouped: (->
+    if @.get('name') == 'hiddenchosenmultiselectgrouped'
+      return true
+  ).property('name')
+  hiddenradio: (->
+    if @.get('name') == 'hiddenradio'
+      return true
+  ).property('name')
+  hiddenselect: (->
+    if @.get('name') == 'hiddenselect'
+      return true
+  ).property('name')
+  hiddentext: (->
+    if @.get('name') == 'hiddentext'
+      return true
+  ).property('name')
+  hiddentextarea: (->
+    if @.get('name') == 'hiddentextarea'
+      return true
+  ).property('name')
 })


Set afterMessageId even if the message is already in the store

--- assets/javascripts/models.js.coffee
+++ assets/javascripts/models.js.coffee
@@ -68,46 +68,48 @@

     for messageAttrs in messages
       if @store.recordIsLoaded("message", messageAttrs.id)
-        message = @store.getById("message", messageAttrs.id)
-        if !@get("room.messages").contains(message)
-          @get("room.messages")[addAction](message)
-        return
+        console.log "message already in store"
+        # message = @store.getById("message", messageAttrs.id)
+        # keys = []
+        # for msg in @get("room.messages")
+        #   keys.push(msg.get("id"))

-      message = @store.push("message", {
-        id: messageAttrs.id,
-        type: messageAttrs.type,
-        body: messageAttrs.body,
-        createdAt: messageAttrs.created_at
-      })
-      message.set("room", @get("room"))
+        # if keys.indexOf(message.id) == -1
+        #   @get("room.messages")[addAction](message)
+      else
+        message = @store.push("message", {
+          id: messageAttrs.id,
+          type: messageAttrs.type,
+          body: messageAttrs.body,
+          createdAt: messageAttrs.created_at
+        })

-      if @store.recordIsLoaded("user", messageAttrs.user.id)
-        user = @store.getById("user", messageAttrs.user.id)
-      else
-        userParams =
-          id: messageAttrs.user.id
-          name: messageAttrs.user.name
-          role: messageAttrs.user.role
-          color: App.paintBox.getColor()
-        user = @store.push("user", userParams)
+        message.set("room", @get("room"))

-      message.set("user", user)
-      @get("room.messages")[addAction](message)
+        if @store.recordIsLoaded("user", messageAttrs.user.id)
+          user = @store.getById("user", messageAttrs.user.id)
+        else
+          userParams =
+            id: messageAttrs.user.id
+            name: messageAttrs.user.name
+            role: messageAttrs.user.role
+            color: App.paintBox.getColor()
+          user = @store.push("user", userParams)

-      if message.get("body").match(@get("user.name")) && addAction == "pushObject" && @get("afterMessageId")
-        @set("notification", true)
-        console.log "set notifications to true"
-        App.notifyBySound()
+        message.set("user", user)
+        @get("room.messages")[addAction](message)

-      if @get("room.messages.length") == (MogoChat.config.messagesPerLoad + 1) && addAction == "pushObject"
-        @get("room.messages").shiftObject()
-        @set("room.isHistoryAvailable", true)
+        if message.get("body").match(@get("user.name")) && addAction == "pushObject" && @get("afterMessageId")
+          @set("notification", true)
+          console.log "set notifications to true"
+          App.notifyBySound()
+
+        if @get("room.messages.length") == (MogoChat.config.messagesPerLoad + 1) && addAction == "pushObject"
+          @get("room.messages").shiftObject()
+          @set("room.isHistoryAvailable", true)

     if messages.length > 0 && !data.before
-      console.log "setting after msg id"
       @set("afterMessageId", messages[messages.length - 1].id)
-    else
-      console.log "not setting after message id"

 App.Message = DS.Model.extend


Unfold lines before trying to scroll

Unfold lines before trying to scroll

(closes travis-ci/travis-ci#1501)

--- assets/scripts/app/views/log.coffee
+++ assets/scripts/app/views/log.coffee
@@ -46,11 +46,15 @@

     createEngine: ->
       console.log 'log view: create engine' if Log.DEBUG
-      @scroll = new Log.Scroll
+      @scroll = new Log.Scroll beforeScroll: =>
+        @unfoldHighlight()
       @engine = Log.create(limit: Log.LIMIT, listeners: [@scroll])
       @logFolder = new Travis.LogFolder(@$().find('#log'))
       @lineSelector = new Travis.LinesSelector(@$().find('#log'), @scroll, @logFolder)
       @observeParts()
+
+    unfoldHighlight: ->
+      @lineSelector.unfoldLines()

     observeParts: ->
       parts = @get('log.parts')
@@ -85,7 +89,10 @@

     noop: -> # TODO required?

-Log.Scroll = ->
+Log.Scroll = (options) ->
+  options ||= {}
+  @beforeScroll = options.beforeScroll
+  this
 Log.Scroll.prototype = $.extend new Log.Listener,
   insert: (log, data, pos) ->
     @tryScroll() if @numbers
@@ -93,6 +100,8 @@

   tryScroll: ->
     if element = $("#log p:visible.highlight:first")
+      if @beforeScroll
+        @beforeScroll()
       $('#main').scrollTop(0)
       $('html, body').scrollTop(element.offset()?.top - (window.innerHeight / 3)) # weird, html works in chrome, body in firefox


Removes debug message.

Removes debug message.

--- app/assets/javascripts/voluntary_ranking/routes/compare_things/compare_things_route.js.coffee
+++ app/assets/javascripts/voluntary_ranking/routes/compare_things/compare_things_route.js.coffee
@@ -4,7 +4,6 @@
     @controllerFor('compare_things').set 'rightThingName', params.right_thing_name

     unless window.top.location.hash.indexOf('/arguments/') > -1
-      alert 'load arguments: ' + params.left_thing_name + ', ' + params.right_thing_name
       @transitionTo('compare_things.arguments', params.left_thing_name, params.right_thing_name, 'both', 1)

     { leftThingName: params.left_thing_name, rightThingName: params.right_thing_name }


Calculator minus as negative [Fixes #4727671]

--- javascripts/lib/math/expression_to_string.coffee
+++ javascripts/lib/math/expression_to_string.coffee
@@ -52,9 +52,9 @@

         ref.forType(comps.classes.subtraction, {
           toString: ->
-            "*"
+            "-"
           toHTMLString: ->
-            "×"
+            "-"
           })

         ref.forType(comps.classes.expression, {


fix replication filter

fix replication filter

--- backend/remote_event_watcher.coffee
+++ backend/remote_event_watcher.coffee
@@ -81,7 +81,7 @@
                 isDocType = doc.docType?
                 isFolder = false
                 isFile = false
-                if isDocType?
+                if isDocType
                     isFolder = doc.docType.toLowerCase() is 'folder'
                     isFile = doc.docType.toLowerCase() is 'file'
                 isDocType and (isFolder or isFile)


top panel: each has x-axis label (they _do_ fit)

--- car_crashes/car_crashes.coffee
+++ car_crashes/car_crashes.coffee
@@ -33,7 +33,7 @@
     top_panel_var = ["total", "not_distracted", "speeding", "alcohol", "ins_premium", "ins_losses"]
     xlim = [[0,25],[0,25],[0,25],[0,25],[0,1500],[0,200]]
     nxticks = [6,6,6,6, 4, 5]
-    xlab = ["", "", "", "", "Dollars", "Dollars"]
+    xlab = ["Crashes per billion miles", "Crashes per billion miles", "Crashes per billion miles", "Crashes per billion miles", "Dollars", "Dollars"]
     title = ["Total", "Not distracted", "Speeding", "Alcohol", "Ave. Ins. premium", "Ave. Ins. Losses"]
     dotplots = []
     for i of top_panel_var
@@ -78,13 +78,5 @@
                      .style("text-anchor", "end")
                      .attr("id", (d,i) -> "text#{i}")

-    # add x-axis label for first four panels
-    svg.append("g").attr("class", "x axis")
-       .append("text")
-       .attr("x", statenamewidth + 2*fullpanelwidth_top)
-       .attr("y", margin_top.top + panelheight_top + 25)
-       .attr("class", "title")
-       .text("Crashes per billion miles")
-
 # load the data and make the plot
 d3.json("data.json", plot)


Stub PartnerView::updateUrl

Stub PartnerView::updateUrl

--- apps/galleries_institutions/client/routes/test/index.coffee
+++ apps/galleries_institutions/client/routes/test/index.coffee
@@ -16,6 +16,7 @@
       PartnersView.__set__ 'PrimaryCarousel', sinon.stub()
       PartnersView.__set__ 'SearchResultsView', sinon.stub()
       PartnersView.__set__ 'FetchFilterPartners', sinon.stub()
+      sinon.stub PartnersView::, 'updateUrl'
       $.fn.typeahead = sinon.stub()
       done()


Refactored scoping, scoping macros

Refactored scoping, scoping macros

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -187,7 +187,6 @@

 class Context
   constructor: ->
-    @_macros = builtInMacros()
     @expand = {}
     @definitions = []
     @_isOperator = []
@@ -198,7 +197,7 @@
     @statement = []
     @cacheScopes = [[]]
     @_assignTos = []
-    topScope = @_augmentScope builtInDefinitions(), @scopeIndex = 0
+    topScope = @_augmentScope builtInDefinitions(), builtInMacros(), @scopeIndex = 0
     topScope.typeNames = builtInTypeNames()
     topScope.topLevel = yes
     @scopes = [topScope]
@@ -206,13 +205,6 @@
     @classParams = newMap()
     @types = []
     @isMalformed = no
-
-  macros: ->
-    @_macros
-
-  addMacro: (name, macro) ->
-    @_macros[name.symbol] = macro
-    name.id = macro.id = @freshId()

   markMalformed: ->
     @isMalformed = yes
@@ -341,10 +333,11 @@
     @scopes[@scopes.length - 2]

   newScope: ->
-    @scopes.push @_augmentScope newMap(), ++@scopeIndex
-
-  _augmentScope: (scope, index) ->
+    @scopes.push @_augmentScope newMap(), newMap(), ++@scopeIndex
+
+  _augmentScope: (scope, macros, index) ->
     scope.index = index
+    scope.macros = macros
     scope.deferred = []
     scope.deferredBindings = []
     scope.boundTypeVariables = newSet()
@@ -435,34 +428,62 @@
     any (for {className} in type.constraints
       lookupInMap (@classNamed className).declarations, name)

+  ## Macro declarations
+
+  declareMacro: (name, macro) ->
+    name.id = macro.id = @freshId()
+    addToMap @_scope().macros, name.symbol, macro
+
+  isMacroDeclared: (name) ->
+    !!@macro name
+
+  macro: (name) ->
+    @_macroInScope @scopes.length - 1, name
+
+  _macroInScope: (i, name) ->
+    (lookupInMap @scopes[i].macros, name) or
+      (not @_declarationInScope i, name) and i > 0 and (@_macroInScope i - 1, name) or
+      undefined # throw "Could not find macro for #{name}"
+
+  ## In-scope Declarations
+  # For each name in a scope:
+  #   type: (? Type)
+  #   arity: (? [String])
+  #   id: Int
+  #   final: Bool
+
   isDeclared: (name) ->
     !!(@_declaration name)

+  isCurrentlyDeclared: (name) ->
+    (declaration = @_declarationInCurrentScope name) and declaration.final
+
   isTyped: (name) ->
     !!@type name

-  isActuallyTyped: (name) ->
-    !!@actualType name
-
-  _declaration: (name) ->
-    @_declarationInScope @scopes.length - 1, name
-
-  _declarationInScope: (i, name) ->
-    (lookupInMap @scopes[i], name) or
-      i > 0 and (@_declarationInScope i - 1, name) or
-      undefined # throw "Could not find declaration for #{name}"
-
-  _scopeOfDeclared: (i, name) ->
-    (lookupInMap @scopes[i], name) and @scopes[i] or
-      i > 0 and (@_scopeOfDeclared i - 1, name) or
-      undefined
-
-  isCurrentlyDeclared: (name) ->
-    !!(lookupInMap @_scope(), name)
+  isPreTyped: (name) ->
+    !!(@preDeclaredType name)
+
+  isFinallyTyped: (name) ->
+    !!@finalType name
+
+  typeForId: (id) ->
+    @types[id]
+
+  type: (name) ->
+    (@_declaration name)?.type
+
+  finalType: (name) ->
+    (type = @type name) and (type not instanceof TempType) and type
+
+  preDeclaredType: (name) ->
+    (@_preDeclaration name)?.type
+
+  currentDeclarations: ->
+    cloneMap @_scope()

   assignType: (name, type) ->
-    # log "TYPE OF #{name}", printType type
-    if declaration = (lookupInMap @_scope(), name)
+    if declaration = @_declarationInCurrentScope name
       if declaration.type and declaration.type not instanceof TempType
         throw new Error "assignType: #{name} already has a type"
       declaration.type = type
@@ -470,36 +491,51 @@
     else
       throw new Error "assignType: #{name} is not declared"

-  typeForId: (id) ->
-    @types[id]
-
-  currentDeclarations: ->
-    cloneMap @_scope()
-
-  declareArity: (name, arity, id) ->
-    @declare name, {arity, id}
-
-  # Returns whether the declaration was successful (not redundant)
-  declare: (name, declaration = {}) ->
-    if lookupInMap @_scope(), name
-      false
-    else
-      declaration.id ?= @freshId()
-      addToMap @_scope(), name, declaration
-      true
+  assignArity: (name, arity) ->
+    (@_declarationInCurrentScope name).arity = arity
+
+  declareTyped: (names, types) ->
+    for name, i in names
+      @declare name, type: types[i]
+
+  declare: (name, declaration) ->
+    declaration.final = yes
+    @_declare name, declaration
+
+  preDeclare: (name, declaration) ->
+    declaration.final = no
+    @_declare name, declaration
+
+  _declare: (name, declaration) ->
+    declaration.id ?= @freshId()
+    replaceOrAddToMap @_scope(), name, declaration
+
+  _preDeclaration: (name) ->
+    (declaration = @_declarationInCurrentScope name) and not declaration.final and declaration
+
+  _declarationInCurrentScope: (name) ->
+    lookupInMap @_scope(), name
+
+  _declaration: (name) ->
+    @_lookupDeclaration @scopes.length - 1, name
+
+  _lookupDeclaration: (i, name) ->
+    (@_declarationInScope i, name) or
+      i > 0 and (@_lookupDeclaration i - 1, name) or
+      undefined # throw "Could not find declaration for #{name}"
+
+  _scopeOfDeclared: (i, name) ->
+    (@_declarationInScope i, name) and @scopes[i] or
+      i > 0 and (@_scopeOfDeclared i - 1, name) or
+      undefined
+
+  _declarationInScope: (i, name) ->
+    lookupInMap @scopes[i], name

   freshId: ->
     @nameIndex++

-  declareTypes: (names, types) ->
-    for name, i in names
-      @declare name, type: types[i]
-
-  type: (name) ->
-    (@_declaration name)?.type
-
-  actualType: (name) ->
-    (type = @type name) and (type not instanceof TempType) and type
+  ## Deferring

   addToDeferredNames: (binding) ->
     @_deferrableDefinition().deferredBindings.push binding
@@ -624,10 +660,9 @@
   operator = _operator call
   operatorName = _symbol operator
   if isName operator
-
     (if isDotAccess operator
       callJsMethodCompile
-    else if operatorName of ctx.macros() and not ctx.arity operatorName
+    else if (ctx.isMacroDeclared operatorName) and not ctx.isDeclared operatorName
       callMacroCompile
     else if (isFake operator) or (ctx.isDeclared operatorName) and not ctx.arity operatorName
       callUnknownCompile
@@ -644,7 +679,7 @@
 callMacroCompile = (ctx, call) ->
   op = _operator call
   op.label = 'keyword'
-  macro = ctx.macros()[op.symbol]
+  macro = ctx.macro op.symbol
   op.id = macro.id
   expanded = macro ctx, call
   if isTranslated expanded
@@ -712,7 +747,7 @@
         compiled
       else
         compiled =
-          if operator.symbol of ctx.macros()
+          if ctx.isMacroDeclared operator.symbol
             callMacroCompile ctx, sortedCall
           else
             callSaturatedKnownCompile ctx, sortedCall
@@ -1116,12 +1151,12 @@

   definedNames = ctx.definedNames()

-  # log "is deferriing", pattern, ctx.shouldDefer()
+  # log "is deferriing", (print pattern), ctx.shouldDefer()
   # Make sure deferred names are added to scope so they are compiled within functions
   if ctx.shouldDefer()
-    for {name} in definedNames
-      if not ctx.arity name
-        ctx.declare name
+    for {name, id} in definedNames
+      if not ctx.isCurrentlyDeclared name
+        ctx.declare name, id: id
     #log "exiting pattern early", pattern, "for", ctx.shouldDefer()
     return {}

@@ -1161,7 +1196,7 @@
   # For explicitly typed bindings, we need to check that the inferred type
   #   corresponds to the annotated
   currentType = substitute ctx.substitution, type
-  if ctx.isActuallyTyped name
+  if ctx.isFinallyTyped name
     # TODO: check class constraints
     if not includesJsType currentType.type
       # Check the declared type
@@ -1265,7 +1300,7 @@
       # First get rid of instances of already resolved types
       unresolvedNames = newMap()
       for name, binding of values bindings
-        if canonicalType = ctx.actualType name
+        if canonicalType = ctx.finalType name
           for type in binding.types
             unify ctx, type.type, (freshInstance ctx, canonicalType).type
         else
@@ -1281,7 +1316,7 @@
           # log "done unifying one"

         # have to promote constraints from just compiled dependencies
-        depConstraints = concat (for dep in binding.deps or [] when cononicalType = ctx.actualType dep.name
+        depConstraints = concat (for dep in binding.deps or [] when cononicalType = ctx.finalType dep.name
           constraintsFromCanonicalType ctx, cononicalType, dep.type)
         allConstraints = (join binding.constraints or [], depConstraints)

@@ -1311,7 +1346,7 @@
     while (_notEmpty ctx.deferred()) and deferredCount < ctx.deferred().length
       prevSize = ctx.deferred().length
       [expression, dependencyName, lhs, rhs] = deferred = ctx.deferred().shift()
-      if ctx.isActuallyTyped dependencyName
+      if ctx.isFinallyTyped dependencyName
         compiledPairs.push definitionPairCompile ctx, lhs, rhs
         deferredCount = 0
       else
@@ -1362,11 +1397,15 @@

       # Arity - before deferring instead? put to assignCompile, because this makes the naming of functions special
       if name = ctx.isAtSimpleDefinition()
-        #log "adding arity for #{ctx.definitionName()}", paramNames
-        ctx.declareArity name, paramNames, ctx.definitionId()
         # Explicit typing
         if type
-          explicitType = assignExplicitType ctx, typeConstrainedCompile ctx, type
+          explicitType = declareExplicitlyTyped ctx, typeConstrainedCompile ctx, type
+          ctx.assignArity name, paramNames
+        else
+          ctx.declare name,
+            arity: paramNames
+            id: ctx.definitionId()
+            type: ctx.preDeclaredType name # if any

       newParamType = (param) ->
         markOrigin (ctx.freshTypeVariable star), param
@@ -1375,9 +1414,12 @@
       ctx.newLateScope()
       ctx.bindTypeVariables (map (({name}) -> name), paramTypeVars)
       # log "adding types", (map _symbol, params), paramTypes
-      ctx.declareTypes paramNames, paramTypes
+      ctx.declareTyped paramNames, paramTypes
       for param in params
         param.id = ctx.declarationId _symbol param
+
+      # Declare wheres first so they properly shadow parent scope
+      preDeclarePatterns ctx, _fst unzip pairs wheres

       #log "compiling wheres", pairs wheres
       compiledWheres = definitionListCompile ctx, pairs wheres
@@ -1434,14 +1476,28 @@
             #     body: (join compiledWheres, [(jsReturn compiledBody)]))])

 # Assumes definition name
-assignExplicitType = (ctx, type) ->
+declareExplicitlyTyped = (ctx, type) ->
   explicitType = quantifyUnbound ctx, type
   name = ctx.definitionName()
-  if ctx.isTyped name
+  if ctx.isPreTyped name
+    # TODO: unify explicit types like in inferType
+    explicitType = ctx.preDeclaredType name
+  if ctx.isCurrentlyDeclared name
     malformed ctx, ctx.definitionPattern(), 'This name is already taken'
   else
-    ctx.assignType name, explicitType
+    ctx.declare name,
+      id: ctx.definitionId()
+      type: explicitType
   explicitType
+
+preDeclarePatterns = (ctx, patterns) ->
+  for pattern in patterns
+    ctx.definePattern pattern
+    ctx.doDefer undefined, undefined
+    ctx.setAssignTo yes
+    compiled = patternCompile ctx, pattern
+    ctx.resetAssignTo()
+    ctx.leaveDefinition()

 ms.type = ms_type = (ctx, call) ->
   hasName = requireName ctx, 'Name required to declare new type alias'
@@ -1592,8 +1648,7 @@
       else
         typeConstrainedCompile ctx, call
     if hasName
-      ctx.declare ctx.definitionName()
-      assignExplicitType ctx, compiledType
+      declareExplicitlyTyped ctx, compiledType
     # TODO: support typing of expressions,
     #watch out of definition patterns without a name
     jsNoop()
@@ -1607,8 +1662,7 @@
     expression = constraintSeq
   compiledType = new Constrained constraints, typeCompile ctx, type
   if name = ctx.definitionName()
-    ctx.declare ctx.definitionName()
-    assignExplicitType ctx, compiledType
+    declareExplicitlyTyped ctx, compiledType
   call.tea = compiledType
   assignCompile ctx, call, (termCompile ctx, expression)

@@ -1807,8 +1861,10 @@
     quantifiedType = quantifyUnbound ctx, instanceSpecificType
     # Prefix so the instance method doesn't shadow the class method
     prefixedName = instancePrefix instanceName, name
-    ctx.declareArity prefixedName, arity
-    ctx.assignType prefixedName, quantifiedType
+    # Check against redefing a method
+    ctx.preDeclare prefixedName,
+      arity: arity
+      type: quantifiedType
   freshInstanceType

 prefixWithInstanceName = (ctx, definitionPairs, instanceName) ->
@@ -1943,7 +1999,7 @@
   [paramTuple, rest...] = _arguments call
   [body] = rest

-  # ctx.addMacro ctx.definitionName(), (ctx) ->
+  # ctx.declareMacro ctx.definitionName(), (ctx) ->
   #   expressionCompile ctx, body

   if hasName
@@ -1954,13 +2010,13 @@
     compiledMacro = translateToJs translateIr ctx,
         (termCompile ctx, call_ (token_ 'fn'), (join [paramTuple], rest))
     macroFn = eval compiledMacro
-    ctx.addMacro ctx.definitionPattern(), (ctx, call) ->
+    ctx.declareMacro ctx.definitionPattern(), (ctx, call) ->
       # operatorCompile ctx, call
       # args = termsCompile ctx, (_arguments call)[0..macroFn.length]
       #callTyping ctx, call
       constantToSource macroFn (_arguments call)...

-    # ctx.addMacro macroName, (ctx, call) ->
+    # ctx.declareMacro macroName, (ctx, call) ->
     #   compiled = termCompile ctx, (fn_ params, body)
     #   assignCompile ctx, body, compiled

@@ -2064,40 +2120,44 @@

 ms.macro = ms_macro = (ctx, call) ->
   hasName = requireName ctx, 'Name required to declare a new instance'
-  [paramTuple, type, rest...] = _arguments call
+  [paramTuple, body...] = _arguments call
+  [type, macroBody...] = body

   if hasName
     macroName = ctx.definitionName()
-    redefining = ctx.macros()[macroName]
+    # TODO: remove along with the below
+    redefining = ctx.isMacroDeclared macroName

     # Register type
     params = _terms paramTuple
     paramNames = map _symbol, params
-    if not type or not isTypeAnnotation type
-      malformed ctx, call, "Type annotation required"
-      rest = join [type], rest
+    if (not type or not isTypeAnnotation type)
+      if (not ctx.isPreTyped macroName)
+        malformed ctx, call, "Type annotation required"
+      macroBody = join [type], macroBody
     else if not redefining
-      type = quantifyUnbound ctx, typeConstrainedCompile ctx, type
-      if not ctx.isDeclared macroName
-        ctx.declare macroName,
-          arity: paramNames
-          type: type
-      call.tea = type
-
-    if not rest.length > 0
-      return malformed ctx, call, "Macro body missing"
+      # TODO: remove and inherit from compiled
+      call.tea = quantifyUnbound ctx, typeConstrainedCompile ctx, type
+      # if not ctx.isDeclared macroName
+      #   ctx.declare macroName,
+      #     arity: paramNames
+      #     type: type
+      # call.tea = type
+
+    # if not macroBody.length > 0
+    #   return malformed ctx, call, "Macro body missing"

     #macroFn = transform call
     compiledMacro = translateToJs translateIr ctx,
-      (termCompile ctx, call_ (token_ 'fn'), (join [paramTuple], rest))
+      (termCompile ctx, call_ (token_ 'fn'), (join [paramTuple], macroBody))
     # log compiledMacro
     params = (map token_, paramNames) # freshen

     if redefining
       malformed ctx, ctx.definitionPattern(), "Macro with this name already defined"
     else
-      ctx.addMacro ctx.definitionPattern(), simpleMacro eval compiledMacro
-      fn_ params, call_ (token_ macroName), params
+      ctx.declareMacro ctx.definitionPattern(), simpleMacro eval compiledMacro
+      typedFn_ params, type, call_ (token_ macroName), params

 simpleMacro = (macroFn) ->
   (ctx, call) ->
@@ -2169,10 +2229,7 @@
 #     assignCompile ctx, call, (jsBinary "+", compiled_x, compiled_b)

 builtInMacros = ->
-  copy = {}
-  for key, val of ms
-    copy[key] = val
-  copy
+  objectToMap ms

 # Creates the condition and body of a branch inside match macro
@@ -2586,6 +2643,9 @@

 fn_ = (params, body) ->
   (call_ (token_ 'fn'), [(tuple_ params), body])
+
+typedFn_ = (params, type, body) ->
+  (call_ (token_ 'fn'), [(tuple_ params), type, body])

 token_ = (string) ->
   (tokenize string)[0]
@@ -2693,8 +2753,8 @@
   else
     finalType = addConstraintsFrom ctx, {id, type}, substitute ctx.substitution, type
     classParams = dictsForConstraint ctx, finalType.constraints
-    params = map validIdentifier, arity
     if classParams.length > 0
+      params = map validIdentifier, arity
       (irFunctionTranslate ctx,
         params: params
         body: [(jsReturn (jsCall (validIdentifier name), (join classParams, params)))])
@@ -3201,6 +3261,8 @@
     colorize(theme[labelOf node, parent], symbol)

 print = (ast) ->
+  if not ast
+    return ast
   collapse crawl ast, (node, symbol) -> symbol

 labelOf = (node, parent) ->
@@ -3595,88 +3657,88 @@
 # Simple macros and builtin functions
 # TODO: reimplement the simple macros

-macros =
-  'if': (cond, zen, elz) ->
-    """(function(){if (#{cond}) {
-      return #{zen};
-    } else {
-      return #{elz};
-    }}())"""
-  'access': (field, obj) ->
-    # TODO: use dot notation if method is valid field name
-    "(#{obj})[#{field}]"
-  'call': (method, obj, args...) ->
-    "(#{macros.access method, obj}(#{args.join ', '}))"
-  'new': (clazz, args...) ->
-    "(new #{clazz}(#{args.join ', '}))"
-
-
-expandBuiltings = (mapping, cb) ->
-  for op, i in mapping.from
-    macros[op] = cb mapping.to[i]
-
-unaryFnMapping =
-  from: 'sqrt alert! not empty'.split ' '
-  to: 'Math.sqrt window.log ! $empty'.split ' '
-
-expandBuiltings unaryFnMapping, (to) ->
-  (x) ->
-    if x
-      "#{to}(#{x})"
-    else
-      "function(__a){return #{to}(__a);}"
-
-binaryFnMapping =
-  from: []
-  to: []
-
-expandBuiltings binaryFnMapping, (to) ->
-  (x, y) ->
-    if x and y
-      "#{to}(#{x}, #{y})"
-    else if x
-      "function(__b){return #{to}(#{a}, __b);}"
-    else
-      "function(__a, __b){return #{to}(__a, __b);}"
-
-invertedBinaryFnMapping =
-  from: '^'.split ' '
-  to: 'Math.pow'.split ' '
-
-expandBuiltings invertedBinaryFnMapping, (to) ->
-  (x, y) ->
-    if x and y
-      "#{to}(#{y}, #{x})"
-    else if x
-      "function(__b){return #{to}(__b, #{a});}"
-    else
-      "function(__a, __b){return #{to}(__b, __a);}"
-
-binaryOpMapping =
-  from: '+ * = != and or'.split ' '
-  to: '+ * == != && ||'.split ' '
-
-expandBuiltings binaryOpMapping, (to) ->
-  (x, y) ->
-    if x and y
-      "(#{x} #{to} #{y})"
-    else if x
-      "function(__b){return #{x} #{to} __b;}"
-    else
-      "function(__a, __b){return __a #{to} __b;}"
-
-invertedBinaryOpMapping =
-  from: '- / rem < > <= >='.split ' '
-  to: '- / % < > <= >='.split ' '
-
-expandBuiltings invertedBinaryOpMapping, (to) ->
-  (x, y) ->
-    if x and y
-      "(#{y} #{to} #{x})"
-    else if x
-      "function(__b){return __b #{to} #{x};}"
-    else
-      "function(__a, __b){return __b #{to} __a;}"
+# macros =
+#   'if': (cond, zen, elz) ->
+#     """(function(){if (#{cond}) {
+#       return #{zen};
+#     } else {
+#       return #{elz};
+#     }}())"""
+#   'access': (field, obj) ->
+#     # TODO: use dot notation if method is valid field name
+#     "(#{obj})[#{field}]"
+#   'call': (method, obj, args...) ->
+#     "(#{macros.access method, obj}(#{args.join ', '}))"
+#   'new': (clazz, args...) ->
+#     "(new #{clazz}(#{args.join ', '}))"
+
+
+# expandBuiltings = (mapping, cb) ->
+#   for op, i in mapping.from
+#     macros[op] = cb mapping.to[i]
+
+# unaryFnMapping =
+#   from: 'sqrt alert! not empty'.split ' '
+#   to: 'Math.sqrt window.log ! $empty'.split ' '
+
+# expandBuiltings unaryFnMapping, (to) ->
+#   (x) ->
+#     if x
+#       "#{to}(#{x})"
+#     else
+#       "function(__a){return #{to}(__a);}"
+
+# binaryFnMapping =
+#   from: []
+#   to: []
+
+# expandBuiltings binaryFnMapping, (to) ->
+#   (x, y) ->
+#     if x and y
+#       "#{to}(#{x}, #{y})"
+#     else if x
+#       "function(__b){return #{to}(#{a}, __b);}"
+#     else
+#       "function(__a, __b){return #{to}(__a, __b);}"
+
+# invertedBinaryFnMapping =
+#   from: '^'.split ' '
+#   to: 'Math.pow'.split ' '
+
+# expandBuiltings invertedBinaryFnMapping, (to) ->
+#   (x, y) ->
+#     if x and y
+#       "#{to}(#{y}, #{x})"
+#     else if x
+#       "function(__b){return #{to}(__b, #{a});}"
+#     else
+#       "function(__a, __b){return #{to}(__b, __a);}"
+
+# binaryOpMapping =
+#   from: '+ * = != and or'.split ' '
+#   to: '+ * == != && ||'.split ' '
+
+# expandBuiltings binaryOpMapping, (to) ->
+#   (x, y) ->
+#     if x and y
+#       "(#{x} #{to} #{y})"
+#     else if x
+#       "function(__b){return #{x} #{to} __b;}"
+#     else
+#       "function(__a, __b){return __a #{to} __b;}"
+
+# invertedBinaryOpMapping =
+#   from: '- / rem < > <= >='.split ' '
+#   to: '- / % < > <= >='.split ' '
+
+# expandBuiltings invertedBinaryOpMapping, (to) ->
+#   (x, y) ->
+#     if x and y
+#       "(#{y} #{to} #{x})"
+#     else if x
+#       "function(__b){return __b #{to} #{x};}"
+#     else
+#       "function(__a, __b){return __b #{to} __a;}"

 # end of Simple macros

@@ -4730,7 +4792,7 @@
   definitions = subtractMaps ctx._scope(), what._scope()
   typeNames = subtractMaps ctx._scope().typeNames, what._scope().typeNames
   classes = subtractMaps ctx._scope().classes, what._scope().classes
-  macros = subtractMaps (objectToMap ctx._macros), (objectToMap what._macros)
+  macros = subtractMaps ctx._scope().macros, what._scope().macros
   savedScopes = ctx.savedScopes
   {definitions, typeNames, classes, macros, savedScopes}

@@ -4742,12 +4804,12 @@

 injectContext = (ctx, compiledModule) ->
   {definitions, typeNames, classes, macros} = compiledModule
+  topScope = ctx._scope()
   for name, macro of values macros
-    if ctx._macros[name]
+    if ctx.isMacroDeclared name
       throw new Error "Macro #{name} already defined"
     else
-      ctx._macros[name] = macro
-  topScope = ctx._scope()
+      addToMap topScope.macros, name, macro
   for name, definition of values definitions
     addToMap topScope, name, definition
   topScope.typeNames = concatMaps topScope.typeNames, typeNames
@@ -4896,7 +4958,7 @@

 finalizeTypes = (ctx, ast) ->
   visitExpressions ast, (expression) ->
-    if expression?.label is 'name' and (type = ctx.actualType expression.symbol)
+    if expression?.label is 'name' and (type = ctx.finalType expression.symbol)
       expression.tea = type
     else if expression.tea
       expression.tea = substitute ctx.substitution, expression.tea
@@ -4955,7 +5017,7 @@
 id = (x) -> x

 map = (fn, list) ->
-  if list then list.map fn else (list) -> map fn, list
+  if list then list.map fn

 allMap = (fn, list) ->
   all (map fn, list)
@@ -6041,14 +6103,14 @@

   'shadowing'
   """
-  f (macro [x]
+  f (macro [n]
     (: (Fn Num Num))
-    (Js.binary "+" x 2))
+    (Js.binary "+" n 2))

   g (fn [x]
     y
     y (f x)
-    f (fn [x] x))
+    f (fn [y] y))
   """
   '(g 3)', 3


Fixed class param compilation by validating the name

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -3209,7 +3209,7 @@
     typeMap = nestedLookupInMap classParams, names
     if not typeMap
       nestedAddToMap classParams, names, (typeMap = newMap())
-    dictName = "_#{className}_#{counter[className] ?= 0; ++counter[className]}"
+    dictName = "_#{validIdentifier className}_#{counter[className] ?= 0; ++counter[className]}"
     addToMap typeMap, className, dictName
     dictName
   ctx.addClassParams classParams


Null check

Null check

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -96,7 +96,7 @@
 						editor.completer.autoSelect = true
 						editor.completer.showPopup(editor)
 						editor.completer.cancelContextMenu()
-						$(editor.completer.popup.container).css({'font-size': @$scope.fontSize + 'px'})
+						$(editor.completer.popup?.container).css({'font-size': @$scope.fontSize + 'px'})
 					bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
 				}


Proper error reporting for other unification errors as well

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -3850,26 +3850,30 @@
     unifyFail t1, t2

 unifyFail = (t1, t2) ->
+  typeFail "Types don't match: ", t1, t2
+
+bindVariable = (variable, type) ->
+  if type.TypeVariable and variable.name is type.name
+    emptySubstitution()
+  else if inSet (findFree type), variable.name
+    typeFail "Types cannot match: ", variable, type
+    # newMapWith variable.name, "occurs check failed"
+  else if not kindsEq (kind variable), (kind type)
+    # newMapWith variable.name, "kinds don't match for #{variable.name} and #{safePrintType type}"
+    typeFail "Kinds of types don't match: ", variable, type
+  else
+    newSubstitution variable.name, type
+
+typeFail = (message, t1, t2) ->
   [first, second] =
     if t1.origin?.start > t2.origin?.start
       [t2, t1]
     else
       [t1, t2]
   substituionFail
-    message: "could not unify #{(safePrintType first)}, #{(safePrintType second)}"
+    message: "#{message} #{(safePrintType first)}, #{(safePrintType second)}"
     conflicts: [first.origin, second.origin]

-bindVariable = (variable, type) ->
-  if type.TypeVariable and variable.name is type.name
-    emptySubstitution()
-  else if inSet (findFree type), variable.name
-    substituionFail "occurs check failed for #{variable.name}"
-    # newMapWith variable.name, "occurs check failed"
-  else if not kindsEq (kind variable), (kind type)
-    # newMapWith variable.name, "kinds don't match for #{variable.name} and #{safePrintType type}"
-    substituionFail "kinds don't match for #{variable.name} and #{safePrintType type}"
-  else
-    newSubstitution variable.name, type

 # Maybe substitution if the types match
 # t1 can be more general than t2


fix initialization error

fix initialization error

--- services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
@@ -8,8 +8,7 @@

 		constructor: (@$scope, @editor, @element) ->
 			window.trackChangesManager ?= @
-			@$scope.reviewPanel.entries = {}
-
+
 			@$scope.$watch "changesTracker", (changesTracker) =>
 				return if !changesTracker?
 				@disconnectFromChangesTracker()


Supported splats inside quote

Supported splats inside quote

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2520,10 +2520,14 @@

   commedAtom = (atom, otherwise) ->
     if (_symbol atom)[0] is ','
-      identifier = token_ (_symbol atom)[1...]
+      splat = (_symbol atom)[1..2] is '..'
+      identifier = token_ (_symbol atom)[(if splat then 3 else 1)...]
       compiled = termCompile ctx, identifier
       retrieve atom, identifier
-      compiled
+      if ctx.assignTo()
+        compiled
+      else
+        if splat then (jsMethod compiled, 'toArray', []) else (jsArray [compiled])
     else
       otherwise()

@@ -2554,18 +2558,22 @@

     serializeAst = (ast) ->
       if isForm ast
-        if (_operator ast)?.symbol is ','
+        (jsArray [if (_operator ast)?.symbol is ','
           termCompile ctx, ast
         else
           labelDelimeters ast, 'const'
-          (jsArray (map serializeAst, ast))
+          terms = map serializeAst, ast
+          if _empty terms
+            (jsArray [])
+          else
+            (jsMethod (_fst terms), 'concat', terms[1...])])
       else
         commedAtom ast, ->
           serialized = (jsValue (JSON.stringify ast))
           if (isExpression ast)
             ast.label = 'const'
-          serialized
-    serializeAst expression
+          (jsArray [serialized])
+    _fst (serializeAst expression).elems

 ms[','] = ms_comma = (ctx, call) ->
   expression = firstOrCall _arguments call


Fix looking up constraints for named definitions

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1451,7 +1451,7 @@
           addedDep.scopeIndex ?= dep.scopeIndex
           addedDep.types.push dep.type
           if dep.defining
-            depDeferredConstraints = lookupInMap allDeferredConstraints, dep.scopeIndex
+            depDeferredConstraints = lookupInMap allDeferredConstraints, dep.name
             # If depDeferredConstraints is null the dependency must be mutually dependant
             added.constraints = join added.constraints, depDeferredConstraints or []

@@ -1511,7 +1511,7 @@
           # log "type", name, binding.id, binding.scopeIndex
           deferredConstraints = inferType ctx, binding.id, canonicalType,
             allConstraints, binding.polymorphic, binding.scopeIndex
-          addToMap allDeferredConstraints, binding.scopeIndex, deferredConstraints
+          addToMap allDeferredConstraints, name, deferredConstraints

   ctx.deferredBindings().length = 0 # clear

@@ -3089,13 +3089,14 @@
 #       ^.___ not necessarily, we could have a tuple of functions or similar
 irDefinitionTranslate = (ctx, {type, expression, reference, bare}) ->
   finalType = type#substitute ctx.substitution, type
-  allConstraints = (addConstraintsFrom ctx,
-    {type, name: reference?.name, scopeIndex: reference?.scopeIndex}, finalType).constraints
+  # allConstraints = (addConstraintsFrom ctx,
+  #   {type, name: reference?.name, scopeIndex: reference?.scopeIndex}, finalType).constraints
+
   reducedConstraints =
-    # if declaredType = ctx.typeForId id
-    #   constraintsFromCanonicalType ctx, declaredType, finalType
-    # else
-      (reduceConstraints ctx, allConstraints).success
+    if reference
+      constraintsFromReference ctx, {type, name: reference.name, scopeIndex: reference.scopeIndex}
+    else
+      (reduceConstraints ctx, finalType.constraints).success
   if not reducedConstraints
     return jsNoop()
   if bare and _notEmpty reducedConstraints
@@ -3154,6 +3155,14 @@
   else
     to

+constraintsFromReference = (ctx, {name, type, scopeIndex}) ->
+  if scopeIndex? and (typed = ctx.savedDeclaration name, scopeIndex) and
+      typed.type and
+      (_notEmpty typed.type.type.constraints)
+    constraintsFromCanonicalType ctx, typed.type, type
+  else
+    []
+
 constraintsFromCanonicalType = (ctx, canonicalType, type) ->
   inferredType = freshInstance ctx, canonicalType
   # console.log "canonicalType", canonicalType, type
@@ -3162,7 +3171,12 @@
   # console.log (JSON.stringify type.type)
   ctx.extendSubstitution matchType inferredType.type, type.type#(substitute ctx.substitution, type).type
   # console.log "after", (printType inferredType.type)
+
   reduceConstraints ctx, inferredType.constraints
+  # We also need to match dependent variables
+  for c1 in inferredType.constraints
+    for c2 in type.constraints
+      constraintsEqual c1, c2
   #(substitute ctx.substitution, inferredType).constraints
   inferredType.constraints


log: fail to turn on vm

log: fail to turn on vm

--- client/Main/kite/oskite.coffee
+++ client/Main/kite/oskite.coffee
@@ -79,6 +79,8 @@
       .catch ((it) -> not /ErrQuotaExceeded/.test it.message), (err) =>
         if t < 5
           return Promise.delay(1000 * Math.pow 1.3, ++t).then => @vmOn t
+
+        ErrorLog.create "terminal: vm turn on", attempt:t, reason: err?.message
         throw err
       .then => @emit 'vmOn'


bugfix: infinite terminals

bugfix: infinite terminals

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -101,7 +101,7 @@
       unless vmName
         return @setMessage "It seems you don't have a VM to use with Terminal."

-      @setTerminalTimeout vmName, 15000
+      @setTerminalTimeout vmName, 15000, => @restoreTabs vmName

   showApprovalModal: (remote, command)->
     modal = new KDModalView
@@ -229,7 +229,8 @@

     terminalView.on 'WebTermConnected', @bound 'updateSessions'

-    @setTerminalTimeout options.vmName, 15000
+    @setTerminalTimeout options.vmName, 15000, ->
+      terminalView.connectToTerminal()

     @appendTerminalTab terminalView

@@ -293,7 +294,7 @@
     else
       @createNewTab vmName: vmName, mode: 'create'

-  setTerminalTimeout: (vmName, delayMs) ->
+  setTerminalTimeout: (vmName, delayMs, isRunningCallback) ->
     vmController = KD.getSingleton 'vmController'
     vmController.info vmName, KD.utils.getTimedOutCallback (err, vm, info)=>
       if err
@@ -301,7 +302,7 @@
         KD.mixpanel "Open Webterm, fail", {vmName}

       if info?.state is 'RUNNING'
-        @restoreTabs vmName
+        isRunningCallback()
       else
         vmController.start vmName, (err, state)=>
           warn "Failed to turn on vm:", err  if err


Questions created

Questions created

--- client/coffee/questions.coffee
+++ client/coffee/questions.coffee
@@ -5,4 +5,337 @@
     "Answer 2"
     "Answer 3"
   ]
-]
+  question: "Sport question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Sport question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]
+
+history: [
+  question: "History question 1"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "History question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]
+
+science: [
+  question: "Science question 1"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Science question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]
+
+geography: [
+  question: "Geography question 1"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Geography question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]
+
+literature: [
+  question: "Literature question 1"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Literature question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]
+
+entertainment: [
+  question: "Entertainment question 1"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 2"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 3"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 4"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 5"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 6"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 7"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 8"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+  question: "Entertainment question 9"
+  answers: [
+    "Answer 1"
+    "Answer 2"
+    "Answer 3"
+  ]
+]


Distinguish drag logic with two different helpers.

--- client/finder/lib/index.coffee
+++ client/finder/lib/index.coffee
@@ -63,21 +63,31 @@
       hoverDetect : no
       delegate    : controller

+
+    checkDraggingItem = (event) ->
+
+      { items } = event.originalEvent.dataTransfer
+
+      # If the user isn't dragging a file and internalDragging is false,
+      # show the overlay.
+      if items?[0].kind isnt 'file' and not controller.treeController.internalDragging
+        finderView.overlay?.show()
+        return no
+
+      return yes
+
+    # Handle framework's drag events.
+    onFwDrag = (nodeView, event) -> onDrag()  if checkDraggingItem event
+
+    # Handle HTML's drag events.
+    onNDrag = (event) -> onDrag()  if checkDraggingItem event
+
     onDrag = (event) ->

-      { internalDragging }  = controller.treeController
-
-      if event
-        { items }  = event.originalEvent.dataTransfer
-
-        # If the user isn't dragging a file and internalDragging is false,
-        # show the overlay.
-        if items?[0].kind isnt 'file' and not internalDragging
-          return finderView.overlay?.show()
-
-      unless internalDragging
+      unless controller.treeController.internalDragging
         uploaderPlaceholder.show()
         uploader.unsetClass 'hover'
+

     controller.on 'MachineMounted', (machine, path) ->

@@ -85,12 +95,12 @@
       uploader.reset()

     { treeController } = controller
-    treeController.on 'dragEnter', (nodeView, event) -> onDrag event
-    treeController.on 'dragOver', (nodeView, event) ->  onDrag event
+    treeController.on 'dragEnter', onFwDrag
+    treeController.on 'dragOver',  onFwDrag

     finderView = controller.getView()
-    finderView.on 'dragenter', onDrag # "event" is passing by default.
-    finderView.on 'dragover', onDrag  # "event" is passing by default.
+    finderView.on 'dragenter', onNDrag
+    finderView.on 'dragover',  onNDrag
     finderView.on 'drop', -> finderView.overlay?.hide()

     uploader


Add shortDate format

Add shortDate format

--- client/lanes/lib/format.coffee
+++ client/lanes/lib/format.coffee
@@ -1,4 +1,7 @@
 Lanes.u.format ||= {}
+
+Lanes.u.format.shartDate = (d) ->
+    _.moment(d).format('L')

 Lanes.u.format.shartDateTime = (d) ->
     _.moment(d).format('lll')


Mocks: Return a channel id which will make sense.

--- client/mocks/mockingjay.coffee
+++ client/mocks/mockingjay.coffee
@@ -150,7 +150,8 @@
       toReturnChannel: ->

         expect.spyOn(socialapi, 'cacheable').andCall (type, id, callback) ->
-          callback null, { some: 'channel', information: 'will be here', in: 'the future' }
+          callback null, { id: '6075644514008039523' }
+

   getMockMachine: ->   return mockMachine


Made x to empty search box clickable on focus out.

--- app/assets/javascripts/app/controllers/navigation.js.coffee
+++ app/assets/javascripts/app/controllers/navigation.js.coffee
@@ -221,7 +221,10 @@

     # observer search box
     @$('#global-search').bind( 'focusout', (e) =>
-      @$('.search').removeClass('focused')
+      # delay to be able to click x
+      update = =>
+        @$('.search').removeClass('focused')
+      @delay( update, 180, 'removeFocused' )
     )

     @$('#global-search').bind( 'focusin', (e) =>


Prevent intervention form submission during compute to avoid submitting incoherent values

--- app/assets/javascripts/backend/interventions_base.js.coffee
+++ app/assets/javascripts/backend/interventions_base.js.coffee
@@ -5,6 +5,8 @@
   'use strict'

   PLANNED_REALISED_ACCEPTED_GAP = { intervention_doer: 1.2, intervention_tool: 1.2 , intervention_input: 1.2}
+
+  no_submit = => false

   E.value = (element) ->
     if element.is(":ui-selector")
@@ -149,6 +151,16 @@
         scopeUri = scopeUri.replace(re, "$1" + newTime + "$3")
         $(item).attr("data-selector", encodeURI(scopeUri))

+    _setWaiting: (form, computing) ->
+      computing.prop 'state', 'waiting'
+      form.on('submit', no_submit)
+      form.find('.form-actions input[type="submit"]').prop 'disabled', true
+
+    _setReady: (form, computing) ->
+      computing.prop 'state', 'ready'
+      form.off('submit', no_submit)
+      form.find('.form-actions input[type="submit"]').prop 'disabled', false
+
     # Ask for a refresh of values depending on given update
     refresh: (updater, options = {}) ->
       unless updater?
@@ -170,9 +182,9 @@
           type: "PATCH"
           data: form.serialize()
           beforeSend: ->
-            computing.prop 'state', 'waiting'
+            E.interventions._setWaiting(form, computing)
           error: (request, status, error) ->
-            computing.prop 'state', 'ready'
+            E.interventions._setReady(form, computing)
             false
           success: (data, status, request) ->
             console.group('Unserialize intervention updated by ' + updaterId)
@@ -182,7 +194,7 @@
             E.interventions.handleDynascope(form, data.intervention, 'intervention_', data.updater_id)
             E.interventions.unserializeRecord(form, data.intervention, 'intervention_', data.updater_id)
             E.interventions.updateProcedureLevelAttributes(form, data.procedure_states)
-            computing.prop 'state', 'ready'
+            E.interventions._setReady(form, computing)
             options.success.call(this, data, status, request) if options.success?
             console.groupEnd()


fixed a weird brain persistence issue

fixed a weird brain persistence issue

--- scripts/phabs_admin.coffee
+++ scripts/phabs_admin.coffee
@@ -30,11 +30,10 @@

   robot.phab ?= new Phabricator robot, process.env
   phab = robot.phab
-  data = robot.brain.data.phabricator

   #   hubot phad projects
   robot.respond /phad (?:projects|list) *$/, (msg) ->
-    projects = Object.keys(data.projects).filter (i) ->
+    projects = Object.keys(phab.data.projects).filter (i) ->
       i isnt '*'
     if projects.length > 0
       msg.send "Known Projects: #{projects.join(', ')}"
@@ -46,10 +45,10 @@
     project = msg.match[1]
     phab.getPermission(msg.envelope.user, 'phadmin')
     .then ->
-      if data.projects[project]?
-        delete data.projects[project]
-        for alias, proj of data.aliases
-          delete data.aliases[alias] if proj is project
+      if phab.data.projects[project]?
+        delete phab.data.projects[project]
+        for alias, proj of phab.data.aliases
+          delete phab.data.aliases[alias] if proj is project
         msg.send "#{project} erased from memory."
       else
         msg.send "#{project} not found in memory."
@@ -94,14 +93,14 @@
     .then ->
       phab.getProject(project)
     .then (proj) ->
-      if data.aliases[alias]?
-        msg.send "The alias '#{alias}' already exists for project '#{data.aliases[alias]}'."
+      if phab.data.aliases[alias]?
+        msg.send "The alias '#{alias}' already exists for project '#{phab.data.aliases[alias]}'."
       else
         if proj.data.parent?
           fullname = "#{proj.data.parent}/#{proj.data.name}"
         else
           fullname = proj.data.name
-        data.aliases[alias] = fullname
+        phab.data.aliases[alias] = fullname
         msg.send "Ok, '#{fullname}' will be known as '#{alias}'."
     .catch (e) ->
       msg.send e.message or e
@@ -111,8 +110,8 @@
     alias = msg.match[1]
     phab.getPermission(msg.envelope.user, 'phadmin')
     .then ->
-      if data.aliases[alias]
-        delete data.aliases[alias]
+      if phab.data.aliases[alias]
+        delete phab.data.aliases[alias]
         msg.send "Ok, the alias '#{alias}' is forgotten."
       else
         msg.send "Sorry, I don't know the alias '#{alias}'."
@@ -131,11 +130,11 @@
         fullname = "#{proj.data.parent}/#{proj.data.name}"
       else
         fullname = proj.data.name
-      data.projects[fullname].feeds ?= [ ]
-      if room in data.projects[fullname].feeds
+      phab.data.projects[fullname].feeds ?= [ ]
+      if room in phab.data.projects[fullname].feeds
         msg.send "The feed from '#{fullname}' to '#{room}' already exist."
       else
-        data.projects[fullname].feeds.push room
+        phab.data.projects[fullname].feeds.push room
         msg.send "Ok, '#{fullname}' is now feeding '#{room}'."
     .catch (e) ->
       msg.send e.message or e
@@ -147,11 +146,11 @@
     .then ->
       phab.getProject('*')
     .then (proj) ->
-      data.projects['*'].feeds ?= [ ]
-      if room in data.projects['*'].feeds
+      phab.data.projects['*'].feeds ?= [ ]
+      if room in phab.data.projects['*'].feeds
         msg.send "The catchall feed to '#{room}' already exist."
       else
-        data.projects['*'].feeds.push room
+        phab.data.projects['*'].feeds.push room
         msg.send "Ok, all feeds will be announced on '#{room}'."
     .catch (e) ->
       msg.send e.message or e
@@ -168,10 +167,10 @@
         fullname = "#{proj.data.parent}/#{proj.data.name}"
       else
         fullname = proj.data.name
-      data.projects[fullname].feeds ?= [ ]
-      if room in data.projects[fullname].feeds
-        idx = data.projects[fullname].feeds.indexOf room
-        data.projects[fullname].feeds.splice(idx, 1)
+      phab.data.projects[fullname].feeds ?= [ ]
+      if room in phab.data.projects[fullname].feeds
+        idx = phab.data.projects[fullname].feeds.indexOf room
+        phab.data.projects[fullname].feeds.splice(idx, 1)
         msg.send "Ok, The feed from '#{fullname}' to '#{room}' was removed."
       else
         msg.send "Sorry, '#{fullname}' is not feeding '#{room}'."
@@ -186,10 +185,10 @@
       phab.getProject('*')
     .then (proj) ->
       proj.data.feeds ?= [ ]
-      data.projects['*'].feeds ?= [ ]
-      if room in data.projects['*'].feeds
-        idx = data.projects['*'].feeds.indexOf room
-        data.projects['*'].feeds.splice(idx, 1)
+      phab.data.projects['*'].feeds ?= [ ]
+      if room in phab.data.projects['*'].feeds
+        idx = phab.data.projects['*'].feeds.indexOf room
+        phab.data.projects['*'].feeds.splice(idx, 1)
         msg.send "Ok, The catchall feed to '#{room}' was removed."
       else
         msg.send "Sorry, the catchall feed for '#{room}' doesn't exist."
@@ -202,9 +201,9 @@
     phab.getPermission(msg.envelope.user, 'phadmin')
     .then ->
       phab.getProject(project)
-    .then (data) ->
-      if data.data.columns? and Object.keys(data.data.columns).length > 0
-        msg.send "Columns for #{project}: #{Object.keys(data.data.columns).join(', ')}"
+    .then (payload) ->
+      if payload.data.columns? and Object.keys(payload.data.columns).length > 0
+        msg.send "Columns for #{project}: #{Object.keys(payload.data.columns).join(', ')}"
       else
         msg.send "The project #{project} has no columns."
     .catch (e) ->


Treat the key value as a list

Treat the key value as a list

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -14,12 +14,13 @@
   , 1000

   checkRedisForShit ->
-    redis_clent.get POO_TRACKER_KEY -> (err, reply)
-      return console.error("Failed get with key '#{POO_TRACKER_KEY}': #{err}") if err
+    redis_clent.lindex POO_TRACKER_KEY, -1 -> (err, reply)
+      return console.error("Failed get with key '#{POO_TRACKER_KEY}' and index -1: #{err}") if err
       return if reply == null

       robot.messageRoom "#arrakis", "#{reply}"
-      redis_clent.del POO_TRACKER_KEY
+      redis_clent.ltrim POO_TRACKER_KEY, -1, -1
+      redis_clent.lpop POO_TRACKER_KEY
       redis_clent.set POO_LATEST_KEY, reply

   robot.respond /poo tracker( me)?/i, (res) ->


Fix server crash when extracting GPS data, refs #159

--- server/models/photo.coffee
+++ server/models/photo.coffee
@@ -25,14 +25,16 @@
                 startkey: [album.id]
                 endkey: [album.id + "0"]
             Photo.request 'byalbum', params, callback
-#Modif Rémi
+
+    # Patch every photo to add GPS data
     @patchGps: (callback) ->
         Photo.fromAlbum {folderId: "all" }, (err, photos) ->

             return callback err if err? # error on request fail
             async.eachSeries photos, (photo, next) ->

-                unless photo.gps? # do it just if never add gps metadata
+                # Don't try to extract data if we already got them
+                if photo.binary? and not photo.gps?
                     photo.extractGpsFromBinary next
                 else setImmediate next
             , callback


Properly spit out env

Properly spit out env

--- server/routes/admin.coffee
+++ server/routes/admin.coffee
@@ -33,5 +33,6 @@

     res.render 'admin',
       user: req.user
+      env: config.env
       adminSegment: config.buckets.adminSegment
       needsInstall: userCount is 0


Removed superfluous comment

Removed superfluous comment

--- server/routes/queue.coffee
+++ server/routes/queue.coffee
@@ -36,7 +36,7 @@
       else if isHTTPMethodPut req
         handler.processTaskResult req, res
       else if isHTTPMethodPost req
-        handler.createNewTask req, res #TODO: do not use this in production
+        handler.createNewTask req, res
       else
         sendMethodNotSupportedError req, res
     catch error


chat: delete an unused model

chat: delete an unused model

--- workers/social/lib/social/models/chatconversation/chatmessage.coffee
+++ workers/social/lib/social/models/chatconversation/chatmessage.coffee
@@ -1,13 +0,0 @@
-{Model} = require 'bongo'
-
-module.exports = class JChatMessage extends Model
-
-  @set
-    schema        :
-      channelName : String
-      createdAt   :
-        type      : Date
-        default   : -> new Date
-      createdBy   : String
-      content     : String
-      readAt      : String


vimeoShell: update `_seekOffset` after seeks

--- coffee/src/shells/vimeo.shell.coffee
+++ coffee/src/shells/vimeo.shell.coffee
@@ -181,7 +181,9 @@
       @_isPlaying = true
       @trigger 'PlayerView:StateChange'

-    @player.addEvent 'seek', @onVimeoSeek
+    @player.addEvent 'seek', (params) =>
+      @_seekOffset = parseFloat params.seconds
+      @enforceVimeoPlaybackState()

     @player.addEvent 'playProgress', (params) =>
       @_timeTotal = parseFloat params.duration
@@ -194,7 +196,7 @@
   # Vimeo's api claims to hold playing-state constant through seeks, but seems
   # to play after any seek (and if previously paused, doesn't realize that the
   # state has changed).
-  onVimeoSeek: =>
+  enforceVimeoPlaybackState: =>
     # get desirable state
     wasPlaying = @isPlaying()


Shell: existence test in describe block

Shell: existence test in describe block

--- coffee/test/shells/shell.spec.coffee
+++ coffee/test/shells/shell.spec.coffee
@@ -7,7 +7,7 @@
   Shell = acorn.shells.Shell

   it 'should be part of acorn.shells', ->
-    expect(acorn.shells.Shell).toBeDefined()
+    expect(Shell).toBeDefined()

   it 'should contain required properties', ->
     required_properties = [ 'id', 'title', 'description' ]


fixing test as getValueFromAccessPath now always return a string

--- test/ascii-table-test.coffee
+++ test/ascii-table-test.coffee
@@ -43,8 +43,8 @@
     expect(value).to.equal('astring')

     value = @asciiTable.getValueFromAccessPath(jsonObj, 'field2')
-    expect(value).to.be.a('number')
-    expect(value).to.equal(42)
+    expect(value).to.be.a('string')
+    expect(value).to.equal('42')

   it 'gets a value from a complex path in a JSON object', ->
     value = @asciiTable.getValueFromAccessPath(jsonObj, 'field3.field5.field6')
@@ -52,5 +52,5 @@
     expect(value).to.equal('yetanotherstring')

     value = @asciiTable.getValueFromAccessPath(jsonObj, 'field3.field5.field7')
-    expect(value).to.be.a('number')
-    expect(value).to.equal(42)
+    expect(value).to.be.a('string')
+    expect(value).to.equal('42')


disable the proposal title / opinion title column and configuration

--- @client/list.coffee
+++ @client/list.coffee
@@ -471,20 +471,20 @@
                   list: @props.list
                   fresh: @props.fresh

-          if edit_list.editing || !is_collapsed
-            DIV
-              style:
-                position: 'relative'
-                marginTop: if get_list_title(list_key, null, subdomain)?.length > 0 || edit_list.editing then  18
-                display: if !edit_list.editing && category_value(list_key, null, subdomain).length + histo_title(list_key).length == 0 then 'none'
-
-              EditableListCategory
-                list: @props.list
-                fresh: @props.fresh
-
-              EditableOpinionLabel
-                list: @props.list
-                fresh: @props.fresh
+          # if edit_list.editing || !is_collapsed
+          #   DIV
+          #     style:
+          #       position: 'relative'
+          #       marginTop: if get_list_title(list_key, null, subdomain)?.length > 0 || edit_list.editing then  18
+          #       display: if !edit_list.editing && category_value(list_key, null, subdomain).length + histo_title(list_key).length == 0 then 'none'
+
+          #     EditableListCategory
+          #       list: @props.list
+          #       fresh: @props.fresh
+
+          #     EditableOpinionLabel
+          #       list: @props.list
+          #       fresh: @props.fresh

         if edit_list.editing

@@ -840,7 +840,7 @@
     edit_list = fetch "edit-#{list_key}"
     subdomain = fetch '/subdomain'

-    title = get_list_title list_key, is_collapsed, subdomain
+    title = get_list_title list_key, true, subdomain

     list_uncollapseable = customization 'list_uncollapseable', list_key, subdomain
     TITLE_WRAPPER = if list_uncollapseable then DIV else BUTTON
@@ -937,134 +937,134 @@
                     verticalAlign: 'top'

-EditableListCategory = ReactiveComponent
-  displayName: 'EditableListCategory'
-  render: ->
-    subdomain = fetch '/subdomain'
-    current_user = fetch '/current_user'
-
-    list = @props.list
-    list_key = list.key
-    list_state = fetch list_key
-    edit_list = fetch "edit-#{list_key}"
-
-    category = category_value list_key, @props.fresh, subdomain
-
-    has_title = customization('list_description', list_key)?.length > 0 || customization('list_title', list_key)?.length > 0
-    heading_style = _.defaults {}, customization('list_label_style', list_key),
-      fontSize: if !has_title then 44 else 36
-      fontWeight: if !has_title then 700 else 500
-      fontFamily: header_font()
-
-    show_opinion_header = edit_list.editing || widthWhenRendered(category, heading_style) <= column_sizes().first + column_sizes().gutter
-
-    DIV
-      style:
-        width: if show_opinion_header then column_sizes().first else '100%'
-        display: 'inline-block'
-
-      if edit_list.editing
-        DIV
-          className: 'LIST-field-edit-label'
-
-          TRANSLATE
-            id: "engage.list-config-category"
-            span:
-              component: SPAN
-              args:
-                style:
-                  fontWeight: 700
-
-            "Category. e.g. \"Ideas\", \"Policies\", \"Questions\", \"Strategies\"."
-
-      H1 null,
-
-        if edit_list.editing
-          AutoGrowTextArea
-            id: "category-#{list_key}"
-            ref: 'input'
-            className: "LIST-header LIST-fat-header-field #{if has_title then 'LIST-smaller-header'}"
-            style: _.defaults {}, customization('list_label_style', list_key) or {},
-              fontFamily: header_font()
-              width: column_sizes().first + 24
-
-            defaultValue: category
-            onChange: (e) ->
-              edit_list.list_category = e.target.value
-              save edit_list
-        else
-          SPAN
-            className: if !has_title then 'LIST-header' else 'LIST-header LIST-smaller-header'
-            style: _.defaults {}, customization('list_label_style', list_key) or {}
-            category
-
-
-EditableOpinionLabel = ReactiveComponent
-  displayName: 'EditableOpinionLabel'
-  render: ->
-    subdomain = fetch '/subdomain'
-    current_user = fetch '/current_user'
-
-    list = @props.list
-    list_key = list.key
-    list_state = fetch list_key
-    edit_list = fetch "edit-#{list_key}"
-
-    opinion_title = histo_title list_key
-
-    has_title = customization('list_description', list_key)?.length > 0 || customization('list_title', list_key)?.length > 0
-    heading_style = _.defaults {}, customization('list_label_style', list_key),
-      fontSize: if !has_title then 44 else 36
-      fontWeight: if !has_title then 700 else 500
-      fontFamily: header_font()
-
-    show = edit_list.editing || widthWhenRendered(category_value(list_key, null, subdomain), heading_style) <= column_sizes().first + column_sizes().gutter
+# EditableListCategory = ReactiveComponent
+#   displayName: 'EditableListCategory'
+#   render: ->
+#     subdomain = fetch '/subdomain'
+#     current_user = fetch '/current_user'
+
+#     list = @props.list
+#     list_key = list.key
+#     list_state = fetch list_key
+#     edit_list = fetch "edit-#{list_key}"
+
+#     category = category_value list_key, @props.fresh, subdomain
+
+#     has_title = customization('list_description', list_key)?.length > 0 || customization('list_title', list_key)?.length > 0
+#     heading_style = _.defaults {}, customization('list_label_style', list_key),
+#       fontSize: if !has_title then 44 else 36
+#       fontWeight: if !has_title then 700 else 500
+#       fontFamily: header_font()
+
+#     show_opinion_header = edit_list.editing || widthWhenRendered(category, heading_style) <= column_sizes().first + column_sizes().gutter
+
+#     DIV
+#       style:
+#         width: if show_opinion_header then column_sizes().first else '100%'
+#         display: 'inline-block'
+
+#       if edit_list.editing
+#         DIV
+#           className: 'LIST-field-edit-label'
+
+#           TRANSLATE
+#             id: "engage.list-config-category"
+#             span:
+#               component: SPAN
+#               args:
+#                 style:
+#                   fontWeight: 700
+
+#             "Category. e.g. \"Ideas\", \"Policies\", \"Questions\", \"Strategies\"."
+
+#       H1 null,
+
+#         if edit_list.editing
+#           AutoGrowTextArea
+#             id: "category-#{list_key}"
+#             ref: 'input'
+#             className: "LIST-header LIST-fat-header-field #{if has_title then 'LIST-smaller-header'}"
+#             style: _.defaults {}, customization('list_label_style', list_key) or {},
+#               fontFamily: header_font()
+#               width: column_sizes().first + 24
+
+#             defaultValue: category
+#             onChange: (e) ->
+#               edit_list.list_category = e.target.value
+#               save edit_list
+#         else
+#           SPAN
+#             className: if !has_title then 'LIST-header' else 'LIST-header LIST-smaller-header'
+#             style: _.defaults {}, customization('list_label_style', list_key) or {}
+#             category
+
+
+# EditableOpinionLabel = ReactiveComponent
+#   displayName: 'EditableOpinionLabel'
+#   render: ->
+#     subdomain = fetch '/subdomain'
+#     current_user = fetch '/current_user'
+
+#     list = @props.list
+#     list_key = list.key
+#     list_state = fetch list_key
+#     edit_list = fetch "edit-#{list_key}"
+
+#     opinion_title = histo_title list_key
+
+#     has_title = customization('list_description', list_key)?.length > 0 || customization('list_title', list_key)?.length > 0
+#     heading_style = _.defaults {}, customization('list_label_style', list_key),
+#       fontSize: if !has_title then 44 else 36
+#       fontWeight: if !has_title then 700 else 500
+#       fontFamily: header_font()
+
+#     show = edit_list.editing || widthWhenRendered(category_value(list_key, null, subdomain), heading_style) <= column_sizes().first + column_sizes().gutter

-    DIV
-      style:
-        width: column_sizes().second
-        display: if show then 'inline-block' else 'none'
-        marginLeft: column_sizes().gutter
-        textAlign: 'right'
-
-      if edit_list.editing
-        DIV
-          className: 'LIST-field-edit-label'
-          style:
-            textAlign: 'right'
-
-          TRANSLATE
-            id: "engage.list-config-opinion-title"
-            span:
-              component: SPAN
-              args:
-                style:
-                  fontWeight: 700
-
-            "Opinion title. e.g. \"Ratings\", \"Gut checks\"."
-
-
-      H1 null,
-        if edit_list.editing
-          AutoGrowTextArea
-            id: "list_opinions_title-#{list_key}"
-            ref: 'input'
-            className: "LIST-header LIST-fat-header-field #{if has_title then 'LIST-smaller-header'}"
-            style: _.defaults {}, customization('list_label_style', list_key) or {},
-              fontFamily: header_font()
-              width: column_sizes().second + 24
-              textAlign: 'right'
-
-            defaultValue: opinion_title
-            onChange: (e) ->
-              edit_list.list_opinions_title = e.target.value
-              save edit_list
-
-        else
-          SPAN
-            className: if !has_title then 'LIST-header' else 'LIST-header LIST-smaller-header'
-            style: _.defaults {}, customization('list_label_style', list_key) or {}
-            opinion_title
+#     DIV
+#       style:
+#         width: column_sizes().second
+#         display: if show then 'inline-block' else 'none'
+#         marginLeft: column_sizes().gutter
+#         textAlign: 'center'
+
+#       if edit_list.editing
+#         DIV
+#           className: 'LIST-field-edit-label'
+#           style:
+#             textAlign: 'right'
+
+#           TRANSLATE
+#             id: "engage.list-config-opinion-title"
+#             span:
+#               component: SPAN
+#               args:
+#                 style:
+#                   fontWeight: 700
+
+#             "Opinion title. e.g. \"Ratings\", \"Gut checks\"."
+
+
+#       H1 null,
+#         if edit_list.editing
+#           AutoGrowTextArea
+#             id: "list_opinions_title-#{list_key}"
+#             ref: 'input'
+#             className: "LIST-header LIST-fat-header-field #{if has_title then 'LIST-smaller-header'}"
+#             style: _.defaults {}, customization('list_label_style', list_key) or {},
+#               fontFamily: header_font()
+#               width: column_sizes().second + 24
+#               textAlign: 'right'
+
+#             defaultValue: opinion_title
+#             onChange: (e) ->
+#               edit_list.list_opinions_title = e.target.value
+#               save edit_list
+
+#         else
+#           SPAN
+#             className: if !has_title then 'LIST-header' else 'LIST-header LIST-smaller-header'
+#             style: _.defaults {}, customization('list_label_style', list_key) or {}
+#             opinion_title

 EditableDescription = ReactiveComponent
@@ -1167,76 +1167,72 @@
     className: 'list_actions'
     style:
       marginBottom: 50
-
-    if add_new
-
-      SPAN null,
-        A
+      marginTop: 24
+      display: 'flex'
+
+    DIV
+      style:
+        width: column_sizes().first
+        marginRight: column_sizes().gutter
+        display: 'flex'
+
+      if add_new
+
+        SPAN null,
+          A
+            style:
+              textDecoration: 'underline'
+              fontSize: 20
+              color: focus_color()
+              fontFamily: customization('font')
+              fontStyle: 'normal'
+              fontWeight: 700
+            onClick: (e) =>
+              list_state = fetch list_key
+              list_state.show_all_proposals = true
+              save list_state
+              e.stopPropagation()
+
+              wait_for = ->
+                add_new_button = $("[name='new_#{props.list.key.substring(5)}']")
+                if add_new_button.length > 0
+                  add_new_button.ensureInView()
+                  add_new_button.click()
+                else
+                  setTimeout wait_for, 1
+
+              wait_for()
+
+            translator "engage.add_new_proposal_to_list", 'add new'
+
+      if props.can_sort && add_new
+        SPAN
           style:
-            textDecoration: 'underline'
+            padding: '0 24px'
             fontSize: 20
-            color: focus_color()
-            fontFamily: customization('font')
-            fontStyle: 'normal'
-            fontWeight: 700
-          onClick: (e) =>
-            list_state = fetch list_key
-            list_state.show_all_proposals = true
-            save list_state
-            e.stopPropagation()
-
-            wait_for = ->
-              add_new_button = $("[name='new_#{props.list.key.substring(5)}']")
-              if add_new_button.length > 0
-                add_new_button.ensureInView()
-                add_new_button.click()
-              else
-                setTimeout wait_for, 1
-
-            wait_for()
-
-          translator "engage.add_new_proposal_to_list", 'add new'
-
-    if props.can_sort && add_new
-      SPAN
-        style:
-          padding: '0 24px'
-          fontSize: 20
-        '|'
-
-    if props.can_sort
-      SortProposalsMenu()
-
-
+          # '|'
+
+      if props.can_sort
+        SortProposalsMenu()
+

     OpinionViews
       style:
-        display: 'inline-block'
-        float: 'right'
-        maxWidth: column_sizes().second
-        textAlign: 'right'
-      enable_comparison_wrapper_style:
-        position: 'absolute'
-        right: 0
-        bottom: -20
-        fontSize: 14
-        zIndex: 99
-
-
-    DIV
-      style:
-        clear: 'both'
+        width: column_sizes().second
+      additional_width: column_sizes().gutter + column_sizes().first
+
+

 window.get_list_title = (list_key, include_category_value, subdomain) ->
   edit_list = fetch "edit-#{list_key}"

-  title = (edit_list.editing and edit_list.list_title) or customization('list_title', list_key, subdomain)
+  title = (edit_list.editing and edit_list.list_title)
+  title ?= customization('list_title', list_key, subdomain)
   if include_category_value
     title ?= category_value list_key, null, subdomain
-  title ?= ""
-
-  if title == 'Show all'
+
+  if title == 'Show all' || !title?
     title = translator "engage.all_proposals_list", "All Proposals"
   else if title == 'Proposals'
     title = translator "engage.default_proposals_list", "Proposals"


fetch packages when the user selects a repository

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -331,6 +331,8 @@
     $scope.selectMirror = (m)->
       console.log("mirror #{m.name} (#{m.id}) selected")
       $scope.mirrorSelected[m.id] = not $scope.mirrorSelected[m.id]
+      # update the list of packages with the selected repo
+      $scope.fetchPackages()

     $scope.loadData = ()->
       # update the cookie with a working url


Final fix

Final fix

Try to keep the active page visible in the TOC when changing pages.

--- src/scripts/modules/media/tabs/contents/contents.coffee
+++ src/scripts/modules/media/tabs/contents/contents.coffee
@@ -85,10 +85,17 @@
       @regions.self.append new AddPopoverView
         model: @model
         owner: @$el.find('.add.btn')
+      # Try to scroll toc to active page, needs a little time
+      setTimeout( =>
+        $activePage = @$el.find('span.active')
+        if $activePage.length > 0
+          @$el.scrollTop($activePage.offset().top)
+      , 50)
+

     processPages: ->
       nodes = @model.get('contents')?.models
-      if nodes?
+      if nodes?.length
         isCcap = nodes[0].isCcap()
         if isCcap
           sections = nodes.filter((node) -> node.isSection())


pretty code

pretty code

--- src/serializations/item-references.coffee
+++ src/serializations/item-references.coffee
@@ -38,12 +38,13 @@
 ###

 deserializeItems = (json, outline, options) ->
+  Outline = require('../outline')
   json = JSON.parse(json)
-  sourceOutline = require('../outline').getOutlineForID(json.outlineID)
   items = []
   expandedItemIDs = []
   items.loadOptions =
     expanded: expandedItemIDs
+  sourceOutline = Outline.getOutlineForID(json.outlineID)

   if sourceOutline
     for each in json.items


Include CI build number in releases

Include CI build number in releases

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -37,6 +37,7 @@
       jsonPath = './build/darwin64/' + manifest.productName + '.app/Contents/Resources/app/package.json'
       updateManifest jsonPath, (manifest) ->
         manifest.distrib = 'darwin64:dmg'
+        manifest.buildNum = process.env.TRAVIS_BUILD_NUMBER
       , callback

     # Remove the dev modules
@@ -101,6 +102,7 @@
       jsonPath = './build/darwin64/' + manifest.productName + '.app/Contents/Resources/app/package.json'
       updateManifest jsonPath, (manifest) ->
         manifest.distrib = 'darwin64:zip'
+        manifest.buildNum = process.env.TRAVIS_BUILD_NUMBER
       , callback

     # Remove the dev modules
@@ -273,6 +275,7 @@
           jsonPath = './build/linux' + arch + '/opt/' + manifest.name + '/resources/app/package.json'
           updateManifest jsonPath, (manifest) ->
             manifest.distrib = 'linux' + arch + ':' + target
+            manifest.buildNum = process.env.CIRCLE_BUILD_NUM
           , callback

         # Remove the dev modules
@@ -311,6 +314,7 @@
     # Update package.json
     async.apply updateManifest, './build/win32/resources/app/package.json', (manifest) ->
       manifest.distrib = 'win32:installer'
+      manifest.buildNum = process.env.APPVEYOR_BUILD_NUMBER

     # Remove the dev modules
     applyIf args.prod, applySpawn 'npm', ['prune', '--production'],
@@ -377,6 +381,7 @@
     async.apply updateManifest, './build/win32/resources/app/package.json', (manifest) ->
       manifest.portable = true
       manifest.distrib = 'win32:portable'
+      manifest.buildNum = process.env.APPVEYOR_BUILD_NUMBER

     # Remove the dev modules
     applyIf args.prod, applySpawn 'npm', ['prune', '--production'],


Fixes misspelling in pack task for linux

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -33,7 +33,7 @@
   async.series [
     # Update package.json
     (cb) ->
-      jsonPath = './build/darwin64/' + manifest.productName + '.app/Contents/Resources/app/packge.json'
+      jsonPath = './build/darwin64/' + manifest.productName + '.app/Contents/Resources/app/package.json'
       updateManifest jsonPath, (manifest) ->
         manifest.distrib = 'darwin64:dmg'
       , cb
@@ -148,7 +148,7 @@
       async.series [
         # Update package.json
         (cb) ->
-          jsonPath = './build/linux' + arch + '/opt/' + manifest.name + '/resources/app/packge.json'
+          jsonPath = './build/linux' + arch + '/opt/' + manifest.name + '/resources/app/package.json'
           updateManifest jsonPath, (manifest) ->
             manifest.distrib = 'linux' + arch + ':' + target
           , cb
@@ -187,7 +187,7 @@

   async.series [
     # Update package.json
-    async.apply updateManifest, './build/win32/resources/app/packge.json', (manifest) ->
+    async.apply updateManifest, './build/win32/resources/app/package.json', (manifest) ->
       manifest.distrib = 'win32:installer'

     # Remove the dev modules
@@ -231,7 +231,7 @@

   async.series [
     # Update package.json
-    async.apply updateManifest, './build/win32/resources/app/packge.json', (manifest) ->
+    async.apply updateManifest, './build/win32/resources/app/package.json', (manifest) ->
       manifest.portable = true
       manifest.distrib = 'win32:portable'


Fixes rpm packages not installing

Fixes rpm packages not installing

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -74,6 +74,7 @@
         '-s dir'
         '-t ' + target
         '--architecture ' + if arch == 32 then 'i386' else 'x86_64'
+        '--rpm-os linux'
         '--name ' + manifest.name
         '--force' # Overwrite existing files
         '--after-install ./build/resources/linux/after-install.sh'


Fix double .gz extension

Fix double .gz extension

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -314,7 +314,7 @@
       # Archive the files
       (callback) ->
         gulp.src './build/linux' + arch + '/opt/' + manifest.name + '/**/*'
-          .pipe tar(manifest.name + '-' + manifest.version + '-linux' + arch + '.tar.gz')
+          .pipe tar(manifest.name + '-' + manifest.version + '-linux' + arch + '.tar')
           .pipe gzip()
           .pipe gulp.dest './dist'
           .on 'end', callback


bugfix: gather all inputs for text task values

--- tasks/text.coffee
+++ tasks/text.coffee
@@ -21,7 +21,7 @@

   getValue: ->
     output = {}
-    inputs = @el.querySelectorAll "input[type=text]"
+    inputs = @el.querySelectorAll "input[type]"
     output[input.name] = input.value for input in inputs

     output


typos

typos

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -83,14 +83,14 @@

     parse_line: (line) ->
         if @should_parse_data
-            @prase_data(line)
+            @parse_data(line)
         else if line.match /open\n/
             @fh = new FileHandler(@)
             @nconn += 1
         else
-            @prase_setting(line)
+            @parse_setting(line)

-    prase_data: (line) ->
+    parse_data: (line) ->
         if @fh.readbytes < @fh.datasize
             @fh.readbytes += Buffer.byteLength(line)
             # remove trailing newline if necessary
@@ -101,7 +101,7 @@
             @should_parse_data = false
             @fh.open_in_atom()

-    prase_setting: (line) ->
+    parse_setting: (line) ->
         m = line.match /([a-z\-]+?)\s*:\s*(.*?)\s*$/
         if m and m[2]?
             @fh.settings[m[1]] = m[2]


upd: test

upd: test

--- test/basic.coffee
+++ test/basic.coffee
@@ -107,13 +107,10 @@
 			old = Yaku.onUnhandledRejection

 			Yaku.onUnhandledRejection = (reason, p) ->
-				old reason, p
 				Yaku.onUnhandledRejection = old
 				r reason

-			setTimeout ->
-				Yaku.reject $val
-			, 3000
+			Yaku.reject $val

 .then ->
 	test 'no unhandled rejection', $val, ->


add expect methods onto extended expect

add expect methods onto extended expect

--- configs/test/jest.setup.coffee
+++ configs/test/jest.setup.coffee
@@ -42,6 +42,8 @@
   combinedMatchers = Object.assign(chaiMatchers, originalMatchers)
   combinedMatchers

-global.expect.extend = originalExpect.extend
+
+for key, fn of originalExpect
+  global.expect[key] = fn

 require './matchers'


simpler getOrElse error thrown test

simpler getOrElse error thrown test

--- test/cache.coffee
+++ test/cache.coffee
@@ -148,9 +148,6 @@
             expect(data).to.be 'fresh cats'
             done()

-          obj =
-            errorGenerator: (callback) -> callback(new Error('backend troubles'))
-
-          bond(cache, 'getWrapped').return(Q.npost(obj, "errorGenerator"))
+          bond(cache, 'getWrapped').return Q.reject new Error('backend troubles')

           cache.getOrElse 'bad_get', valueGenerator, freshFor: 5, theCallback


remove debug logging statement

remove debug logging statement

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -19,7 +19,6 @@
       @[attrname] = glyph_value
       if not @[attrname].default?
         @[attrname].default = default_value
-      console.log @[attrname]
     else
       console.log("string property '#{ attrname }' given invalid glyph value: " + glyph_value)


fix unitialized var usage

fix unitialized var usage

--- bokehjs/src/coffee/server/embed.coffee
+++ bokehjs/src/coffee/server/embed.coffee
@@ -25,11 +25,11 @@
     $('body').append(link)

   add_plot_static = (element, model_id, model_type, all_models) ->
+    model = base.Collections(model_type).get(model_id)
+    view = new model.default_view({model : model})
     if model_id not of index
       load_models(all_models);
       index[model_id] = view
-    model = base.Collections(model_type).get(model_id)
-    view = new model.default_view({model : model})
     _.delay(-> $(element).replaceWith(view.$el))

   add_plot_server = (element, doc_id, model_id) ->


Use observeConfig

Use observeConfig

--- lib/theme-panel.coffee
+++ lib/theme-panel.coffee
@@ -23,7 +23,7 @@
             @ul outlet: 'syntaxMenu', class: 'dropdown-menu theme-menu', role: 'menu'

   initialize: ->
-    atom.config.observe 'core.themes', =>
+    @observeConfig 'core.themes', =>
       @activeUiTheme = @getActiveUiTheme()
       @activeSyntaxTheme = @getActiveSyntaxTheme()
       @selectActiveThemes()


fix test

fix test

--- test/fetch.coffee
+++ test/fetch.coffee
@@ -60,7 +60,7 @@
           display_on_desktop: true
         }
       ]
-      Backbone.sync.args[1][2].url.should.include 'set/8/items'
+      Backbone.sync.args[1][2].url.should.include 'set/7/items'
       Backbone.sync.args[1][2].success [{ name: 'FooBar' }]

     it 'returns an empty collection if there are no sets', (done) ->


Social: change variable name

Social: change variable name

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -88,7 +88,7 @@
     {SocialChannel} = KD.remote.api
     for channel in channels
       data = channel.channel
-      data.isParticipated = channel.isParticipated
+      data.isParticipant = channel.isParticipant
       data.participantCount = channel.participantCount

       c = new SocialChannel data


SyncLocalController: indentation and style fix

--- client/Main/synclocalcontroller.coffee
+++ client/Main/synclocalcontroller.coffee
@@ -30,8 +30,8 @@
     # TODO : find better name to this method
     syncFiles = @storage.getValue "saveRequestedFiles"
     if syncFiles
-      i = syncFiles.indexOf fileName
-      @filesToSave.push fileName if i == -1
+      index = syncFiles.indexOf fileName
+      @filesToSave.push fileName if index == -1
     else
       @filesToSave.push fileName

@@ -59,7 +59,7 @@
     KD.singletons.vmController.start vmName, (err, result)=>
       return err if err
       # for each diff file, fetch the contents from user VM
-      @fetchFileContents fileName,(err, content)=>
+      @fetchFileContents vmName, fileName,(err, content)=>
         if content and not err
           newContent = @getPatchedContent content, localContent
           # save new content if not same as ori


SyncController: add fallback to VirtualizationController::info method

--- client/Main/synclocalcontroller.coffee
+++ client/Main/synclocalcontroller.coffee
@@ -12,15 +12,7 @@
     @initializeListeners()

   initializeListeners: ->
-    status = KD.remote
-
-    @on "LocalContentSynced", (file)->
-      log "LocalContentSynced : #{file.path}"
-
-    @on "LocalContentCouldntSynced", (file)->
-      log "LocalContentSynced : #{file.path}"
-
-    status.on "reconnected", =>
+    KD.remote.on "reconnected", =>
       return  if @synStarted
       @synStarted = yes
       @syncLocalContentIfDiffExists (err)=>
@@ -46,30 +38,33 @@
   removeFromSaveArray: (file) ->
     fileName = @getFileFullPath file
     index = @filesToSave.indexOf fileName
-    if index > -1
-      @filesToSave.splice index, 1
-      @storage.setValue "saveRequestedFiles", @filesToSave
+    return  unless index > -1
+    @filesToSave.splice index, 1
+    @storage.setValue "saveRequestedFiles", @filesToSave

-  patchFileIfDiffExist: (file, localContent, cb)->
-    KD.singletons.vmController.info file.vmName, (err, vm, info)=>
-      # TODO : we may need to listen vmController when state is not RUNNING
+  patchFileIfDiffExist: (file, localContent, cb, callCounter = 0)->
+    KD.singletons.vmController.info file.vmName, @utils.getTimedOutCallback (err, vm, info)=>
       return cb err unless info.state is "RUNNING"
       file.fetchContents (err, content)=>
         if content and not err
           newContent = @getPatchedContent content, localContent
-          if newContent
-            file.save newContent[0],
-            @utils.getTimedOutCallback (err, res)=>
+          unless content is localContent
+            file.save localContent, (err, res)=>
               return cb err if err
               @removeFromSaveArray file
               @removeFileContentFromLocalStorage file
+              @updateEditorStatus file, localContent
               @emit "LocalContentSynced", file
               cb null, file
-            ->
-              @emit "LocalContentCouldntSynced", file
           else
             @removeFromSaveArray file
             cb null, file
+    ,=>
+      ++callCounter
+      if callCounter > 5
+        @emit "VMNotResponding", file
+      else
+        @patchFileIfDiffExist file, localContent, cb, callCounter

   getPatchedContent: (originalContent, localContent)->
     dmp = new diff_match_patch
@@ -97,11 +92,11 @@
   removeFromOpenedFiles: (file)->
     fileName = @getFileFullPath file
     index    = @openedFiles.indexOf fileName
-    unless index is -1
-      @openedFiles.splice index, 1
-      @storage.setValue "openedFiles", @openedFiles
-      @removeFromSaveArray file
-      @removeFileContentFromLocalStorage file
+    return if index is -1
+    @openedFiles.splice index, 1
+    @storage.setValue "openedFiles", @openedFiles
+    @removeFromSaveArray file
+    @removeFileContentFromLocalStorage file

   getRecentOpenedFiles: ->
     @openedFiles
@@ -111,5 +106,18 @@
     fileName = "[#{file.vmName}]#{plainPath}"
     return fileName

-  saveEditorHistory: ->
-    log "NOT IMPLEMENTED YET"
+  updateEditorStatus:(file, lastSavedContent)->
+    fileName   = FSHelper.plainPath file.path
+    # get current AceViews
+    aceAppView = KD.singletons.appManager.get("Ace").getView()
+    {ace} = aceAppView.aceViews[fileName]
+    ace.lastSavedContents = lastSavedContent
+    unless ace.getContents() is lastSavedContent
+      ace.emit "FileContentChanged"
+    else
+      ace.emit "FileContentSynced"
+
+  removeLocalContents:->
+    for key in @storage.getLocalStorageKeys()
+      if key.indexOf("koding-editor") > -1
+        delete window.localStorage[key]


Client: Popover on switch buttons as well.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -43,7 +43,13 @@
       yourTeam     : @model.getTeam()
       validActions : validActions[0] || {}
       window       : window
-    @$('.battle_actions').html @action_template(locals)
+    $actions = @$('.battle_actions')
+    $actions.html @action_template(locals)
+    $actions.find('.switch.button').each (i, el) =>
+      $this = $(el)
+      slot = $this.data('slot')
+      pokemon = @model.getPokemon(@model.index, slot)
+      @popover($this, pokemon)
     this

   renderWaiting: =>
@@ -59,21 +65,24 @@
       yourIndex    : @model.index
       window       : window
     $userInfo = @$('.battle_user_info')
+    $userInfo.find('.pokemon_icon').popover('destroy')
     $userInfo.html @user_info_template(locals)
-    $('.popover').remove()
     $userInfo.find('.pokemon_icon').each (i, el) =>
       $this = $(el)
       team = $this.data('team')
       slot = $this.data('slot')
       pokemon = @model.getPokemon(team, slot)
-      $this.popover
-        title: pokemon.get('name')
-        html: true
-        content: JST['battle_hover_info']({window, pokemon})
-        trigger: 'hover'
-        animation: false
-        container: "body"
+      @popover($this, pokemon)
     this
+
+  popover: ($this, pokemon) =>
+    $this.popover
+      title: pokemon.get('name')
+      html: true
+      content: JST['battle_hover_info']({window, pokemon})
+      trigger: 'hover'
+      animation: false
+      container: "body"

   renderTeamPreview: (teams) =>
     locals =
@@ -372,6 +381,7 @@
     @renderActions(validActions)

   disableButtons: =>
+    @$('.battle_actions .switch.button').popover('destroy')
     @renderWaiting()

   addLog: (message) =>


Highlight current tool after is installed

--- client/code/view/tool/menu-item.coffee
+++ client/code/view/tool/menu-item.coffee
@@ -87,6 +87,11 @@
       success: (dataset, resp, options) =>
         dataset.installPlugin @options.archetype.get('name'), (err, view) =>
           console.warn 'Error', err if err?
+          v = new Cu.View.ToolMenuItem model: view
+          el = v.render().el
+          $('a', el).addClass('active')
+          $('#toolbar .tool.active').removeClass("active")
+          $('#toolbar .tools').append el
           window.app.navigate "/dataset/#{dataset.id}/view/#{view.id}", trigger: true
       error: (model, xhr, options) ->
         @active = false