EntityList重构为Array结构

EntityList重构为Array结构

--- src/core/entity.coffee
+++ src/core/entity.coffee
@@ -1113,8 +1113,150 @@
 			json.$oldData = @_oldData
 		return json

-class LinkedList
-	_size: 0
+class Page extends Array
+	loaded: false
+	entityCount: 0
+
+	constructor: (@entityList, @pageNo)->
+
+	initData: (json)->
+		rawJson = json
+		entityList = @entityList
+
+		if json.hasOwnProperty("$data")
+			json = rawJson.$data
+		else if json.hasOwnProperty("data$")    # Deprecated
+			json = rawJson.data$
+
+		if json and not (json instanceof Array)
+			throw new cola.Exception("Unmatched DataType. expect \"Array\" but \"Object\".")
+
+		if json?.length
+			dataType = entityList.dataType
+			for data in json
+				entity = new _Entity(data, dataType)
+				@insert(entity)
+
+			if rawJson.$entityCount?
+				entityList.totalEntityCount = rawJson.$entityCount
+			else if rawJson.entityCount$?    # Deprecated
+				entityList.totalEntityCount = rawJson.entityCount$
+
+			if entityList.totalEntityCount?
+				if entityList.pageSize
+					entityList.pageCount = Math.floor((entityList.totalEntityCount + entityList.pageSize - 1) / entityList.pageSize)
+				entityList.pageCountDetermined = true
+
+			entityList.entityCount += json.length
+			entityList.timestamp = cola.sequenceNo()
+
+			entityList._notify(cola.constants.MESSAGE_REFRESH, {
+				data: entityList
+			})
+		else
+			entityList.totalEntityCount = entityList.entityCount
+			entityList.pageCountDetermined = true
+		return
+
+	insert: (entity, index)->
+		if 0 <= index < @length
+			@splice(index, 0, entity)
+		else
+			index = @length
+			@push(entity)
+		@hotIndex = index
+
+		entityList = @entityList
+		entity._page = @
+		entity.parent = entityList
+		delete entity._parentProperty
+
+		if not @_dontAutoSetCurrent and not entityList.current?
+			if entity.state isnt _Entity.STATE_DELETED
+				entityList._setCurrentPage(entity._page, false)
+				entityList.setCurrent(entity)
+
+		entity._setDataModel(entityList._dataModel)
+		entity._onPathChange()
+		if entity.state isnt _Entity.STATE_DELETED
+			@entityCount++
+			@totalEntityCount++
+		return
+
+	remove: (entity)->
+		index = @indexOf(entity)
+		if index >= 0
+			@splice(index, 1)
+			if index is @hotIndex
+				if index is @length - 1
+					@hotIndex--
+			else if index < @hotIndex
+				@hotIndex--
+
+			delete entity._page
+			entity._parent = entity.parent
+			delete entity.parent
+			entity._setDataModel(null)
+			entity._onPathChange()
+			if entity.state isnt _Entity.STATE_DELETED
+				@entityCount--
+				@totalEntityCount--
+		return
+
+	clear: ()->
+		i = @length - 1
+		while i >= 0
+			entity = @[i]
+			delete entity._page
+			entity._parent = entity.parent
+			delete entity.parent
+			entity._setDataModel(null)
+			entity._onPathChange()
+			entity = entity._next
+			i--
+		@splice(0, @length)
+		@entityCount = 0
+		@hotIndex = 0
+		return
+
+	loadData: (callback)->
+		providerInvoker = @entityList._providerInvoker
+		if providerInvoker
+			providerInvoker.pageSize = @entityList.pageSize
+			providerInvoker.pageNo = @pageNo
+			if callback
+				dfd = providerInvoker.invokeAsync(
+					complete: (success, result)=>
+						if success then @initData(result)
+						cola.callback(callback, success, result)
+				)
+			else
+				result = providerInvoker.invokeSync()
+				@initData(result)
+				dfd = $.Deferred().resolve(result)
+		return cola.util.createDeferredIf(dfd)
+
+class cola.EntityList
+	current: null
+	entityCount: 0
+
+	pageSize: 0
+	pageNo: 1
+	pageCount: 1
+
+	_pageCount: 0
+	_disableObserverCount: 0
+
+	# totalEntityCount
+	# _parent
+	# _parentProperty
+	# _providerInvoker
+
+	constructor: (array, dataType)->
+		@id = cola.uniqueId()
+		@timestamp = cola.sequenceNo()
+		@dataType = dataType
+		if array then @fillData(array)

 	_insertElement: (element, insertMode, refEntity)->
 		if not @_first
@@ -1145,7 +1287,7 @@
 				@_first._previous = element
 				@_first = element
 		element._page = @
-		@_size++
+		@_pageCount++
 		return

 	_removeElement: (element)->
@@ -1155,141 +1297,13 @@
 		next?._previous = previous
 		if @_first is element then @_first = next
 		if @_last is element then @_last = previous
-		@_size--
+		@_pageCount--
 		return

 	_clearElements: ()->
 		@_first = @_last = null
-		@_size = 0
-		return
-
-class Page extends LinkedList
-	loaded: false
-	entityCount: 0
-
-	constructor: (@entityList, @pageNo)->
-
-	initData: (json)->
-		rawJson = json
-		entityList = @entityList
-
-		if json.hasOwnProperty("$data")
-			json = rawJson.$data
-		else if json.hasOwnProperty("data$")
-			json = rawJson.data$
-
-		if json and not (json instanceof Array)
-			throw new cola.Exception("Unmatched DataType. expect \"Array\" but \"Object\".")
-
-		if json?.length
-			dataType = entityList.dataType
-			for data in json
-				entity = new _Entity(data, dataType)
-				@_insertElement(entity)
-
-			if rawJson.$entityCount?
-				entityList.totalEntityCount = rawJson.$entityCount
-			else if rawJson.entityCount$?
-				entityList.totalEntityCount = rawJson.entityCount$
-
-			if entityList.totalEntityCount?
-				if entityList.pageSize
-					entityList.pageCount = Math.floor((entityList.totalEntityCount + entityList.pageSize - 1) / entityList.pageSize)
-				entityList.pageCountDetermined = true
-
-			entityList.entityCount += json.length
-			entityList.timestamp = cola.sequenceNo()
-
-			entityList._notify(cola.constants.MESSAGE_REFRESH, {
-				data: entityList
-			})
-		else
-			entityList.totalEntityCount = entityList.entityCount
-			entityList.pageCountDetermined = true
-		return
-
-	_insertElement: (entity, insertMode, refEntity)->
-		super(entity, insertMode, refEntity)
-
-		entityList = @entityList
-		entity._page = @
-		entity.parent = entityList
-		delete entity._parentProperty
-
-		if not @_dontAutoSetCurrent and not entityList.current?
-			if entity.state isnt _Entity.STATE_DELETED
-				entityList._setCurrentPage(entity._page, false)
-				entityList.setCurrent(entity)
-
-		entity._setDataModel(entityList._dataModel)
-		entity._onPathChange()
-		if entity.state isnt _Entity.STATE_DELETED
-			@entityCount++
-			@totalEntityCount++
-		return
-
-	_removeElement: (entity)->
-		super(entity)
-		delete entity._page
-		entity._parent = entity.parent
-		delete entity.parent
-		entity._setDataModel(null)
-		entity._onPathChange()
-		if entity.state isnt _Entity.STATE_DELETED
-			@entityCount--
-			@totalEntityCount--
-		return
-
-	_clearElements: ()->
-		entity = @_first
-		while entity
-			delete entity._page
-			entity._parent = entity.parent
-			delete entity.parent
-			entity._setDataModel(null)
-			entity._onPathChange()
-			entity = entity._next
-		@entityCount = 0
-		super()
-		return
-
-	loadData: (callback)->
-		providerInvoker = @entityList._providerInvoker
-		if providerInvoker
-			providerInvoker.pageSize = @entityList.pageSize
-			providerInvoker.pageNo = @pageNo
-			if callback
-				providerInvoker.invokeAsync(
-					complete: (success, result)=>
-						if success then @initData(result)
-						cola.callback(callback, success, result)
-				)
-			else
-				result = providerInvoker.invokeSync()
-				@initData(result)
-		return
-
-class cola.EntityList extends LinkedList
-	current: null
-	entityCount: 0
-
-	pageMode: "append"
-	pageSize: 0
-	pageNo: 1
-	pageCount: 1
-
-	_disableObserverCount: 0
-
-	# totalEntityCount
-	# _parent
-	# _parentProperty
-	# _providerInvoker
-
-	constructor: (array, dataType)->
-		@id = cola.uniqueId()
-		@timestamp = cola.sequenceNo()
-		@dataType = dataType
-		if array then @fillData(array)
+		@_pageCount = 0
+		return

 	fillData: (array)->
 		page = @_findPage(@pageNo)
@@ -1307,12 +1321,9 @@

 		next = page._first
 		while page
-			if next
-				next._setDataModel(dataModel)
-				next = next._next
-			else
-				page = page._next
-				next = page?._first
+			for entity in page
+				entity._setDataModel(dataModel)
+			page = page._next
 		return

 	watch: _watch
@@ -1325,71 +1336,69 @@
 		@timestamp = cola.sequenceNo()

 		if setCurrent
-			entity = page._first
-			while entity
+			for entity in page
 				if entity.state != _Entity.STATE_DELETED
 					@setCurrent(entity)
 					break
-				entity = entity._next
 		return

 	_onPathChange: ()->
 		delete @_pathCache

 		page = @_first
-		if !page then return
+		if not page then return

 		next = page._first
 		while page
-			if next
-				next._onPathChange()
-				next = next._next
-			else
+			for entity in page
+				entity._onPathChange()
+			page = page._next
+		return
+
+	_findPrevious: (entity)->
+		if not (entity?.parent isnt @)
+			if entity
+				page = entity._page
+				if page[page.hotIndex] is entity
+					index = page.hotIndex
+				else
+					index = page.indexOf(entity)
+			else
+				page = @_last
+				index = page.length
+
+			while page
+				while index > 0
+					entity = page[--index]
+					if entity.state isnt _Entity.STATE_DELETED
+						return [entity, index]
+
+				page = page._previous
+				index = page?.length
+		return []
+
+	_findNext: (entity)->
+		if not (entity?.parent isnt @)
+			if entity
+				page = entity._page
+				if page[page.hotIndex] is entity
+					index = page.hotIndex
+				else
+					index = page.indexOf(entity)
+			else
+				page = @_first
+				index = -1
+
+			while page
+				lastIndex = page.length - 1
+				while index < lastIndex
+					entity = page[++index]
+					if entity.state isnt _Entity.STATE_DELETED
+						return [entity, index]
+
 				page = page._next
-				next = page?._first
-		return
-
-	_findPrevious: (entity)->
-		return if entity and entity.parent != @
-
-		if entity
-			page = entity._page
-			previous = entity._previous
-		else
-			page = @_last
-			previous = page._last
-
-		while page
-			if previous
-				if previous.state != _Entity.STATE_DELETED
-					return previous
-				else
-					previous = previous._previous
-			else
-				page = page._previous
-				previous = page?._last
-		return
-
-	_findNext: (entity)->
-		return if entity and entity.parent != @
-
-		if entity
-			page = entity._page
-			next = entity._next
-		else
-			page = @_first
-			next = page._first
-
-		while page
-			if next
-				if next.state != _Entity.STATE_DELETED
-					return next
-				else
-					next = next._next
-			else
-				page = page._next
-				next = page?._first
-		return
+				index = -1
+		return []

 	_findPage: (pageNo)->
 		if pageNo < 1 then return null
@@ -1398,9 +1407,9 @@
 				return null

 		page = @_currentPage or @_first
-		if !page then return null
-
-		if page.pageNo == pageNo
+		if not page then return null
+
+		if page.pageNo is pageNo
 			return page
 		else if page.pageNo < pageNo
 			page = page._next
@@ -1429,9 +1438,9 @@
 		insertMode = "end"
 		refPage = @_currentPage or @_first
 		if refPage
-			if refPage.page == pageNo - 1
+			if refPage.page is pageNo - 1
 				insertMode = "after"
-			else if refPage.page == pageNo + 1
+			else if refPage.page is pageNo + 1
 				insertMode = "before"
 			else
 				page = @_last
@@ -1527,16 +1536,24 @@
 		return @

 	insert: (entity, insertMode, refEntity)->
-		if insertMode == "before" or insertMode == "after"
-			if refEntity and refEntity.parent != @
+		if isFinite(insertMode)
+			index = +insertMode
+		else if insertMode is "before" or insertMode is "after"
+			if refEntity and refEntity.parent isnt @
 				refEntity = null
 			refEntity ?= @current
-			if refEntity then page = refEntity._page
-		else if @pageMode == "append"
-			if insertMode == "end"
+			if refEntity
+				page = refEntity._page
+				index = page.indexOf(entity)
+				if insertMode is "after"
+					index++
+		else
+			if insertMode is "end"
 				page = @_last
-			else if insertMode == "begin"
+				index = page.length
+			else if insertMode is "begin"
 				page = @_first
+				index = 0

 		if not page
 			page = @_currentPage
@@ -1561,7 +1578,7 @@
 				return null

 		page._dontAutoSetCurrent = true
-		page._insertElement(entity, insertMode, refEntity)
+		page.insert(entity, index)
 		page._dontAutoSetCurrent = false

 		if entity.state isnt _Entity.STATE_DELETED then @entityCount++
@@ -1580,7 +1597,10 @@
 				entity: entity
 			})

-		if not @current then @setCurrent(entity)
+		if not @current
+			@setCurrent(entity)
+			if 0 <= index < entity.page.length
+				entity.page.hotIndex = index
 		return entity

 	remove: (entity, detach)->
@@ -1599,18 +1619,19 @@

 		if entity is @current
 			changeCurrent = true
-			newCurrent = @_findNext(entity)
-			if not newCurrent then newCurrent = @_findPrevious(entity)
+			ret = @_findNext(entity)
+			if not ret.length then ret = @_findPrevious(entity)
+			[newCurrent, newCurrentIndex] = ret

 		page = entity._page
 		if detach
-			page._removeElement(entity)
+			page.remove(entity)
 			@entityCount--
-		else if entity.state == _Entity.STATE_NEW
+		else if entity.state is _Entity.STATE_NEW
 			entity.setState(_Entity.STATE_DELETED)
-			page._removeElement(entity)
+			page.remove(entity)
 			@entityCount--
-		else if entity.state != _Entity.STATE_DELETED
+		else if entity.state isnt _Entity.STATE_DELETED
 			entity.setState(_Entity.STATE_DELETED)
 			@entityCount--

@@ -1626,7 +1647,10 @@
 				entity: entity
 			})

-		@setCurrent(newCurrent) if changeCurrent
+		if changeCurrent
+			@setCurrent(newCurrent)
+			if newCurrent
+				newCurrent.page.hotIndex = newCurrentIndex
 		return entity

 	empty: ()->
@@ -1635,9 +1659,10 @@
 		return

 	setCurrent: (entity)->
-		if @current is entity or entity?.state is cola.Entity.STATE_DELETED then return @
-
-		if entity and entity.parent != @
+		if @current is entity or entity?.state is cola.Entity.STATE_DELETED
+			return @
+
+		if entity and entity.parent isnt @
 			throw new cola.Exception("The entity is not belongs to this EntityList.")

 		oldCurrent = @current
@@ -1654,7 +1679,10 @@
 		@current = entity

 		if entity
-			@_setCurrentPage(entity._page)
+			page = entity._page
+			@_setCurrentPage(page)
+			if page[page.hotIndex] isnt entity
+				page.hotIndex = page.indexOf(entity)
 			entity._onPathChange()

 		@_notify(cola.constants.MESSAGE_CURRENT_CHANGE, {
@@ -1672,42 +1700,46 @@
 		return @

 	first: ()->
-		entity = @_findNext()
+		[entity, index] = @_findNext()
 		if entity
 			@setCurrent(entity)
+			entity.page.hotIndex = index
 			return entity
 		else
 			return @current

 	previous: ()->
-		entity = @_findPrevious(@current)
+		[entity, index] = @_findPrevious(@current)
 		if entity
 			@setCurrent(entity)
+			entity.page.hotIndex = index
 			return entity
 		else
 			return @current

 	next: ()->
-		entity = @_findNext(@current)
+		[entity, index] = @_findNext(@current)
 		if entity
 			@setCurrent(entity)
+			entity.page.hotIndex = index
 			return entity
 		else
 			return @current

 	last: ()->
-		entity = @_findPrevious()
+		[entity, index] = @_findPrevious()
 		if entity
 			@setCurrent(entity)
+			entity.page.hotIndex = index
 			return entity
 		else
 			return @current

 	hasPrevious: ()->
-		return !!@_findPrevious(@current)
+		return @_findPrevious(@current).length > 0

 	hasNext: ()->
-		return !!@_findNext(@current)
+		return @_findNext(@current).length > 0

 	_reset: ()->
 		@current = null
@@ -1717,7 +1749,7 @@

 		page = @_first
 		while page
-			page._clearElements()
+			page.clear()
 			page = page._next

 		delete @_currentPage
@@ -1745,7 +1777,7 @@
 			arg.originPath = path
 			@_dataModel?.onDataMessage(path, type, arg)

-			if type is cola.constants.MESSAGE_CURRENT_CHANGE or type is cola.constants.MESSAGE_INSERT or type is cola.constants.MESSAGE_REMOVE
+			if type is cola.constants.MESSAGE_CURRENT_CHANGE or type is cola.constants.MESSAGEinsert or type is cola.constants.MESSAGE_REMOVE
 				@_triggerWatcher([ "*" ], type, arg)
 		return

@@ -1766,16 +1798,13 @@
 			page = @_findPage(pageNo)
 			return @ unless page

-		next = page._first
 		i = 0
 		while page
-			if next
-				if deleted or next.state != _Entity.STATE_DELETED
-					if fn.call(@, next, i++) == false then break
-				next = next._next
-			else if page and not pageNo
+			for entity in page
+				if deleted or entity.state isnt _Entity.STATE_DELETED
+					if fn.call(@, entity, i++) is false then break
+			if not pageNo
 				page = page._next
-				next = page?._first
 			else
 				break
 		return @
@@ -1787,31 +1816,21 @@

 		array = []
 		page = @_first
-		if page
-			next = page._first
-			while page
-				if next
-					if deleted or next.state != _Entity.STATE_DELETED
-						array.push(next.toJSON(options))
-					next = next._next
-				else
-					page = page._next
-					next = page?._first
+		while page
+			for entity in page
+				if deleted or entity.state isnt _Entity.STATE_DELETED
+					array.push(entity.toJSON(options))
+			page = page._next
 		return array

 	toArray: ()->
 		array = []
 		page = @_first
-		if page
-			next = page._first
-			while page
-				if next
-					if next.state != _Entity.STATE_DELETED
-						array.push(next)
-					next = next._next
-				else
-					page = page._next
-					next = page?._first
+		while page
+			for entity in page
+				if entity.state isnt _Entity.STATE_DELETED
+					array.push(entity)
+			page = page._next
 		return array

 	filter: (criteria, option)->


Fix bug with config change server reload

--- src/core/server.coffee
+++ src/core/server.coffee
@@ -283,8 +283,11 @@
     async.waterfall [stop, start], callback

   stop = (callback) ->
-    server.close callback
-    server = null
+    if server?
+      server.close callback
+      server = null
+    else
+      callback()

   start = (callback) ->
     handler = setup env


Remove console.log

Remove console.log

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -26,7 +26,6 @@
 				skuDimension = sku.dimensions[dimension]
 				if $.inArray(skuDimension, uniqueDimensionsMap[dimension]) is -1
 					uniqueDimensionsMap[dimension].push skuDimension
-		console.log uniqueDimensionsMap
 		return uniqueDimensionsMap

 	findUndefinedDimensions: =>


Fixed binding type variables, which must be done for all definitions, to get correct fixed var sets

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -419,8 +419,11 @@
   bindTypeVariables: (vars) ->
     addAllToSet @_scope().boundTypeVariables, vars

+  bindTypeVariablesToOuterScope: (vars) ->
+    addAllToSet @_outerScope().boundTypeVariables, vars
+
   allBoundTypeVariables: ->
-    substituteVarNames this, concatSets (for scope in @scopes
+    substituteVarNames this, concatSets (for scope in @scopes[...-1]
       scope.boundTypeVariables)...

   addToScopeConstraints: (constraints) ->
@@ -1220,7 +1223,6 @@

   definedNames = ctx.definedNames()

-  # log "is deferriing", (print pattern), ctx.shouldDefer()
   # Make sure deferred names are added to scope so they are compiled within functions
   if ctx.shouldDefer()
     shouldDeclareFinally = ctx.isDeclared ctx.deferReason()[1]
@@ -1250,7 +1252,6 @@
     if not ctx.isCurrentlyDeclared name
       ctx.declare name, id: id
     if deps.length > 0
-      # log "adding top level lhs to deferred #{name}", deps
       currentType = substitute ctx.substitution, type
       ctx.addToDeferred
         name: name
@@ -1279,7 +1280,7 @@
       # Check the declared type
       declaredType = ctx.type name
       explicitType = freshInstance ctx, declaredType
-      updatedDeclaredType = quantifyUnbound ctx, (substitute ctx.substitution, explicitType)
+      updatedDeclaredType = quantifyUnbound ctx, (substitute ctx.substitution, explicitType) # to rename user-named type vars
       unify ctx, currentType.type, explicitType.type
       unifiedType = substitute ctx.substitution, explicitType
       inferredType = quantifyUnbound ctx, unifiedType
@@ -1523,8 +1524,8 @@
         withOrigin (ctx.freshTypeVariable star), param
       paramTypeVars = map newParamType, params
       paramTypes = map (__ toForAll, toConstrained), paramTypeVars
+      ctx.bindTypeVariables (map (({name}) -> name), paramTypeVars)
       ctx.newLateScope()
-      ctx.bindTypeVariables (map (({name}) -> name), paramTypeVars)
       # log "adding types", (map _symbol, params), paramTypes
       ctx.declareTyped paramNames, paramTypes
       for param in params
@@ -1839,8 +1840,8 @@
     #   allDeclared = (ctx.isClass c for c in superClasses)

     methodDefinitions = pairs wheres
+    ctx.bindTypeVariables paramNames
     ctx.newScope()
-    ctx.bindTypeVariables paramNames
     definitionList ctx, methodDefinitions
     declarations = ctx.currentDeclarations()
     ctx.closeScope()
@@ -1996,7 +1997,7 @@
     malformed ctx, typeExpression, 'Type doesn\'t match class type'
     return null

-  ctx.bindTypeVariables setToArray (findFree freshInstanceType)
+  ctx.bindTypeVariablesToOuterScope setToArray (findFree freshInstanceType)
   for name, {arity, type} of values classDeclaration.declarations
     freshType = freshInstance ctx, type
     instanceSpecificType = substitute sub, freshType
@@ -2509,7 +2510,7 @@
     isSubset fixedVars, (findUnconstrained constraint)
   [deferred, retained] = partition isFixed, impliedConstraints
   quantifiedVars = findFree finalType = substitute ctx.substitution, type
-  impliedVars = concatSets (map findConstrained, retained)...
+  impliedVars = concatSets (map findConstrained, impliedConstraints)...
   validVars = concatSets quantifiedVars, impliedVars
   isAmbiguous = (constraint) ->
     not isSubset validVars, (findUnconstrained constraint)
@@ -2742,7 +2743,7 @@
       id = (ctx.definitionName() and ctx.definitionId()) ?
         (ctx.currentDeclarationId symbol) ? ctx.freshId()
       type = toConstrained ctx.freshTypeVariable star
-      # ctx.bindTypeVariables [type.type.name]
+      ctx.bindTypeVariables [type.type.name]
       ctx.addToDefinedNames {name: symbol, id: id, type: type}
       type: type
       id: id
@@ -2762,7 +2763,7 @@
       ctx.addToDeferredNames {name: symbol, type: (mapOrigin type, atom)}
       nameTranslate ctx, atom, symbol, type
     else
-      # log "deferring in rhs for #{symbol}", ctx._deferrableDefinition().name
+      # console.log "deferring because of #{symbol} rhs:", ctx._deferrableDefinition().name
       ctx.doDefer atom, symbol
       translation: deferredExpression()

@@ -3005,7 +3006,6 @@
   inferredType = freshInstance ctx, canonicalType
   ctx.extendSubstitution matchType inferredType.type, (substitute ctx.substitution, type).type
   reduceConstraints ctx, inferredType.constraints
-  console.log (printType (substitute ctx.substitution, inferredType))
   (substitute ctx.substitution, inferredType).constraints

 irReference = (name, type, scopeIndex) ->
@@ -5267,7 +5267,6 @@

 findMatchingDefinitionsOnType = (type, definitionLists) ->
   ctx = new Context
-  console.log definitionLists
   [typed, untyped] = unzip (for definitions, i in definitionLists
     isValid = (name, def) ->
       def.type? and not def.type.TempType
@@ -5512,10 +5511,12 @@
     log (collapse toHtml compiled.ast)
     if _notEmpty compiled.subs
       log map formatFail, compiled.subs
+      failure = yes
     if result isnt (got = eval compiled.compiled)
       log "'#{testName}' expected", result, "got", got
+      success = no
     else
-      success = yes
+      success = not failure
   catch e
     logError "Error in test |#{testName}|\n#{teaSource}\n", e
   success
@@ -6680,38 +6681,11 @@
     (map f lines))

   f (fn [x]
-    x
-    y (show x))
+    x)

   expand (fn [x]
     gg
     gg (g {""}))
-  """
-  '3', 3
-
-  'overloaded reference 2'
-  """
-  Mappable (class [wrapper]
-    map (fn [what onto]
-      (: (Fn (Fn a b) (wrapper a) (wrapper b)))))
-
-  array-mappable (instance (Mappable Array)
-    map (macro [what over]
-      (: (Fn (Fn a b) (Array a) (Array b)))
-      (Js.method over "map" {what})))
-
-  Show (class [a]
-    show (fn [x] (: (Fn a String))))
-
-  show-string (instance (Show String)
-    show (fn [x] x))
-
-  g (fn [x]
-    (map f {}))
-
-  f (fn [x]
-    ""
-    y (show x))
   """
   '3', 3


unused barChart directive removed

unused barChart directive removed

--- src/common/directives/barChart.coffee
+++ src/common/directives/barChart.coffee
@@ -1,56 +0,0 @@
-'use strict'
-
-# module definition
-# ------------------------------------------------------------
-angular.module('hoay.directives.charts', [])
-
-.directive 'barchart', [
-  '$log',
-  '$timeout'
-  ($log, $timeout) ->
-    restrict: 'E'
-    replace: true
-    template: '''
-

-

-

-      '''
-    link: (scope, element, attrs)->
-      # get bar using Zepto
-      bar = $(element[0]).find ".bar-single"
-      bar.addClass "bar-#{attrs.ngType}"
-
-      scope.$watch attrs.ngPercentX, (value)->
-        unless value is undefined
-          bar.css "left", "#{value}%"
-
-      scope.$watch attrs.ngPercentWidth, (value)->
-        unless value is undefined
-          bar.css "width", "#{value}%"
-
-      scope.$watch attrs.ngPercentHeight, (value)->
-        unless value is undefined
-          # setting default values
-          t = "translate(0,100%) scaleY(0)"
-          bar.css getTransform(t)
-          # just grabing any css value,
-          # which delays starting animate()
-          # to avoid layout issues setting default values before
-          temp = bar.css "height"
-#          $timeout animate, 1
-          animate()
-
-      animate = ->
-        value = attrs.ngPercentHeight
-        t = "translate(0, #{(100-value)/2}%) scaleY(#{value/100})"
-        bar.css getTransform(t)
-
-
-      getTransform = (value)->
-        'transform': value
-        '-moz-transform': value
-        '-webkit-transform': value
-        '-ms-transform': value
-        '-o-transform': value
-
-]


Show type errors in expressions and fix format with no args

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1313,20 +1313,17 @@
       isDeclaredConstraint = (c) ->
         entailed = entail ctx, unifiedType.constraints, c
       notDeclared = filter (__ _not, isDeclaredConstraint), checked = constraints #(substituteList ctx.substitution, constraints)
-      {success, error} = deferConstraints ctx, notDeclared, currentType
-      if error
-        ctx.extendSubstitution error
-      else
+      {success} = deferConstraints ctx, notDeclared, currentType
+      if success
         [deferredConstraints, retainedConstraints] = success
         if _notEmpty retainedConstraints
           ctx.extendSubstitution substitutionFail "#{name}'s context is too weak, missing #{listOf (map printType, retainedConstraints)}"
   else
     if not ctx.isAtNonDeferrableDefinition()
-      {success, error} = deferConstraints ctx,
+      {success} = deferConstraints ctx,
         constraints#(substituteList ctx.substitution, constraints),
         currentType
-      if error
-        ctx.extendSubstitution error
+      if not success
         return
       [deferredConstraints, retainedConstraints] = success
       # Finalizing type again after possibly added substitution when defer constraints
@@ -2217,6 +2214,8 @@
     s: stringType
     c: charType
   [formatStringToken, args...] = _arguments call
+  if not formatStringToken
+    return malformed ctx, call, 'Format string required.'
   compiledArgs = termsCompile ctx, args
   if not all map _tea, args
     call.tea = toConstrained stringType
@@ -2577,6 +2576,12 @@

 # Returns deferred and retained constraints
 deferConstraints = (ctx, constraints, type) ->
+  result = tryDeferConstraints ctx, constraints, type
+  if result.error
+    ctx.extendSubstitution result.error
+  result
+
+tryDeferConstraints = (ctx, constraints, type) ->
   reducedConstraints = reduceConstraints ctx, constraints
   if not reducedConstraints.success
     return reducedConstraints
@@ -2593,10 +2598,9 @@
     not isSubset validVars, (findUnconstrained constraint)
   [ambiguous, retained] = partition isAmbiguous, retained
   if _notEmpty ambiguous
-    {error:
-      substitutionFail
-        message: "Constraint #{printType ambiguous[0]} is ambiguous for inferred type #{printType finalType}"
-        conflicts: [type.type.origin, ambiguous[0].origin]}
+    {error: substitutionFail
+      message: "Constraint #{printType ambiguous[0]} is ambiguous for inferred type #{printType finalType}"
+      conflicts: [type.type.origin, ambiguous[0].origin]}
   else
     success: [deferred, retained]


simplify hideAnswers logic

simplify hideAnswers logic

--- src/components/exercise-card.cjsx
+++ src/components/exercise-card.cjsx
@@ -52,10 +52,9 @@
     content = @props.exercise.content
     question = content.questions[0]

-    renderedAnswers = if @props.hideAnswers
-      []
-    else
-      _(question.answers).chain().sortBy('id').map(@renderAnswer).value()
+    unless @props.hideAnswers
+      renderedAnswers = _(question.answers)
+        .chain().sortBy('id').map(@renderAnswer).value()

     tags = _.clone @props.exercise.tags
     unless @props.displayAllTags


Inlining macros, into a single line

Inlining macros, into a single line

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -767,17 +767,20 @@
         (call_ (join [expandedOp], (_arguments call)))

 callMacroCompile = (ctx, call) ->
+  expanded = callMacroExpand ctx, call
+  if isTranslated expanded
+    expanded
+  else
+    compiled = expressionCompile ctx, expanded
+    retrieve call, expanded
+    compiled
+
+callMacroExpand = (ctx, call) ->
   op = _operator call
   op.label = 'keyword'
   macro = ctx.macro op.symbol
   op.id = macro.id
-  expanded = macro ctx, call
-  if isTranslated expanded
-    expanded
-  else
-    compiled = expressionCompile ctx, expanded
-    retrieve call, expanded
-    compiled
+  macro ctx, call

 isTranslated = (result) ->
   (isSimpleTranslated result) or (Array.isArray result) and ((_empty result) or (isSimpleTranslated result[0]))
@@ -5726,7 +5729,6 @@
       js: ''
     }
   else
-    module = lookupCompiledModule moduleName
     {modules, ctx} = contextWithDependencies moduleDependencies moduleName
     [expression] = _terms ast
     compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
@@ -5737,6 +5739,12 @@
     ast: ast
     errors: errors
     malformed: ctx.isMalformed
+
+expandCall = (moduleName, call) ->
+  {modules, ctx} = contextWithDependencies moduleDependencies moduleName
+  expanded = callMacroExpand ctx, call
+  if not isTranslated expanded
+    expanded

 importsFor = (moduleSet) ->
   lookupDefinitions = (name) ->
@@ -7538,6 +7546,7 @@
 exports.compileTopLevel = compileTopLevel
 exports.compileModule = compileModule
 exports.compileExpression = compileExpression
+exports.expand = expandCall
 exports.findAvailableTypes = findAvailableTypes
 exports.findMatchingDefinitions = findMatchingDefinitions
 exports.findDocsFor = findDocsFor


adding saved notification examples to kitchen-sink.coffee

--- examples/kitchen-sink/coffeescript/kitchen-sink.coffee
+++ examples/kitchen-sink/coffeescript/kitchen-sink.coffee
@@ -30,7 +30,8 @@
   formOpts: ->
     data: @linkState("account")
     errors: ["Test Error", "Moo"]
-    onSubmit: @onSubmit
+    onSubmit: @onSubmit,
+    # layout: "horizontal"

   render: ->
     frig @formOpts(), (f) =>
@@ -50,7 +51,12 @@
             else
               "No Avatar"
           # Email and github account emails (to the left of the avatar image)
-          f.input "email", xs: 10, saved: false
+          f.input "InputWithoutALabel",
+            xs: 10
+            label: false
+            saved: true
+            placeholder: "Input Without a Label"
+          f.input "email", xs: 10, saved: true
           f.input("githubAccount",
             type: "typeahead",
             errors: ["test"],
@@ -59,13 +65,16 @@
               parser: @parseGithubResponse
               maxReqsPerMinute: 10
             }
-            xs: 10
+            xs: 10,
+            saved: true
           ),

         div className: "row",
           f.input("friendsGithubAccounts",
             type: "typeahead",
             multiple: true,
+            label: false,
+            saved: true
             remote: {
               url: @githubSearchUrl
               parser: @parseGithubResponse
@@ -81,31 +90,69 @@
           f.input "description",
             className: "testing-class-name"
             type: "text"
-            rows: 10
+            rows: 5
+            xs: 12
+            saved: true
+
+          f.input "description-two",
+            className: "testing-class-name"
+            type: "text"
+            rows: 5
+            label: false
+            placeholder: "Description without a label"
+            saved: true
+            xs: 12

         div className: "row",
           f.input "time_of_day",
             type: "switch",
-            xs: "6"
-            label: "Time of Day"
+            xs: "6",
+            label: "Time of Day",
+            saved: true
             errors: ["This error is an example", "As is this one"]
+          f.input "time_of_night",
+            type: "switch",
+            xs: "6",
+            label: "Time of Night",
+            label: false
+            saved: true

+        div className: "row",
           f.input("uploadAvatar",
             type: "file",
             xs: "6"
             label: "Uploading Avatar"
+            saved: true
           ),
-
+          f.input "uploadVirus",
+            type: "file",
+            xs: "6"
+            label: false
+            saved: true
         div className: "row",
           f.input "startTime",
             type: "time"
-            xs: "6"
+            xs: "12"
             placeholder: "12:00pm"
             label: "Start Time"
-          f.input "color",
+            saved: true
+          f.input "endTime",
+            type: "time"
+            xs: "12"
+            placeholder: "End Time"
+            label: false
+            saved: true
+        div className: "row",
+          f.input "colorOne",
             type: "color"
-            xs: "6"
-
+            xs: "12"
+            label: "Color One"
+            saved: true
+          f.input "colorTwo",
+            type: "color"
+            xs: "12",
+            label: false
+            saved: true
         div className: "row",
           div className: "col-xs-12",
             h3 {}, "Nested Fields (Eg. Has Many)"
@@ -122,7 +169,7 @@
             h3 {}, "Additional Sketchy Info"

         div className: "row",
-          f.input "shareSketchyInfo"
+          f.input "shareSketchyInfo", saved: true
           if @state.account.shareSketchyInfo
             [
               f.input "socialSecurityNumber"


fix test

fix test

--- karma/flows/test_controllers.coffee
+++ karma/flows/test_controllers.coffee
@@ -244,12 +244,13 @@

         # USSD flow
         flowService.flow.flow_type = 'U'
-        expect(modalScope.validActionFilter(getAction('reply'))).toBe(true)
+        expect(modalScope.validActionFilter(getAction('reply'))).toBe(false)
         expect(modalScope.validActionFilter(getAction('say'))).toBe(false)
         expect(modalScope.validActionFilter(getAction('play'))).toBe(false)
         expect(modalScope.validActionFilter(getAction('api'))).toBe(false)

       $timeout.flush()
+

     it 'updateContactAction should not duplicate fields on save', ->


Let the filtering to be case insensitive

--- src/js/controllers/photosets-ctrl.coffee
+++ src/js/controllers/photosets-ctrl.coffee
@@ -19,7 +19,7 @@
     filter = ->
       $scope.photosets = if $scope.isFilterEnabled and !_.isEmpty($scope.filterString)
         photosets.filter (photoset) ->
-          photoset.title._content.indexOf($scope.filterString) >= 0
+          photoset.title._content.toLowerCase().indexOf($scope.filterString.toLowerCase()) >= 0
       else
         photosets
       $scope.totalPhotosets = $scope.photosets.length


Allow buffer access by index for addressable ports

--- src/lib/InPort.coffee
+++ src/lib/InPort.coffee
@@ -69,13 +69,9 @@
     return if @process
     return if @options.control and ip.type isnt 'data'
     ip.owner = @nodeInstance
-    ip.index = id
+    ip.index = id if @isAddressable()

-    if ip.scope?
-      @scopedBuffer[ip.scope] = [] unless ip.scope of @scopedBuffer
-      buf = @scopedBuffer[ip.scope]
-    else
-      buf = @buffer
+    buf = @prepareBufferForIP ip
     buf.push ip
     buf.shift() if @options.control and buf.length > 1

@@ -116,7 +112,21 @@

   prepareBuffer: ->
     @buffer = []
+    @indexedBuffer = {} if @isAddressable()
     @scopedBuffer = {}
+
+  prepareBufferForIP: (ip) ->
+    if @isAddressable()
+      if ip.scope?
+        @scopedBuffer[ip.scope] = [] unless ip.scope of @scopedBuffer
+        @scopedBuffer[ip.scope][ip.index] = [] unless ip.index of @scopedBuffer[ip.scope]
+        return @scopedBuffer[ip.scope][ip.index]
+      @indexedBuffer[ip.index] = [] unless ip.index of @indexedBuffer
+      return @indexedBuffer[ip.index]
+    if ip.scope?
+      @scopedBuffer[ip.scope] = [] unless ip.scope of @scopedBuffer
+      return @scopedBuffer[ip.scope]
+    return @buffer

   validateData: (data) ->
     return unless @options.values
@@ -137,7 +147,7 @@
       throw new Error 'Contains query is only possible on buffered ports'
     @buffer.filter((packet) -> return true if packet.event is 'data').length

-  getBuffer: (scope) ->
+  getBuffer: (scope, idx) ->
     if scope?
       return undefined unless scope of @scopedBuffer
       buf = @scopedBuffer[scope]
@@ -146,32 +156,33 @@
     return buf

   # Fetches a packet from the port
-  get: (scope) ->
-    buf = @getBuffer scope
+  get: (scope, idx) ->
+    buf = @getBuffer scope, idx
     return if @options.control then buf[buf.length - 1] else buf.shift()

   # Returns true if port contains packet(s) matching the validator
-  has: (scope, validate) ->
-    buf = @getBuffer scope
+  has: (scope, idx, validate) ->
+    unless @isAddressable()
+      validate = idx
+      idx = null
+    buf = @getBuffer scope, idx
     return false unless buf?.length
     for packet in buf
       return true if validate packet
     false

   # Returns the number of data packets in an inport
-  length: (scope) ->
-    buf = @getBuffer scope
+  length: (scope, idx) ->
+    buf = @getBuffer scope, idx
     return 0 unless buf
     return buf.length

   # Tells if buffer has packets or not
-  ready: (scope) ->
+  ready: (scope, idx) ->
     return @length(scope) > 0

   # Clears inport buffers
   clear: ->
-    @buffer = [] if @buffer
-    @scopedBuffer = {} if @scopedBuffer
-
+    @prepareBuffer()

 module.exports = InPort


Don't filter on  after query

Don't filter on  after query

--- src/lib/cursor.coffee
+++ src/lib/cursor.coffee
@@ -183,10 +183,6 @@
   selectResults: (json) ->
     json = json.slice(0, 1) if @_cursor.$one

-    if @_cursor.$unique
-      $select = if @_cursor.$white_list then _.intersection(@_cursor.$unique, @_cursor.$white_list) else @_cursor.$unique
-      json = (_.pick(item, $select) for item in json)
-
     # TODO: OPTIMIZE TO REMOVE 'id' and '_rev' if needed
     if @_cursor.$values
       $values = if @_cursor.$white_list then _.intersection(@_cursor.$values, @_cursor.$white_list) else @_cursor.$values


Proxy piping for the win!

Proxy piping for the win!

--- ntrapy.coffee
+++ ntrapy.coffee
@@ -77,37 +77,11 @@
   res.clearCookie "AuthSession"
   res.send "Logged out!"

-app.post "/roush/*", (req, res) ->
-  console.log req.params[0]
-  res.send "Stub"
-  #auth = req.cookies["AuthSession"]
-  #unless auth?
-  #  res.send 401, "Error: Login first"
-  #else
-    #nano = require("nano")
-    #  url: "http://localhost:5984"
-    #  cookie: "AuthSession=" + auth
-    #
-    #db = nano.use req.params.db
-    #db.get req.params.doc, (err, body) ->
-    #  if err?
-    #    res.send 400, "Error: /" + req.params.db + "/" + req.params.doc + " returned: " + err.reason
-    #  else
-    #    res.cookie headers["set-cookie"] if headers?["set-cookie"]
-    #    res.send body
-    #res.send "Stub"
-
-app.get "/roush/*", (req, res) ->
-
-  #nano = require("nano") "http://localhost:5984"
-  #db = nano.use req.params.db
-  #db.get req.params.doc, (err, body) ->
-  #  if err?
-  #    res.send 400, "Error: /" + req.params.db + "/" + req.params.doc + " returned: " + err.reason
-  #  else
-  #    res.cookie headers["set-cookie"] if headers?["set-cookie"]
-  #    res.send body
-
+app.all "/roush/?*", (req, res) ->
+  req.pipe(request
+    url: "https://localhost:8080/" + req?.params?[0]
+    followRedirect: true
+  ).pipe(res)

 # HTTP server
 http.createServer(app).listen app.get("port"), ->


Fix search issues menu link

Fix search issues menu link

The previous link would give a 404 when the user was not logged in to
GitHub. This link works whether logged in or not.

--- src/main-process/atom-application.coffee
+++ src/main-process/atom-application.coffee
@@ -238,7 +238,7 @@
     @on 'application:open-faq', -> shell.openExternal('https://atom.io/faq')
     @on 'application:open-terms-of-use', -> shell.openExternal('https://atom.io/terms')
     @on 'application:report-issue', -> shell.openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues')
-    @on 'application:search-issues', -> shell.openExternal('https://github.com/issues?q=+is%3Aissue+user%3Aatom')
+    @on 'application:search-issues', -> shell.openExternal('https://github.com/search?q=+is%3Aissue+user%3Aatom')

     @on 'application:install-update', =>
       @quitting = true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -8,7 +8,7 @@

     # => called with no arguments, flicker will
     # toggle the player's alpha value between 0.9 (value provided above)
-    # and 0.5 (flickerable defau)
+    # and 0.5 (flickerable default) every
     player.flicker()

 @name Flickerable


Adding linter-javac and linter-pylint

Adding linter-javac and linter-pylint

--- packages.cson
+++ packages.cson
@@ -40,7 +40,9 @@
   "linter"
   "linter-clang"
   "linter-coffeescript"
+  "linter-javac"
   "linter-js-tenx"
+  "linter-pylint"
   "markdown-writer"
   "minimap"
   "pigments"


Reload workspace search results on render

--- src/scripts/modules/workspace/workspace.coffee
+++ src/scripts/modules/workspace/workspace.coffee
@@ -28,13 +28,13 @@
     initialize: () ->
       super()

-      @model = searchResults.load("?q=authorID:#{session.get('id')}%20sort:pubDate", WORKSPACE_URI)
-
       @listenTo(@model, 'change:error', @displayError)
       @listenTo(@model, 'change:results change:loaded', @render) if @model

     onRender: () ->
+      @model = searchResults.load("?authorID:#{session.get('id')}", WORKSPACE_URI)
+
       @regions.workspace.show(new WorkspaceResultsView({model: @model}))

     displayError: () ->


Fix RegEx to find messages with near indicator (Closes: #4)

Extend the regular expression that parses the perl interpreter syntaxcheck output, to be able to find errors ending with '…, near "…"'.

--- lib/linter-perl-check-syntax.coffee
+++ lib/linter-perl-check-syntax.coffee
@@ -4,7 +4,7 @@
 pkg = require('../package.json')

 class PerlCheckSyntax
-    REGEX = /(:<message>[\s\S]*?) at (:<file>.*) line (:<line>\d+)\./ig
+    REGEX = /(:<message>[\s\S]*?) at (:<file>.*) line (:<line>\d+)(?:\.|, near.*)/ig
     helpers = require('atom-linter')

     lint: (textEditor, settings) ->


client: don't redirect to Account page on pass. reset

--- client/Login/AppController.coffee
+++ client/Login/AppController.coffee
@@ -9,7 +9,7 @@
   handleResetRoute = ({params:{token}})->
     KD.singleton('appManager').open 'Login', (app) ->
       if KD.isLoggedIn()
-        KD.getSingleton('router').handleRoute "/Account/Profile?focus=password&token=#{token}"
+        KD.getSingleton('router').handleRoute "/Activity"
       else
         app.getView().setCustomDataToForm('reset', {recoveryToken:token})
         app.getView().animateToForm('reset')


handle datahubs with no datasets on dashboard page

--- client/code/view/dashboard.coffee
+++ client/code/view/dashboard.coffee
@@ -74,14 +74,17 @@
       url: "/api/#{user.get 'shortName'}/datasets"
       dataType: 'json'
       success: (datasets) =>
-        collection = new Cu.Collection.Datasets datasets
-        collection.forEach (dataset) ->
-          if dataset.get('state') isnt 'deleted'
-            view = new Cu.View.DatasetRow
-              model: dataset
-              clickable: false
-            $('tr.loading', $section).remove()
-            $('tbody', $section).append view.render().el
+        if datasets.length
+          collection = new Cu.Collection.Datasets datasets
+          collection.forEach (dataset) ->
+            if dataset.get('state') isnt 'deleted'
+              view = new Cu.View.DatasetRow
+                model: dataset
+                clickable: false
+              $('tr.loading', $section).remove()
+              $('tbody', $section).append view.render().el
+        else
+          $('tr.loading', $section).remove()
       error: () =>
         $('tbody', $section).html """
                                   <tr class="ajax-error">


spin loading thing more

spin loading thing more

--- client/code/view/tool/tile.coffee
+++ client/code/view/tool/tile.coffee
@@ -30,7 +30,7 @@
       success: (dataset, resp, options) =>
         dataset.installPlugin @model.get('name'), (err, view) =>
           console.warn 'Error', err if err?
-          @$el.removeClass 'loading'
           window.app.navigate "/dataset/#{dataset.id}/view/#{view.id}", trigger: true
       error: (model, xhr, options) ->
+        @$el.removeClass 'loading'
         console.warn xhr


Fixing rendering bug in Firefox 3.6

Fixing rendering bug in Firefox 3.6

--- src/js/Renderer.coffee
+++ src/js/Renderer.coffee
@@ -59,7 +59,7 @@
       [x, y] = @renderingCoordinatesForMarble marble

       context.beginPath()
-      context.arc x, y, marble.radius, 0, Math.PI * 2
+      context.arc x, y, marble.radius, 0, Math.PI * 2, yes
       context.closePath()
       context.fill()


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -66,10 +66,7 @@
             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!
             </p>

-            <Alert bsStyle="danger">
-                27 января и 10 февраля занятий в ННГУ не будет в связи с проведением, соответственно, областной и городской олимпиад.
-            </Alert>
-
+
             {
             if @state.result?.error
                 <Alert bsStyle="danger">


Load last game version, not first

Load last game version, not first

--- client/src/app/model/games.coffee
+++ client/src/app/model/games.coffee
@@ -93,13 +93,14 @@
       .success( (result) -> allGames.push game for game in result )
     allGames

+  # Load the game content and the creator info, then put it all into currentGame
   load: (game) ->
-    # Load the game content and the creator info, then put it all into currentGame
+    # TODO Optimize - Query for the last version only
     getVersion = $http.get('/game-versions?gameId=' + game.id)
     getCreator = $http.get("/users?id=#{game.ownerId}")
     updateCurrentGame = ([version, creator]) ->
       currentGame.info = game
-      currentGame.version = gameConverter.convertGameVersionFromEmbeddedJson(version.data[0])
+      currentGame.version = gameConverter.convertGameVersionFromEmbeddedJson(version.data[version.data.length-1])
       currentGame.creator = creator.data.username
       console.log("loaded game", currentGame)
     onError = (error) -> console.log("Error loading game", error) # TODO: notify the user of the error


Update test for backbone-relational

Update test for backbone-relational

--- test/ecosystem/backbone-relational/test.coffee
+++ test/ecosystem/backbone-relational/test.coffee
@@ -16,7 +16,7 @@
     assert.ok(!!kb, 'kb')
     done()

-  Backbone.RelationalModel.store.addModelScope(window)
+  Backbone.Relational.store.addModelScope(window)

   window.Person = Backbone.RelationalModel.extend({
     relations: [{


I hate this code so bad

I hate this code so bad

--- src/legal-eagle.coffee
+++ src/legal-eagle.coffee
@@ -47,15 +47,16 @@
   else if license?
     unless typeof license is 'string'
       license = license.type ? 'UNKNOWN'
-    license = 'BSD' if license.match /^[\s(]*BSD-.*/
-    license = 'Apache' if license.match /^[\s(]*Apache.*/
-    license = 'ISC' if license.match /^[\s(]*ISC.*/
+    license = 'BSD' if license.match /[\s(]*BSD-.*/
+    license = 'Apache' if license.match /[\s(]*Apache.*/
+    license = 'ISC' if license.match /[\s(]*ISC.*/
+    license = 'MIT' if license.match /[\s(]*MIT.*/
     license = 'WTF' if license is 'WTFPL'
-    license = 'Unlicense' if license.match /^[\s(]*unlicen[sc]e$/i
-    license = 'CC-BY' if license.match /^[\s(]*CC-BY(-\d(\.\d)*)?$/i
-    license = 'Public Domain' if license.match /^[\s(]*Public Domain/i
-    license = 'LGPL' if license.match /^[\s(]*LGPL(-.+)*/
-    license = 'GPL' if license.match /^[\s(]*[^L]GPL(-.+)*/
+    license = 'Unlicense' if license.match /[\s(]*unlicen[sc]e/i
+    license = 'CC-BY' if license.match /[\s(]*CC-BY(-\d(\.\d)*)?/i
+    license = 'Public Domain' if license.match /[\s(]*Public Domain/i
+    license = 'LGPL' if license.match /[\s(]*LGPL(-.+)*/
+    license = 'GPL' if license.match /[\s(]*[^L]GPL(-.+)*/
     {license, source: 'package.json'}
   else if readme and readme isnt 'ERROR: No README data found!'
     extractLicenseFromReadme(readme) ? {license: 'UNKNOWN'}


Fix "DOMException: INDEX_SIZE_ERR" error in IE 11 🤷♂

--- test/src/system/attachment_gallery_test.coffee
+++ test/src/system/attachment_gallery_test.coffee
@@ -1,4 +1,4 @@
-{assert, clickToolbarButton, createImageAttachment, insertAttachments, moveCursor, pressKey, test, testGroup, typeCharacters} = Trix.TestHelpers
+{assert, clickToolbarButton, createImageAttachment, defer, insertAttachments, moveCursor, pressKey, test, testGroup, typeCharacters} = Trix.TestHelpers

 ORC = Trix.OBJECT_REPLACEMENT_CHARACTER

@@ -29,9 +29,10 @@
     clickToolbarButton attribute: "quote", ->
       typeCharacters "abc", ->
         insertAttachments(createImageAttachments(2))
-        assert.blockAttributes([0, 3], ["quote"])
-        assert.blockAttributes([4, 6], ["attachmentGallery"])
-        expectDocument "abc\n#{ORC}#{ORC}\n"
+        defer ->
+          assert.blockAttributes([0, 3], ["quote"])
+          assert.blockAttributes([4, 6], ["attachmentGallery"])
+          expectDocument "abc\n#{ORC}#{ORC}\n"

 createImageAttachments = (num = 1) ->
   attachments = []


We wanted to keep this async

We wanted to keep this async

--- src/lib/loader/ComponentIo.coffee
+++ src/lib/loader/ComponentIo.coffee
@@ -59,7 +59,9 @@

 exports.register = (loader, callback) ->
   # Start discovery from baseDir
-  customLoader.getModuleComponents loader, loader.baseDir, callback
+  setTimeout ->
+    customLoader.getModuleComponents loader, loader.baseDir, callback
+  , 1

 exports.dynamicLoad = (name, cPath, metadata, callback) ->
   try


remove test which isn't really that important

--- services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee
@@ -152,9 +152,6 @@
 				@RecurlyWrapper.cancelSubscription.called.should.equal true
 				@RecurlyWrapper.cancelSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true

-			it "should send a cancellation email", ->
-				@EmailHandler.sendEmail.calledWith("canceledSubscription", {to:@user.email, first_name:@user.first_name}).should.equal true
-
 	describe "reactiveRecurlySubscription", ->
 		describe "with a user without a subscription", ->
 			beforeEach (done) ->


Mark search results that don't match as found if their parent group is also found.

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -150,6 +150,10 @@
             option.search_text = text

             @results_data[option.group_array_index].search_match = true if option.group_array_index?
+
+          else if option.group_array_index? and @results_data[option.group_array_index].search_match
+            option.search_match = true
+            option.search_text = option.html

     if results < 1 and searchText.length
       this.update_results_content ""


Make sure command/control click works as expected.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -1596,7 +1596,8 @@

     loadLinksInApp: ->
       $(document).on 'click', '[data-behavior~=entry_final_content] a', (event) ->
-        if feedbin.data.view_links_in_app && event.target.nodeName == "A"
+        newTab = (event.ctrlKey || event.metaKey)
+        if feedbin.data.view_links_in_app && event.target.nodeName == "A" && !newTab
           href = $(@).attr('href')
           feedbin.loadLink(href)
           event.preventDefault()


remove announce

remove announce

--- app/assets/javascripts/myrtc.js.coffee
+++ app/assets/javascripts/myrtc.js.coffee
@@ -16,6 +16,7 @@
         direction: "many-to-many"
       )
       connection.onstream = (stream) ->
+        $(".js-accept-webrtc").hide()
         video = getVideo(stream)
         document.getElementById("localVideo").appendChild video  if stream.type is "local"
         if stream.type is "remote"


Properly clear notes bindings to prevent double comments on Ctrl-Enter.

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -77,7 +77,7 @@
     $(document).off "click", ".js-discussion-reply-button"
     $(document).off "click", ".js-add-diff-note-button"
     $(document).off "visibilitychange"
-    $(document).off "keypress", @notes_forms
+    $(document).off "keydown", @notes_forms
     $(document).off "keyup", ".js-note-text"
     $(document).off "click", ".js-note-target-reopen"
     $(document).off "click", ".js-note-target-close"


fix leads/form recaptcha on turbolinks load

--- app/cells/folio/leads/form/form.coffee
+++ app/cells/folio/leads/form/form.coffee
@@ -1,29 +1,35 @@
-$(document).on 'submit', '.f-leads-form__form', (e) ->
-  e.preventDefault()
+$(document)
+  .on 'submit', '.f-leads-form__form', (e) ->
+    e.preventDefault()

-  $form = $(this)
-  $wrap = $form.closest('.f-leads-form')
-  return if $wrap.hasClass('f-leads-form--submitting')
+    $form = $(this)
+    $wrap = $form.closest('.f-leads-form')
+    return if $wrap.hasClass('f-leads-form--submitting')

-  $wrap.addClass('f-leads-form--submitting')
+    $wrap.addClass('f-leads-form--submitting')

-  $.post($form.attr('action'), $form.serialize())
-    .then (response) ->
-      $response = $(response)
-      $wrap.replaceWith($response)
+    $.post($form.attr('action'), $form.serialize())
+      .then (response) ->
+        $response = $(response)
+        $wrap.replaceWith($response)

-      $recaptcha = $response.find('.g-recaptcha')
-      if grecaptcha? and $recaptcha.length
-        grecaptcha.render $recaptcha[0]
+        $recaptcha = $response.find('.g-recaptcha')
+        if grecaptcha? and $recaptcha.length
+          grecaptcha.render $recaptcha[0]

-      $response.trigger('folio:submitted')
+        $response.trigger('folio:submitted')

-      if $response.find('.f-leads-form__message').length
-        $response.trigger('folio:success')
-      else
-        $response.trigger('folio:failure')
+        if $response.find('.f-leads-form__message').length
+          $response.trigger('folio:success')
+        else
+          $response.trigger('folio:failure')

-    .fail ->
-      alert($wrap.data('failure'))
-      $wrap.find('.f-leads-form__submit').prop('disabled', false)
-      $wrap.removeClass('f-leads-form--submitting')
+      .fail ->
+        alert($wrap.data('failure'))
+        $wrap.find('.f-leads-form__submit').prop('disabled', false)
+        $wrap.removeClass('f-leads-form--submitting')
+
+  .on 'turbolinks:load', ->
+    if grecaptcha? and grecaptcha.render
+      $('.f-leads-form__recaptcha .g-recaptcha').each ->
+        grecaptcha.render(this) if $(this).html() is ''


Prefer the last matching *text node* or the first matching node

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -174,7 +174,10 @@
         endPosition = startPosition + walker.currentNode.trixLength

         if startPosition <= location.offset <= endPosition
-          match = walker.currentNode
+          if walker.currentNode.nodeType is Node.TEXT_NODE
+            match = walker.currentNode
+          else
+            match ?= walker.currentNode

         if match?.trixCursorTarget
           break


Guard against nil user for initializing artwork saves

--- mobile/apps/artwork/client/index.coffee
+++ mobile/apps/artwork/client/index.coffee
@@ -15,7 +15,7 @@
   artworkTabsView()
   artwork = new Artwork sd.ARTWORK
   user = CurrentUser.orNull()
-  user.initializeDefaultArtworkCollection()
+  user.initializeDefaultArtworkCollection() if user

   new ArtworkImageView
     artwork: sd.ARTWORK


Remove server from array after destroy and restart. closes #61

--- lib/atom-ternjs-manager.coffee
+++ lib/atom-ternjs-manager.coffee
@@ -216,7 +216,13 @@
       @restartServer()

   restartServer: ->
+    return unless @server
     dir = @server.rootPath
+    for i in [0..@servers.length - 1]
+      if dir is @servers[i].rootPath
+        serverIdx = i
+        break
     @server?.stop()
     @server = null
+    @servers.splice(serverIdx, 1)
     @startServer(dir)


Fetching data by selection

Fetching data by selection

--- src/scripts/app.coffee
+++ src/scripts/app.coffee
@@ -17,10 +17,17 @@
     initialize: ->
         @loadProperties @properties
         @highlightMarkers()
+        @bindEvents()

     render: (data)->
         @el.append @template data

+    bindEvents: ->
+        document.addEventListener "mouseup", ->
+            selection = window.getSelection().toString()
+            fetch "https://www.kinopoisk.ru/search/handler-chromium-extensions?v=1&query=" + encodeURIComponent( selection )
+                .then ( res )-> res.json()
+                .then ( res )-> console.log( res )

     addLinks: (dyn_url, options, filmname)->
         data =


fix debug message

fix debug message

--- src/scripts/fgb.coffee
+++ src/scripts/fgb.coffee
@@ -54,5 +54,6 @@
     if webhook.type is 1 # issue created
       onIssueCreated robot, space, webhook
     else
-      robot.logger.debug "hubot-fgb: unknown webhook type: #{webhook.type}"
+      return if webhook.type <= 17 # max webhook.type
+      robot.logger.warning "hubot-fgb: unknown webhook type: #{webhook.type}"
     res.send 'OK'


* [bug] typo

* [bug] typo

--- src/property-manager/index.coffee
+++ src/property-manager/index.coffee
@@ -1,43 +1 @@
-defineProperty  = require 'util-ex/lib/defineProperty'
-isFunction      = require 'util-ex/lib/is/type/function'
-isObject        = require 'util-ex/lib/is/type/object'
-isString        = require 'util-ex/lib/is/type/string'
-extend          = require 'util-ex/lib/_extend'
-deepEqual       = require 'deep-equal'
-#PropertyManager = require './abstract'
-Simple          = require './simple'
-Normal          = require './normal'
-Advance         = require './advance'
-getkeys         = Object.keys
-
-module.export   = getPropertyManagerClass = (aOptions)->
-
-  gOptPos = aOptions.optionsPosition || 0
-  # it could be null(Simple), ctor(complex attributes), object
-  gAttrs  = aOptions.attributes
-  gAttrsName = '$attributes'
-
-  class PropertyManager
-    constructor: ->@initialize.call @, arguments[gOptPos]
-
-    if isFunction(gAttrs)
-      gAttrs = new gAttr
-      extend @::, Advance::
-    else if isObject gAttrs
-      extend @::, Normal::
-    else
-      extend @::, Simple::
-
-
-    # the non-enumerable property can not be injected directly:
-    defineProperty @::, '$initialize', ->
-      options = arugments[gOptPos]
-      that = @
-      if @super and @self
-        inherited = @super
-        that = @self
-        inherited.call(that, options)
-      that._initialize options if isFunction that._initialize
-      that.assign(options)
-
-
+module.exports  = require './normal'


only show points on features

only show points on features

--- src/story/story.coffee
+++ src/story/story.coffee
@@ -8,9 +8,9 @@
     when 'chore' then 'fa-wrench'
     when 'bug' then 'fa-bug'
     when 'release' then 'fa-flag-checkered'
-  if estimate
-    estimate = React.DOM.span className: 'estimate', estimate
-  React.DOM.i className: "story-type fa #{className}", estimate
+  if storyType is 'feature' and estimate
+    estimateLabel = React.DOM.span className: 'estimate', estimate
+  React.DOM.i className: "story-type fa #{className}", estimateLabel

 storyState = (state)->
   if state


Changing around the status bar html

Changing around the status bar html

--- src/status-bar.coffee
+++ src/status-bar.coffee
@@ -19,15 +19,15 @@

   @content: ->
     @div class: 'status-bar', =>
-      @div class: 'file-info', =>
+      @span class: 'git-branch', outlet: 'branchArea', =>
+        @span class: 'octicons branch-icon'
+        @span class: 'branch-label', outlet: 'branchLabel'
+        @span class: 'git-status', outlet: 'gitStatusIcon'
+      @span class: 'cursor-position', outlet: 'cursorPosition'
+      @span class: 'file-info', =>
         @span class: 'current-path', outlet: 'currentPath'
         @span class: 'buffer-modified', outlet: 'bufferModified'
-      @div class: 'cursor-position', =>
-        @span outlet: 'gitStatusIcon'
-        @span outlet: 'branchArea', =>
-          @span class: 'octicons branch-icon'
-          @span class: 'branch-label', outlet: 'branchLabel'
-        @span outlet: 'cursorPosition'
+

   initialize: (@rootView, @editor) ->
     @updatePathText()
@@ -76,7 +76,7 @@
     @gitStatusIcon.empty()
     return unless path

-    @gitStatusIcon.removeClass().addClass('octicons')
+    @gitStatusIcon.removeClass().addClass('git-status octicons')
     if @buffer.getGit()?.isPathModified(path)
       @gitStatusIcon.addClass('modified-status-icon')
     else if  @buffer.getGit()?.isPathNew(path)
@@ -90,4 +90,4 @@

   updateCursorPositionText: ->
     { row, column } = @editor.getCursorBufferPosition()
-    @cursorPosition.text("#{row + 1},#{column + 1}")
+    @cursorPosition.text("Line #{row + 1}, Column #{column + 1}")


Fix sso_session not retrieved in kpi.svc @load

--- src/services/kpis/kpis.svc.coffee
+++ src/services/kpis/kpis.svc.coffee
@@ -77,14 +77,14 @@
         _self.locked = true

         return $q.all([ImpacMainSvc.loadUserData(force), ImpacDashboardsSvc.load(force)]).then(
-          ->
+          (results)->
             if _.isEmpty(_self.getKpisTemplates()) || force

               # clear array
               _.remove _self.config.kpisTemplates, (-> true)

               orgUids = _.pluck _self.getCurrentDashboard().data_sources, 'uid'
-              ssoSessionId = _self.getSsoSessionId()
+              ssoSessionId = results[0].sso_session

               params =
                 metadata:


Add camera pan

Add camera pan

--- app/scripts/lazerscripts/stage2.coffee
+++ app/scripts/lazerscripts/stage2.coffee
@@ -54,8 +54,8 @@
         options:
           from: 'top'
       => Crafty('Train').trigger 'Progress'
-      @wait 1000
-      @moveCamera y: -100, duration: 3000
+      @moveCamera y: 100, duration: 6000
+      @wait 3000

       @wait 100000


Initial rewrite of keysTree controller viewChange separate send/receive handling, seems to work

--- muleview/app/controller/KeysTree.coffee
+++ muleview/app/controller/KeysTree.coffee
@@ -1,174 +1,186 @@
-Ext.define "Muleview.controller.KeysTree",
-  extend: "Ext.app.Controller"
-  models: [
-    "MuleKey"
-  ]
-
-  multiMode: false
-
-  refs: [
-      ref: "tree"
-      selector: "#keysTree"
-    ,
-      ref: "mainPanel"
-      selector: "#mainPanel"
-    ,
-      ref: "normalModeBtn"
-      selector: "#btnSwitchToNormal"
-    ,
-      ref: "multiModeBtn"
-      selector: "#btnSwitchToMultiple"
-  ]
-
-  updateSelectedKey: ->
-
-    # Selection doesn't apply rendering in multiMode;
-    return if @multiMode
-
-    # Get current selection:
-    selection = @getTree().getSelectionModel().getSelection()
-    selected = selection[0]
-
-    # Quit if nothing selected:
-    return unless selected
-
-    # update:
-    key = selected.get("fullname")
-    Muleview.event "viewChange", key, Muleview.currentRetention
-
-  onCheckChange: ->
-    # return if @updating
-    keys = (node.get("fullname") for node in @getTree().getChecked()).join(",")
-    Muleview.event "viewChange", keys, Muleview.currentRetention
-
-  onLaunch: ->
-    @store = @getTree().getStore()
-
-    @getTree().on
-      selectionchange: @updateSelectedKey
-      itemexpand: @onItemExpand
-      checkchange: @onCheckChange
-      scope: @
-
-    @getNormalModeBtn().on
-      scope: @
-      click: -> @setMultiMode(false)
-
-    @getMultiModeBtn().on
-      scope: @
-      click: -> @setMultiMode(true)
-
-
-    Muleview.app.on
-      viewChange: @updateSelection
-      keysReceived: @addKeys
-      scope: @
-
-    @fillFirstkeys()
-
-  setMultiMode: (multi) ->
-    # Don't change to existing mode:
-    return if !!multi == @multiMode
-
-    # Save current state:
-    @multiMode = !!multi
-
-    # Update buttons:
-    @getMultiModeBtn().setVisible(!multi)
-    @getNormalModeBtn().setVisible(multi)
-
-    # Find current selection
-    selectedNode = @getTree().getSelectionModel().getSelection()[0]
-
-    # Mark all nodes as unchecked, except for the current selected one:
-    @store.getRootNode().cascadeBy (node) ->
-      checked = if multi then (node == selectedNode) else null
-      node.set("checked", checked)
-
-    # If switching back to single, update the graph:
-    @updateSelectedKey() if not multi
-
-  onItemExpand: (node) ->
-    # We set the node as "loading" to reflect that an asynch request is being sent to request deeper-level keys
-    node.set("loading", true)
-    @fetchKeys node.get("fullname"), (keys) =>
-
-      # Commented out the following section after reducing subkey prefetching from 2 to 1 due to apparent overload in key data: <<< BEGIN COMMENTING OUT
-
-
-
-      # We would like to mark subkeys which we know for sure that they are leaves
-      # NOTE: We assume Mule returned at least 2 levels of keys!
-      # for key in keys
-      #   record = @store.getById(key)
-      #   # Since at least 2 levels of keys were received, if a node in the first level has no children then it is definitely a leaf:
-      #   if record.parentNode == node and not record.firstChild
-      #     record.set("leaf", true)
-      #     record.set("loaded", true)
-
-      # END COMMENTING OUT  >>>
-      # Mark the original node as done loading:
-      node.set("loading", false)
-
-
-  fillFirstkeys: ->
-    # Add Root key:
-    root = @getMuleKeyModel().create
-      name: "root"
-      fullname: "_root"
-    @store.setRootNode(root)
-
-    # Ask Mule for the first keys
-    @getTree().setLoading(true)
-    @fetchKeys "", =>
-      @getTree().setLoading(false)
-
-
-  fetchKeys: (parent, callback) ->
-    Muleview.Mule.getSubKeys parent, 1, (keys) =>
-      @addKeys keys
-      callback?(keys)
-
-  addKeys: (newKeys) ->
-    @addKey(key) for key in newKeys
-
-  addKey: (key) ->
-    # Don't add already existing keys:
-    return @store.getRootNode() unless key
-    existingNode = @store.getById(key)
-    return existingNode if existingNode
-
-    # Make sure the parent exists:
-    parentName = key.substring(0, key.lastIndexOf("."))
-    parent = @addKey(parentName)
-
-    # Create the new node:
-    newNode = @getMuleKeyModel().create
-      name: key.substring(key.lastIndexOf(".") + 1)
-      fullname: key
-
-    # Add the new node as a child to its parent:
-    parent.appendChild(newNode)
-
-    # Return the new node:
-    return newNode
-
-  updateSelection: (newKeys) ->
-    # return if @updating
-    # @updating = true
-    keysArr = Ext.Array.from(newKeys)
-    # @setMultiMode(keysArr.length > 1)
-    if keysArr.length == 1
-      record = @store.getById(keysArr[0])
-      @getTree().getSelectionModel().select(record, false, true)
-      while record
-        record.expand()
-        record=record.parentNode
-    # else if keysArr.length > 1
-    #   for key in keysArr
-    #     record = @store.getById(key)
-    #     record?.set("checked", true)
-    #     while record
-    #       record.expand()
-    #       record=record.parentNode
-    # @updating = false
+Ext.define "Muleview.controller.KeysTree",
+  extend: "Ext.app.Controller"
+  models: [
+    "MuleKey"
+  ]
+
+  multiMode: false
+
+  refs: [
+      ref: "tree"
+      selector: "#keysTree"
+    ,
+      ref: "mainPanel"
+      selector: "#mainPanel"
+    ,
+      ref: "normalModeBtn"
+      selector: "#btnSwitchToNormal"
+    ,
+      ref: "multiModeBtn"
+      selector: "#btnSwitchToMultiple"
+  ]
+
+  onLaunch: ->
+    @tree = @getTree()
+    @store = @tree.getStore()
+
+    @tree.on
+      selectionchange: @createViewChangeEvent
+      itemexpand: @onItemExpand
+      checkchange: @createViewChangeEvent
+      scope: @
+
+    @getNormalModeBtn().on
+      click: => @setMultiMode(false)
+
+    @getMultiModeBtn().on
+      click: => @setMultiMode(true)
+
+
+    Muleview.app.on
+      viewChange: @receiveViewChangeEvent
+      keysReceived: @addKeys
+      scope: @
+
+    @fillFirstkeys()
+
+  # ================================================================
+  # Helpers:
+
+  forAllNodes: (fn) ->
+    @store.getRootNode().cascadeBy fn
+
+  getSelectedNode: () ->
+    @getTree().getSelectionModel().getSelection()[0]
+
+  # ================================================================
+  # Event handling:
+
+  setMultiMode: (multi) ->
+    console.log("SETMULTIMODE: ", multi)
+    return if !!multi == @isMulti
+    @isMulti = !!multi
+
+    # Find current selection
+    selectedNode = @getSelectedNode()
+
+    @store.suspendEvents(false) #TODO: check
+
+    @forAllNodes (node) ->
+      checked = if multi then (node == selectedNode) else null
+      node.suspendEvents(false) #TODO: check
+      node.set("checked", checked)
+      node.resumeEvents(true) #TODO: check
+
+    @store.resumeEvents() #TODO: check
+
+    @getMultiModeBtn().setVisible(!multi)
+    @getNormalModeBtn().setVisible(multi)
+
+  createViewChangeEvent: ->
+    console.log("CREATE VIEW CHANGE")
+    chosenKeys = []
+    if @isMulti
+      # keys are chosen by their checked value:
+      chosenKeys = (node.get("fullname") for node in @getTree().getChecked())
+    else
+      # the chosen key is the selected key (no, really)
+      chosenKeys = @getSelectedNode().get("fullname")
+    Muleview.event "viewChange", chosenKeys , Muleview.currentRetention
+
+  receiveViewChangeEvent: (keys) ->
+    console.log("RECEIVE VIEW CHANGE EVENT - ", keys)
+    keysArr = Ext.Array.from(keys)
+    @setMultiMode(@isMulti or keysArr.length > 1)
+
+    @store.suspendEvents(false) #TODO: check
+    @tree.suspendEvents(false) #TODO: check
+
+    if @isMulti
+      @tree.getSelectionModel().deselectAll()
+      @forAllNodes (node) =>
+        checked = Ext.Array.contains(keysArr, node.get("fullname"))
+        node.suspendEvents(false) #TODO: check
+        node.set("checked", checked)
+        node.resumeEvents() #TODO: check
+
+    else
+      chosenNode = @store.getById(keysArr[0])
+      @tree.getSelectionModel().select(chosenNode)
+
+    @store.resumeEvents() #TODO: check
+    @tree.resumeEvents() #TODO: check
+
+  # ================================================================
+  # Data fetching and displaying:
+
+  onItemExpand: (node) ->
+    # We set the node as "loading" to reflect that an asynch request is being sent to request deeper-level keys
+    node.set("loading", true)
+    @fetchKeys node.get("fullname"), (keys) =>
+
+      #TODO: implement numchild
+      # Commented out the following section after reducing subkey prefetching from 2 to 1 due to apparent overload in key data: <<< BEGIN COMMENTING OUT
+
+
+
+      # We would like to mark subkeys which we know for sure that they are leaves
+      # NOTE: We assume Mule returned at least 2 levels of keys!
+      # for key in keys
+      #   record = @store.getById(key)
+      #   # Since at least 2 levels of keys were received, if a node in the first level has no children then it is definitely a leaf:
+      #   if record.parentNode == node and not record.firstChild
+      #     record.set("leaf", true)
+      #     record.set("loaded", true)
+
+      # END COMMENTING OUT  >>>
+      # Mark the original node as done loading:
+      node.set("loading", false)
+
+
+  fillFirstkeys: ->
+    # Add Root key:
+    root = @getMuleKeyModel().create
+      name: "root"
+      fullname: "_root"
+    @store.setRootNode(root)
+
+    # Ask Mule for the first keys
+    @getTree().setLoading(true)
+    @fetchKeys "", =>
+      @getTree().setLoading(false)
+
+  fetchKeys: (parent, callback) ->
+    Muleview.Mule.getSubKeys parent, 1, (keys) =>
+      @addKeys keys
+      callback?(keys)
+
+  addKeys: (newKeys) ->
+    @addKey(key) for key in newKeys
+
+  addKey: (key) ->
+    # Don't add already existing keys:
+    return @store.getRootNode() unless key
+    existingNode = @store.getById(key)
+    return existingNode if existingNode
+
+    # Make sure the parent exists:
+    parentName = key.substring(0, key.lastIndexOf("."))
+    parent = @addKey(parentName)
+
+    # Create the new node:
+    newNode = @getMuleKeyModel().create
+      name: key.substring(key.lastIndexOf(".") + 1)
+      fullname: key
+
+    # Add the new node as a child to its parent:
+    parent.appendChild(newNode)
+
+    # Return the new node:
+    return newNode
+
+  # expand a tree item and all its ancestors:
+  expandKey: (record) ->
+    if (record)
+      record.expand()
+      @expandKey(record.parentNode)


:memo: Remove Private: prefix

:memo: Remove Private: prefix

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -63,14 +63,14 @@
     @setPath(params.filePath) if params?.filePath
     @load() if params?.load

-  # Private: Called by {Serializable} mixin during deserialization.
+  # Called by {Serializable} mixin during deserialization.
   deserializeParams: (params) ->
     params.markers = MarkerManager.deserialize(params.markers, buffer: this)
     params.history = History.deserialize(params.history, buffer: this)
     params.load = true
     params

-  # Private: Called by {Serializable} mixin during serialization.
+  # Called by {Serializable} mixin during serialization.
   serializeParams: ->
     text: @getText()
     markers: @markers.serialize()
@@ -260,8 +260,8 @@

     @delete(new Range(startPoint, endPoint))

-  # Private: Builds a {BufferPatch}, which is used to modify the buffer and is
-  # also pushed into the undo history so it can be undone.
+  # Builds a {BufferPatch}, which is used to modify the buffer and is also
+  # pushed into the undo history so it can be undone.
   buildPatch: (oldRange, newText, normalizeLineEndings) ->
     oldRange = @clipRange(oldRange)
     oldText = @getTextInRange(oldRange)
@@ -270,8 +270,8 @@
     @markers?.handleBufferChange(patch)
     patch

-  # Private: Applies a {BufferPatch} to the buffer based on its old range and
-  # new text. Also applies any {MarkerPatch}es associated with the {BufferPatch}.
+  # Applies a {BufferPatch} to the buffer based on its old range and new text.
+  # Also applies any {MarkerPatch}es associated with the {BufferPatch}.
   applyPatch: ({oldRange, newRange, oldText, newText, normalizeLineEndings, markerPatches}) ->
     @cachedText = null


Don't allocate an extra `Point` object in `textBuffer.clipPosition`

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1432,13 +1432,14 @@
       @getEndPosition()
     else if column < 0
       Point(row, 0)
-    else if column >= @lineLengthForRow(row)
-      if options?.clipDirection is 'forward' and row < @getLastRow()
+    else
+      lineLength = @lineLengthForRow(row)
+      if column >= lineLength and options?.clipDirection is 'forward' and row < @getLastRow()
         Point(row + 1, 0)
+      else if column > lineLength
+        Point(row, lineLength)
       else
-        Point(row, @lineLengthForRow(row))
-    else
-      position
+        position

   ###
   Section: Buffer Operations


:art: Add comments to new methods

:art: Add comments to new methods

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -932,6 +932,7 @@

       @setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)

+  # Move any active selections one column to the left.
   moveSelectionLeft: ->
     selections = @getSelectedBufferRanges()

@@ -953,6 +954,7 @@

       @setSelectedBufferRanges(translatedRanges)

+  # Move any active selections one column to the right.
   moveSelectionRight: ->
     selections = @getSelectedBufferRanges()


few easings for coffee lint

few easings for coffee lint

--- src/steroids/GruntDefaults.coffee
+++ src/steroids/GruntDefaults.coffee
@@ -35,7 +35,12 @@
       newDirPrefix = ""

       coffeeContent = grunt.file.read(filePath, "utf8").toString()
-      errors = coffeelint.lint coffeeContent
+      errors = coffeelint.lint coffeeContent,
+        max_line_length:
+          level: "ignore"
+        no_backticks:
+          level: "ignore"
+
       if errors.length > 0
         text = "#red[#{errors.length} errors in #underline[#{filePath}]]\n\n"
         for error in errors


Remove duplicate scrollPastEnd setter and getter

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -3355,20 +3355,6 @@
     scrollEvent = {screenRange, options}
     @emitter.emit "did-request-autoscroll", scrollEvent

-  getScrollPastEnd: ->
-    if @scrollPastEnd?
-      @scrollPastEnd
-    else
-      @config.get('editor.scrollPastEnd', scope: @getRootScopeDescriptor())
-
-  setScrollPastEnd: (scrollPastEnd) ->
-    if scrollPastEnd isnt @scrollPastEnd
-      @scrollPastEnd = scrollPastEnd
-      @emitter.emit('did-change-scroll-past-end')
-
-  onDidChangeScrollPastEnd: (callback) ->
-    @emitter.on('did-change-scroll-past-end', callback)
-
   getHorizontalScrollbarHeight: ->
     Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")


Serialize pending state for editor

Serialize pending state for editor

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -150,6 +150,7 @@
     firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
     displayBuffer: @displayBuffer.serialize()
     selectionsMarkerLayerId: @selectionsMarkerLayer.id
+    pending: @isPending()

   subscribeToBuffer: ->
     @buffer.retain()


Fix minimal trend

Fix minimal trend

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -66,7 +66,7 @@
                 project.chart = plot.createChartObject()
                 project.prettyTrend = _.sprintf '%.2f', project.trend if not _.isUndefined(project.trend)
                 project.trendClass = if project.trend >= 0.01 then 'success' else
-                    if project.trend <= -0.99 then 'failed' else 'neutral'
+                    if project.trend <= -0.01 then 'failed' else 'neutral'
                 if project.last_task and project.last_task.status == 2
                     failedTasks += 1
             favicoService.badge(failedTasks)


propagate "booted" event up to the fuckers

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -256,6 +256,7 @@
             assert @consumeProviders; @consumeProviders @socket
             open = "successfully bootloaded at #{@location}"
             @on "booted", -> @booted = yes; @duplexed = yes
+            @on "booted", -> $root.emit "attached", this
             confirm = => logger.info open; @emit "booted"
             @trampoline _.omit(@, foreign), confirm


Add two experimental hooks to the compose

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -103,6 +103,7 @@
         throw new Error orphans if _.isEmpty commons
         differentiated = _.take current, culrpit
         alternative = _.map differentiated, shader
+        compound.composition? this, current, foreign
         return @rebased compound if _.isEmpty alternative
         tails = alternative.pop().rebased compound
         rebased = (acc, cls) -> cls.rebased acc; cls
@@ -138,6 +139,7 @@
         isClass = _.isObject baseclass?.__super__
         noClass = "The #{baseclass} is not a class"
         throw new Error noClass unless isClass
+        baseclass.rebasement? this, force
         p = (k) => force is yes or not this[k]?
         this[k] = v for own k, v of baseclass when p(k)
         original = this.prototype or {}; r = this


Fix the assertion in the scoping

Fix the assertion in the scoping

--- library/nucleus/scoping.coffee
+++ library/nucleus/scoping.coffee
@@ -106,7 +106,7 @@
         logger.info "Assuming the #{fpath.underline} config".cyan
         exists = fs.existsSync fpath; nconf.file fpath if exists
         for directory in nconf.get("env:dirs") or []
-            assert not _.isEmpty mode = nconf.get("env:mode")
+            assert _.isNumber mode = nconf.get "env:mode"
             msg = "Environment mkdir at %s with 0%s mode".yellow
             logger.info msg, directory.underline, mode.toString 8
             mkdirSyncRecursive directory, mode


Fix naming mistakes

Fix naming mistakes

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -59,12 +59,12 @@
     # Supports implicit extraction of captured groups in the match.
     # Use this to configure what resources should match with service.
     @resource: (pattern) ->
-        inspected = @nick or @name
+        identify = @nick or @name
         duplicate = pattern in (@resources or [])
         regexify = (s) -> new RegExp "^#{RegExp.escape(s)}$"
         pattern = regexify(pattern)  if _.isString(pattern)
         inspected = pattern.unescape()?.underline or pattern.source
-        associate = "Associating #{inspected} resource with #{inspected}"
+        associate = "Associating #{inspected} resource with #{identify}"
         notRegexp = "The #{inspected} is not a valid regular expression"
         throw new Error(notRegexp) unless _.isRegExp(pattern)
         (@resources ?= []).push pattern unless duplicate
@@ -76,12 +76,12 @@
     # Supports implicit extraction of captured groups in the match.
     # Use this to configure what domains should match with service.
     @domain: (pattern) ->
-        inspected = @nick or @name
+        identify = @nick or @name
         duplicate = pattern in (@domains or [])
         regexify = (s) -> new RegExp(RegExp.escape(s))
         pattern = regexify(pattern)  if _.isString(pattern)
         inspected = pattern.unescape()?.underline or pattern.source
-        associate = "Associating #{inspected} domain with #{inspected}"
+        associate = "Associating #{inspected} domain with #{identify}"
         notRegexp = "The #{inspected} is not a valid regular expression"
         throw new Error(notRegexp) unless _.isRegExp(pattern)
         (@domains ?= []).push pattern unless duplicate


Pass srv instance explicitly

Pass srv instance explicitly

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -72,7 +72,7 @@
     # information on the parameters it accepts. Beware, this hook
     # is asynchronously wired in, so consult with `async` package.
     # Please be sure invoke the `next` arg to proceed, if relevant.
-    instance: (kernel, next) -> next()
+    instance: (kernel, service, next) -> next()

     # A hook that will be called prior to registering the service
     # implementation. Please refer to this prototype signature for
@@ -100,7 +100,7 @@
         upstream = upstream.bind service
         instance = upstream "instance", ->
             callback? service, kernel
-        instance kernel; service
+        instance kernel, service; service

     # Either obtain or set the HTTP location of the current service.
     # If not location has been set, but the one is requested then


update atom font

update atom font

--- home/atom/config.cson
+++ home/atom/config.cson
@@ -7,7 +7,6 @@
     restorePreviousWindowsOnStart: "no"
     telemetryConsent: "no"
   editor:
-    fontFamily: "Adobe Source Code Pro"
     fontSize: 13
     showInvisibles: false
     softWrap: true


added the 'template optimizations' config

pertains to #3

--- component.coffee
+++ component.coffee
@@ -1,6 +1,6 @@
 fs = require "fs"
 {join, basename, resolve, normalize, dirname} = require "path"
-exists = fs.existsSync or require('path').existsSync
+exists = fs.existsSync or require("path").existsSync
 crypto = require "crypto"
 extend = require "node.extend"
 _ = require "underscore"
@@ -19,6 +19,7 @@
 write = (fname, content, encoding="utf8") ->
   fs.writeFileSync(fname, content, encoding)
 pathSep = normalize("/")
+re = (s) -> new RegExp s
 argv = usage = null

 pkgJson = (dir = ".") ->
@@ -72,7 +73,7 @@
       log "link-installing #{name} to #{tgtDir}"
       orig = resolve process.cwd()
       process.chdir tgtDir
-      # there's a chance that 'csi' is a dead link (so exists() returns false,
+      # there's a chance that "csi" is a dead link (so exists() returns false,
       # but there's actually a link in the directory).  remove that here.
       try
         fs.unlinkSync name
@@ -81,6 +82,9 @@
     else
       log "installing #{name} to #{tgtDir}"
       wrench.copyDirSyncRecursive src, join(tgtDir, name)
+
+hasBuildProfile = (filename = "app.build.js", dir = ".") ->
+  exists join(dir, "app.build.js")

 appBuildProfile = (filename = "app.build.js", dir = ".") ->
   eval read(join(dir, filename))
@@ -195,17 +199,35 @@
   base = json.component?.testDirectory or (exists("static") and ".") or ".test"
   join base, "static"

+withOutStaticPath = (path) ->
+  path.replace re('^' + argv.staticpath), ''
+
 listTests = (tests, host, port) ->
   for test in tests
     console.log("http://#{host}:#{port}/#{test.replace(/\.js$/, "")}")

 templateCommands =
   requirejs: (config) ->
-    join config.baseUrl || '', config.paths.csi, "require.js"
+    join config.baseUrl || "", config.paths.csi, "require.js"
   extra: () ->
     stringBundlesAsRequirejsModule()
   config: (config) ->
     JSON.stringify config, null, "    "
+  optimizations: (config) ->
+    return {} if not hasBuildProfile(argv.buildprofile)
+    ext = (fname, newExt) -> fname.match(/\.(css|js)$/)[1]
+    names = (m.name for m in (appBuildProfile().modules or []))
+    names.reduce (optimizations, module) ->
+      filesInDir = fs.readdirSync dirname(join(argv.staticpath, module))
+      fileNamesRe = re "^" + module + "-[a-f0-9]{32}(\.min)?\.(js|css)"
+      for filename in filesInDir
+        if fileNamesRe.test(filename)
+          optimizations[module] ||= {}
+          subKey = if /\.min\./.test(filename) then "minified" else "optimized"
+          (optimizations[module][subKey] ||= {})[ext(filename)] = filename
+      optimizations
+    , {}
+  baseurl: (config) -> argv.baseurl
   all: (config) ->
     _.reduce(templateCommands, ((templateObj, cmd, name) ->
       return templateObj if name is "all"
@@ -241,7 +263,7 @@
         """
     action: () ->
       log "building client side code docs"
-      docco_lib = require.resolve('docco-husky')
+      docco_lib = require.resolve("docco-husky")
       docco = docco_lib.substring(0, docco_lib.lastIndexOf "node_modules") +
               "node_modules/.bin/docco-husky"
       child_process.execFile(docco, [sourceDirectory()],
@@ -276,8 +298,8 @@
   template:
     description: """
     output html snippets for things like the require.js path, can be any of:
-    #{(' - ' + cmd + '\n' for cmd of templateCommands).join("")}
-    """.replace(/\n$/, '')
+    #{(" - " + cmd + "\n" for cmd of templateCommands).join("")}
+    """.replace(/\n$/, "")
     action: (cmd, args...) ->
       console.log templateCommands[cmd](getConfig())

@@ -302,20 +324,13 @@

       commands.install.action()

-      if argv.templatepath
-        templateObj = templateCommands.all config
-        provide argv.templatepath
-        contextjsonname = join(argv.templatepath, argv.contextjsonname)
-        log "writing context json to #{contextjsonname}"
-        write contextjsonname, JSON.stringify(templateObj)
-
       if argv.optimize

-        # returns the on-disk filename for a css dep like 'css!100:foo.css'
+        # returns the on-disk filename for a css dep like "css!100:foo.css"
         cssFilename = (moduleName) ->
           _.compose.apply(_, [
             # remove plugin prefix css!100:
-            (n) -> n.replace /^css!(\d+:)?/, ''
+            (n) -> n.replace /^css!(\d+:)?/, ""

             # check the paths config, replace the path if it's in there
             (n) ->
@@ -331,7 +346,7 @@
             (n) -> join argv.staticpath, n
           ].reverse())(moduleName)

-        # returns the order for a css dep like 'css!100:foo.css'
+        # returns the order for a css dep like "css!100:foo.css"
         cssOrder = (moduleName) ->
           +(moduleName.match(/^css!((\d+):)?/)[2] or "0")

@@ -344,9 +359,12 @@
         # re-written appropriately
         cssWithPathsReWritten = (fname) ->
           updateCssPaths read(fname), (u) ->
-            if u[0] isnt "/" then join(argv.baseurl, dirname(fname), u) else u
-
-        # go through all the components build configs and create a 'paths'
+            if u[0] isnt "/" and u[0..4] isnt "data:"
+              join(argv.baseurl, dirname(withOutStaticPath(fname)), u)
+            else
+              u
+
+        # go through all the components build configs and create a "paths"
         # config for the require.js optimizer that includes empty modules for
         # all excluded files
         pathsConfig = () ->
@@ -362,8 +380,8 @@
           cssText = if opts.minify then cssmin(opts.css) else opts.css
           hash = crypto.createHash("md5").update(text + cssText).digest("hex")
           id = "#{module.name}-#{hash}#{if opts.minify then ".min" else ""}"
-          rId = new RegExp('(define\\([\'"])' + module.name)
-          text = text.replace rId, '$1' + id
+          rId = re('(define\\([\'"])' + module.name)
+          text = text.replace rId, "$1" + id

           name = join argv.staticpath, "#{id}.js"
           write name, text
@@ -401,6 +419,16 @@
               if argv.minify
                 output text, minify: true, module: module, css: cssText

+      # this must be after the optimization step, since it checks for optimized
+      # files and includes them in the config
+      if argv.templatepath
+        templateObj = templateCommands.all config
+        provide argv.templatepath
+        contextjsonname = join(argv.templatepath, argv.contextjsonname)
+        log "writing context json to #{contextjsonname}"
+        write contextjsonname, JSON.stringify(templateObj)
+
+
   completion:
     description: """
     spits out a bash completion command.  something you can run like this:
@@ -431,7 +459,7 @@
 log = (msg, level="info") ->
   console.log "[#{basename process.argv[1]} #{argv._[0]}] #{msg}"

-usage = """#{("node $0 "+cmd+'\n' for cmd of commands).join("")}
+usage = """#{("node $0 "+cmd+"\n" for cmd of commands).join("")}
 `component` is a utility that's used for installing javascript components and
 their dependencies -- imagine that!

@@ -441,7 +469,7 @@
 for name, command of commands
   usage += "
   #{name}:\n
-    #{command.description.replace(/\n/g, '\n    ')}\n"
+    #{command.description.replace(/\n/g, "\n    ")}\n"

 exports.parseArgs = parseArgs = () ->
   argv = require("optimist")
@@ -484,8 +512,8 @@
       describe: """
       specify the installation path.  the default for this value is dynamically determined:
        - if there is a package.json file with a component.testDirectory property, staticpath is set to that
-       - otherwise if the './static' directory exits, staticpath is set to 'static'
-       - finally if nothing else component will create a '.test' directory and use it as the staticpath
+       - otherwise if the "./static" directory exits, staticpath is set to "static"
+       - finally if nothing else component will create a ".test" directory and use it as the staticpath
        (cmd: build)
        """

@@ -523,8 +551,8 @@
     .argv

   specified = (argName, shorthand) ->
-    argv[argName+'Specified'] = _.any process.argv, (arg) ->
-      RegExp('^-{1,2}' + argName).test(arg) or (arg is "-" + shorthand)
+    argv[argName+"Specified"] = _.any process.argv, (arg) ->
+      RegExp("^-{1,2}" + argName).test(arg) or (arg is "-" + shorthand)

   specified("templatepath", "t")
   specified("staticpath", "s")


FSItem: fix move/rename issues [completes #77048848]

--- client/finder/fs/fsitem.coffee
+++ client/finder/fs/fsitem.coffee
@@ -224,16 +224,9 @@
     .then =>
       @emit "fs.delete.finished"

-  # Rename file with provided new name, its using ::move
-  rename: (newName, callback)->
-
-    @move "#{FSHelper.getParentPath @getPath()}/#{newName}", callback
-
-  move: (newPath, callback)->
-
-    @emit "fs.job.started"
-
-    newPath = FSHelper.plainPath newPath
+  moveOrRename: (toPath, callback)->
+
+    @emit "fs.job.started"

     kite = @getKite()

@@ -243,12 +236,22 @@

       kite.fsMove
         oldpath : @getPath()
-        newpath : newPath
-
-    .nodeify(callback)
-
-    .then =>
-      @emit "fs.job.finished"
+        newpath : toPath
+
+    .nodeify(callback)
+
+    .then =>
+      @emit "fs.job.finished"
+
+
+  rename: (newName, callback)->
+
+    @moveOrRename "#{FSHelper.getParentPath @getPath()}/#{newName}", callback
+
+  move: (newPath, callback)->
+
+    @moveOrRename "#{FSHelper.plainPath newPath}/#{@name}", callback
+

   chmod:(options, callback)->


Displaying custom default labels for specific question types

e.g. for images, "Point and shoot! Take a photo"...

--- source/javascripts/src/model_xlform.coffee
+++ source/javascripts/src/model_xlform.coffee
@@ -58,6 +58,7 @@
     if (sname = @settings.get("name"))
       @set("name", sname)
     @newRowDetails = options.newRowDetails || XLF.newRowDetails
+    @defaultsForType = options.defaultsForType || XLF.defaultsForType
     @surveyDetails = new XLF.SurveyDetails(_.values(XLF.defaultSurveyDetails))
     passedChoices = options.choices || []
     @choices = do ->
@@ -191,12 +192,21 @@
           needs to keep track of how the value was built
     ###
     if @_parent
-      defaultsUnlessDefined = @_parent.newRowDetails
+      defaultsUnlessDefined = @_parent.newRowDetails || XLF.newRowDetails
+      defaultsForType = @_parent.defaultsForType || XLF.defaultsForType
     else
       console?.error "Row not linked to parent survey."
       defaultsUnlessDefined = XLF.newRowDetails
-
-    for key, vals of defaultsUnlessDefined
+      defaultsForType = XLF.defaultsForType
+
+    if @attributes.type and @attributes.type of defaultsForType
+      curTypeDefaults = defaultsForType[@attributes.type]
+    else
+      curTypeDefaults = {}
+
+    defaults = _.extend {}, defaultsUnlessDefined, curTypeDefaults
+
+    for key, vals of defaults
       unless key of @attributes
         newVals = {}
         for vk, vv of vals
@@ -505,3 +515,22 @@
   required:
     value: false
     _hideUnlessChanged: true
+
+XLF.defaultsForType =
+  geopoint:
+    label:
+      value: "Record your current location"
+  image:
+    label:
+      value: "Point and shoot! Take a photo with the phone's camera."
+  integer:
+    label:
+      value: "Enter a number"
+      _promptToChangeDefault: true
+  date:
+    label:
+      value: "Enter a date"
+  datetime:
+    label:
+      value: "Enter a date and time"
+


Refactor _setupInitializingMessage to remove duplication

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -52,27 +52,24 @@

   _setupInitializingMessage: ->
     if @initializingMessage.isSubclassOf(Space.domain.Event)
-      @eventBus.subscribeTo @initializingMessage, (event) =>
-        @log.debug("#{this}: Creating new #{@eventSourceable} with event
-                  #{event.typeName()}\n", event)
-        eventSourceable = @_handleDomainErrors(->
-          instance = new @eventSourceable(event.sourceId)
-          @injector.injectInto(instance)
-          instance.handle(event)
-          return instance
-        )
-        @repository.save(eventSourceable) if eventSourceable?
+      messageBus = @eventBus
+      handlerSubscriber = @eventBus.subscribeTo
+      idProperty = 'sourceId'
     else if @initializingMessage.isSubclassOf(Space.domain.Command)
-      @commandBus.registerHandler @initializingMessage, (cmd) =>
-        @log.debug("#{this}: Creating new #{@eventSourceable} with command
-                  #{cmd.typeName()}\n", cmd)
-        eventSourceable = @_handleDomainErrors(->
-          instance = new @eventSourceable(cmd.targetId)
-          @injector.injectInto(instance)
-          instance.handle(cmd)
-          return instance
-        )
-        @repository.save(eventSourceable) if eventSourceable?
+      messageBus = @commandBus
+      handlerSubscriber = @commandBus.registerHandler
+      idProperty = 'targetId'
+    handlerSubscriber.call(messageBus, @initializingMessage, (message) =>
+      @log.debug("#{this}: Creating new #{@eventSourceable} with message
+                  #{message.typeName()}\n", message)
+      eventSourceable = @_handleDomainErrors(->
+        instance = new @eventSourceable(message[idProperty])
+        @injector.injectInto(instance)
+        instance.handle(message)
+        return instance
+      )
+      @repository.save(eventSourceable) if eventSourceable?
+    )

   _routeEventToEventSourceable: (eventType) ->
     @eventBus.subscribeTo eventType, @_genericEventHandler


Fix bug in router from typo correction

Fix bug in router from typo correction

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -46,8 +46,8 @@
   onDependenciesReady: ->
     super
     @_setupInitializingMessage()
-    @_routeEventToEventSourcable(eventType) for eventType in @routeEvents
-    @_routeCommandToEventSourcable(commandType) for commandType in @routeCommands
+    @_routeEventToEventSourceable(eventType) for eventType in @routeEvents
+    @_routeCommandToEventSourceable(commandType) for commandType in @routeCommands

   _setupInitializingMessage: ->
     if @initializingMessage.isSubclassOf(Space.messaging.Event)
@@ -61,7 +61,7 @@
                   #{cmd.typeName()}\n", cmd)
         @_handleDomainErrors -> @repository.save new @eventSourceable(cmd)

-  _routeEventToEventSourcable: (eventType) ->
+  _routeEventToEventSourceable: (eventType) ->
     @eventBus.subscribeTo eventType, @_genericEventHandler

   _routeCommandToEventSourceable: (commandType) ->