View params in path.

View params in path.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -87,7 +87,7 @@
                 $(".report_"+window.API.current_state.report+"_title").addClass("focused_report")

                 @children.each (report_title) ->
-                        report_title.ui.link.attr("href",window.API.compose(report: report_title.model.id, date: null, version: null)) if report_title.ui?
+                        report_title.ui.link.attr("href",window.API.compose(report: report_title.model.id, date: null, version: null, view: null)) if report_title.ui?

         onShow: () ->
                 @updateView()
@@ -278,7 +278,7 @@
 #                console.log("switching report",window.listController.reports)
                 current_index = window.listController.reports.models.indexOf(window.listController.reports.get(@api.current_state.report or 1))
                 new_index = (current_index + 1) % window.listController.reports.models.length
-                @api.navigate(report: window.listController.reports.models[new_index].id.toString())
+                @api.navigate(report: window.listController.reports.models[new_index].id.toString(), view: null)

         navigate: (old_state,new_state,changed) ->
                 if _.intersection(changed, ["demo"]).length > 0
@@ -411,7 +411,8 @@
                 list_hidden: false
                 demo: false
                 date: undefined
-                version: undefined }
+                version: undefined
+                view: undefined }

         constructor: () ->
@@ -433,6 +434,8 @@
                         ret += "/date/"+data.date
                 if data.version? and data.version
                         ret += "/version/"+data.version
+                if data.view? and data.view
+                        ret += "/view/"+data.view
                 if /[^\/]*:\/\/[^\/\#]*\#/.test(window.location)
                         ret.replace(/^\//,"#")
                 ret
@@ -446,7 +449,8 @@
                         list_hidden: false
                         demo: false
                         date: null
-                        version: null }
+                        version: null
+                        view: null }
                 i = 0
                 while i+1 < split.length
                         key = split[i]
@@ -458,6 +462,7 @@
                                 when "demo" then new_state.demo = value
                                 when "date" then new_state.date = value
                                 when "version" then new_state.version = value
+                                when "view" then new_state.view = value
                                 else
                                         console.log("stupid key in route: ",key)
                         i += 2


bugfix

bugfix

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -25,10 +25,11 @@
       failures: []

       processDrop: () ->
+        @multiple = @multiple || @getQueuedFiles().length > 1
         @startTime          = new Date
         $scope.files_count  = @files.length
         $scope.dragged_over = false
-        $scope.state        = 'processing'
+        $scope.$apply -> $scope.state = 'processing'

         if @multiple
           @uploadMultipleFiles()
@@ -63,7 +64,7 @@
           $scope.elapsed    = ((new Date) - @startTime)/1000.0
           $scope.view_url   = "#{location.origin}/shares/#{@key}.html"
           $scope.count_down = 5
-          @countDown()
+          #@countDown()

       allSuccessful: () ->
         _.isEmpty( @getQueuedFiles() ) &&
@@ -109,7 +110,6 @@
         formData.append "path", file.fullPath || file.name

       @on "drop", (evt) ->
-        @multiple = !@isSingleFileDropped(evt.items)

       @on "queuecomplete", (evt) ->


Use Link from este.react.Element.

Use Link from este.react.Element.

--- app/client/js/react/pages/mysongs.coffee
+++ app/client/js/react/pages/mysongs.coffee
@@ -3,13 +3,14 @@
 class app.react.pages.MySongs

   ###*
+    TODO: Rename to homepage, this is confusing.
     @param {app.Routes} routes
     @param {app.users.Store} usersStore
     @param {este.react.Element} element
     @constructor
   ###
   constructor: (routes, usersStore, element) ->
-    {div, ul, li, p, nav, a} = element
+    {div, ul, li, p, nav, Link} = element

     @component = React.createFactory React.createClass

@@ -23,36 +24,29 @@
           if songs.length
             ul className: 'songs', visibleSongs.map (song) ->
               li key: song.id,
-                a
-                  href: routes.mySong.url song
+                Link
+                  route: routes.mySong
+                  params: song
                   touchAction: 'pan-y'
                 , "#{song.getDisplayName()} [#{song.getDisplayArtist()}]"
           else
             div {},
               p {}, MySongs.MSG_NO_SONGS
               p {},
-                a
-                  href: routes.newSong.url()
-                  touchAction: 'none'
-                , MySongs.MSG_ADD
+                Link route: routes.newSong, MySongs.MSG_ADD
                 MySongs.MSG_OR
-                a
-                  href: routes.songs.url()
-                  touchAction: 'none'
-                , MySongs.MSG_SEARCH
+                Link route: routes.songs, MySongs.MSG_SEARCH
                 MySongs.MSG_ONE
           nav {},
             if songs.length > 0
-              a
+              Link
                 className: 'btn btn-link'
-                href: routes.newSong.url()
-                touchAction: 'none'
+                route: routes.newSong
               , MySongs.MSG_ADD_NEW_SONG
             if deletedSongs.length > 0
-              a
+              Link
                 className: 'btn btn-link'
-                href: routes.trash.url()
-                touchAction: 'none'
+                route: routes.trash
               , "Trash (#{deletedSongs.length})"

   @MSG_ADD: goog.getMsg 'Add'


Use _.toArray to convert NodeList to Array

--- src/helpers/mathjax.coffee
+++ src/helpers/mathjax.coffee
@@ -37,11 +37,8 @@
     # mark node as processed
     node.classList.add('math-rendered')

-  # convert NodeList to Array
-  node_array = Array.prototype.slice.call(nodes)
-
   # submit all nodes at once for optimal rendering performance
-  window.MathJax.Hub.Queue(['Typeset', MathJax.Hub, node_array])
+  window.MathJax.Hub.Queue(['Typeset', MathJax.Hub, _.toArray(nodes)])

   # render MathML with MathJax
   window.MathJax.Hub.Queue(['Typeset', MathJax.Hub, root]) if hasMath


Work on RequireJS Jasmine test.

Work on RequireJS Jasmine test.

--- lms/static/coffee/spec/requirejs_spec.coffee
+++ lms/static/coffee/spec/requirejs_spec.coffee
@@ -1,68 +1,51 @@
-describe('RequireJS namespacing', function() {
-  beforeEach(function() {
-    this.addMatchers({
-      requirejsTobeUndefined: function() {
-        return (typeof requirejs === 'undefined');
-      },
-      requireTobeUndefined: function() {
-        return (typeof require === 'undefined');
-      },
-      defineTobeUndefined: function() {
-        return (typeof define === 'undefined');
-      },
-    });
-  });
+describe "RequireJS namespacing", ->
+  beforeEach ->
+    @addMatchers
+      requirejsTobeUndefined: ->
+        typeof requirejs is "undefined"

-    it('check that the RequireJS object is present in the global namespace', function() {
-        expect(RequireJS).toEqual(jasmine.any(Object));
-        expect(window.RequireJS).toEqual(jasmine.any(Object));
-    });
+      requireTobeUndefined: ->
+        typeof require is "undefined"

-    it('check that requirejs(), require(), and define() are not in the global namespace', function () {
-        expect({}).requirejsTobeUndefined();
-        expect({}).requireTobeUndefined();
-        expect({}).defineTobeUndefined();
+      defineTobeUndefined: ->
+        typeof define is "undefined"

-        expect(window.requirejs).not.toBeDefined();
-        expect(window.require).not.toBeDefined();
-        expect(window.define).not.toBeDefined();
-    });
-});

-describe('RequireJS module creation', function() {
-    var inDefineCallback, inRequireCallback;
+  it "check that the RequireJS object is present in the global namespace", ->
+    expect(RequireJS).toEqual jasmine.any(Object)
+    expect(window.RequireJS).toEqual jasmine.any(Object)

-    it('check that we can use RequireJS define() and require() a module', function() {
-        runs(function () {
-            inDefineCallback = false;
-            inRequireCallback = false;
+  it "check that requirejs(), require(), and define() are not in the global namespace", ->
+    expect({}).requirejsTobeUndefined()
+    expect({}).requireTobeUndefined()
+    expect({}).defineTobeUndefined()
+    expect(window.requirejs).not.toBeDefined()
+    expect(window.require).not.toBeDefined()
+    expect(window.define).not.toBeDefined()

-            RequireJS.define('test_module', [], function () {
-                inDefineCallback = true;

-                return {
-                    'module_status': 'OK'
-                };
-            });
+describe "RequireJS module creation", ->
+  inDefineCallback = undefined
+  inRequireCallback = undefined
+  it "check that we can use RequireJS define() and require() a module", ->
+    runs ->
+      inDefineCallback = false
+      inRequireCallback = false
+      RequireJS.define "test_module", [], ->
+        inDefineCallback = true
+        module_status: "OK"

-            RequireJS.require(['test_module'], function (test_module) {
-                inRequireCallback = true;
+      RequireJS.require ["test_module"], (test_module) ->
+        inRequireCallback = true
+        expect(test_module.module_status).toBe "OK"

-                expect(test_module.module_status).toBe('OK');
-            });
-        });

-        waitsFor(function () {
-            if ((inDefineCallback !== true) || (inRequireCallback !== true)) {
-                return false;
-            }
+    waitsFor (->
+      return false  if (inDefineCallback isnt true) or (inRequireCallback isnt true)
+      true
+    ), "We should eventually end up in the defined callback", 1000
+    runs ->
+      expect(inDefineCallback).toBeTruthy()
+      expect(inRequireCallback).toBeTruthy()

-            return true
-        }, 'We should eventually end up in the defined callback', 1000);

-        runs(function () {
-            expect(inDefineCallback).toBeTruthy();
-            expect(inRequireCallback).toBeTruthy();
-        });
-    });
-});


Set form ID

Set form ID

--- upload.coffee
+++ upload.coffee
@@ -12,6 +12,7 @@

   constructor: ->
     super
+    @el.attr id: 'form'

     @listenTo User, 'change', (e, user) =>
       @el.html @template @


Add needed fs import

Add needed fs import

--- server/server.coffee
+++ server/server.coffee
@@ -11,6 +11,7 @@
 path = require 'path'
 useragent = require 'express-useragent'
 http = require 'http'
+fs = require 'fs'

 config = require './server-config'


setProperty and resetProperty should be a noop for keys which are not properties - they should be ignored

--- src/art/engine/core/element_base.coffee
+++ src/art/engine/core/element_base.coffee
@@ -439,7 +439,7 @@
       metaProperties = @metaProperties
       for property, mp of metaProperties when !mp.virtual
         externalName = mp.externalName
-        @[mp.setterName] if propertySet.hasOwnProperty(externalName)
+        @[mp.setterName]? if propertySet.hasOwnProperty(externalName)
           propertySet[externalName]
         else
           mp.defaultValue
@@ -447,12 +447,12 @@

     setProperty: (property, value) ->
       if mp = @metaProperties[property]
-        @[mp.setterName] value
+        @[mp.setterName]? value

     # reset property to its default
     resetProperty: (property) ->
       if mp = @metaProperties[property]
-        @[mp.setterName] mp.defaultValue
+        @[mp.setterName]? mp.defaultValue

     # used by Animator / Foundation.Transaction to normalize to/from values
     preprocessProperties: (propertySet) ->


Load all properties for gen. man. from JSON

--- src/game.coffee
+++ src/game.coffee
@@ -155,8 +155,7 @@
 				@quit()

 	loadFromJSON: (json) ->
-		@generationManager.connections =
-			json.generationManager.connections
+		@generationManager[k] = v for k,v of json.generationManager

 		@logs = json.logs


Make the game easier to cheat :)

Make the game easier to cheat :)

--- src/game.coffee
+++ src/game.coffee
@@ -38,8 +38,7 @@
         else
           game.flags++
           newTileClass = Flag
-        game.board.cases[tilePosition].g.remove()
-        game.board.cases[tilePosition] = new newTileClass @ctx, @x, @y
+        game.setFlag @ctx, @x, @y
         game.board.flagsLbl.attr text: "Flags: #{game.flags}/#{game.nbMines}"
       else
         @showTile()
@@ -130,6 +129,12 @@

   rnd: (min, max) ->
     return Math.floor(Math.random() * (max - min + 1)) + min
+
+
+  setFlag: (ctx, x, y) ->
+    tilePosition = @positionFromCoord x, y
+    @board.cases[tilePosition].g.remove()
+    @board.cases[tilePosition] = new Flag ctx, x, y

   neighborCoord: (x, y) ->


Remove dependency on request, not using it currently

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -56,8 +56,8 @@
 SSO_CLIENT_ID = client.sso_client_id
 CREST_HOST = CONFIG[env].crest_host

+
 React = require 'react'
-request = require 'request'
 $ = require 'jquery'
 d3 = require 'd3'
 _ = require 'lodash'


Navbar, spacing tweaks, changing a few libs around

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -53,12 +53,10 @@
 $ = require 'jquery'
 d3 = require 'd3'
 _ = require 'lodash'
+numeral = require 'numeral'
+
 qs = require 'querystring'
 urlParse = require('url').parse
-
-# polyfill for safari
-if not Intl
-  Intl = require 'intl'

 CharacterStats = require './character_stats'
 CharacterFacts = require './character_facts'
@@ -359,7 +357,10 @@
   return out

 numFmt = (number) ->
-  return Intl.NumberFormat().format(number)
+  if number
+    return numeral(number).format('0,0')
+  else
+    return null

 StatsUI = React.createClass(
   displayName: 'CharacterStatsUI'
@@ -405,7 +406,7 @@
           jrequest data.character.href, (err, data, xhr) =>
             char = @state.character
             char.name = data.name
-            @setState {character: char}
+            @setState {character: char, corp: char.corporation}
           @loadStatsYear(2014)

   loadStatsYear: (year) ->
@@ -431,6 +432,11 @@
         character: {
           name: 'Bellatroix'
           id: 1412571394
+        }
+        corp: {
+          name: 'Folkvangr Acres'
+          logo:
+            '32x32': ''
         }
       }

@@ -456,7 +462,18 @@
       else
         facts = null

-      title = React.createElement(Title, {character: @state.character, year: @state.year, switchToYear: @loadStatsYear, hideSwitch: @state.sample})
+      title = React.createElement(Title, {
+        character: @state.character,
+        year: @state.year,
+        switchToYear: @loadStatsYear,
+        hideSwitch: @state.sample
+      })
+      header = React.createElement(Header, {
+        character: @state.character,
+        year: @state.year,
+        switchToYear: @loadStatsYear,
+        hideSwitch: @state.sample
+      })

       if @state.noData
         return dom.div {className: 'container translucent'},
@@ -469,7 +486,12 @@

           dom.div null, "No data for #{@state.year}"

-      charInfoPanel = React.createElement(CharacterInfoPanel, {character: @state.character, facts: facts, stats: @state.stats})
+      charInfoPanel = React.createElement(CharacterInfoPanel, {
+        character: @state.character
+        corp: @state.corp
+        facts: facts
+        stats: @state.stats
+      })

       travelJumpsPanel = React.createElement(TravelJumpsPanel, {stats: @state.stats})
       travelDistancePanel = React.createElement(TravelDistancePanel, {stats: @state.stats})
@@ -535,90 +557,89 @@

       rawStatsList = React.createElement(RawStatsList, {stats: @state.stats})

-      dom.div {className: 'container translucent'},
-
-        React.createElement(SSOLoginButton, {linkText: 'Switch Character/Account'})
-
-        title
-
-        charInfoPanel
-
-        # Residence / Distance
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, travelJumpsPanel
-          dom.div {className: 'col-md-6'}, travelDistancePanel
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, distanceAnalogy
-
-        # PVP
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, killsPanel
-          dom.div {className: 'col-md-6'}, deathsPanel
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, weaponUsagePanel
-          dom.div {className: 'col-md-6'}, damageTakenPanel
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, damageAnalogy
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-3'}, pvpModulesUsage
-          dom.div {className: 'col-md-3'}, pvpModulesAgainst
-          dom.div {className: 'col-md-3'}, miscPvpStats
-          dom.div {className: 'col-md-3'}, miscTooPvpStats
-
-        # TODO: without spacer columns, things overlap for some reason.  fix that!
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-3'}, repsGivenPanel
-          dom.div {className: 'col-md-1'}, ''
-          dom.div {className: 'col-md-3'}, repsReceivedPanel
-          dom.div {className: 'col-md-1'}, ''
-          dom.div {className: 'col-md-3'}, selfRepPanel
-
-        # PVE Stats
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, miscModules
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, pveStats
-
-        # ISK / Markets
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, totalIskPanel
-          dom.div {className: 'col-md-6'}, marketIskPanel
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, marketPanel
-
-        # Industry
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, miningPanel
-          dom.div {className: 'col-md-6'}, industryJobs
-
-        # Mining
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, blueprints
-
-
-        # Social
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-6'}, contactsSelfPanel
-          dom.div {className: 'col-md-6'}, contactsOtherPanel
-
-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-12'}, socialMiscPanel
-
-        dom.div {className: 'row'}
-          dom.div {className: 'col-md-12'}, miscStats
-
-        # Misc
-
-        #rawStatsList
+      dom.div null,
+        # header
+        header
+
+        # main content
+        dom.div {className: 'container translucent'},
+
+          header
+
+          charInfoPanel
+
+          # Residence / Distance
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, travelJumpsPanel
+            dom.div {className: 'col-md-6'}, travelDistancePanel
+
+          dom.div {className: 'row'},
+            dom.div {className: 'col-md-12'}, distanceAnalogy
+
+          # PVP
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, killsPanel
+            dom.div {className: 'col-md-6'}, deathsPanel
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, weaponUsagePanel
+            dom.div {className: 'col-md-6'}, damageTakenPanel
+
+          dom.div {className: 'row'},
+            dom.div {className: 'col-md-12'}, damageAnalogy
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-3'}, pvpModulesUsage
+            dom.div {className: 'col-md-3'}, pvpModulesAgainst
+            dom.div {className: 'col-md-3'}, miscPvpStats
+            dom.div {className: 'col-md-3'}, miscTooPvpStats
+
+          # TODO: without spacer columns, things overlap for some reason.  fix that!
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-3'}, repsGivenPanel
+            dom.div {className: 'col-md-1'}, ''
+            dom.div {className: 'col-md-3'}, repsReceivedPanel
+            dom.div {className: 'col-md-1'}, ''
+            dom.div {className: 'col-md-3'}, selfRepPanel
+
+          # PVE Stats
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-12'}, miscModules
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-12'}, pveStats
+
+          # ISK / Markets
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, totalIskPanel
+            dom.div {className: 'col-md-6'}, marketIskPanel
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-12'}, marketPanel
+
+          # Industry
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, miningPanel
+            dom.div {className: 'col-md-6'}, industryJobs
+
+          # Mining
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-12'}, blueprints
+
+
+          # Social
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-6'}, contactsSelfPanel
+            dom.div {className: 'col-md-6'}, contactsOtherPanel
+
+          dom.div {className: 'row top-buffer'},
+            dom.div {className: 'col-md-12'}, socialMiscPanel
+
+          dom.div {className: 'row top-buffer'}
+            dom.div {className: 'col-md-12'}, miscStats
 )

 SSOLoginButton = React.createClass(
@@ -642,12 +663,34 @@
     return dom.a {href: ssoUrl}, content
 )

+Header = React.createClass(
+  displayName: 'Header'
+  render: ->
+    years = [ 2014, 2013 ]
+    dom.nav {className: 'navbar navbar-default navbar-fixed-top'},
+      dom.div {className: 'container'},
+        dom.div {className: 'collapse navbar-collapse'},
+          dom.a {className: 'navbar-brand'}, React.createElement(CharacterAvatar, {id: @props.character.id, width: 32})
+          dom.ul {className: 'nav navbar-nav'},
+            dom.li null,
+              dom.a null, "EVE: Year in Review"
+            dom.li {className: 'dropdown'},
+              dom.a {className: 'dropdown-toggle', 'data-toggle': 'dropdown', role: 'button', 'aria-expanded': false}, "Change Year", dom.span({className: 'caret'})
+              dom.ul {className: 'dropdown-menu', role: 'menu'},
+                dom.li null,
+                  dom.a null, 2014
+                dom.li null,
+                  dom.a null, 2013
+          dom.ul {className: 'nav navbar-nav navbar-right'},
+            dom.li null,
+              React.createElement(SSOLoginButton, {linkText: 'Switch Character/Account'})
+)
+
 Title = React.createClass(
   displayName: 'Title'
   switchYear: ->
     return if @props.year == 2014 then 2013 else 2014
   onClick: ->
-    console.log "Switching to", @switchYear()
     this.props.switchToYear(@switchYear())
   render: ->
     dom.h2 null,
@@ -660,13 +703,30 @@
   render: ->
     avgSession = Math.round(@props.stats.characterMinutes / @props.stats.characterSessionsStarted)
     dom.div {className: 'row'},
-      dom.div {className: 'col-md-4'},
+      dom.div {className: 'col-md-3'},
         React.createElement(CharacterAvatar, {id: @props.character.id})
-      dom.div {className: 'col-md-8'},
-        React.createElement CalloutStat, {value: @props.stats.characterMinutes, description: 'Time Played', formatter: humanizeMinutes}
-        React.createElement CalloutStat, {value: @props.stats.daysOfActivity, description: 'Active Days'}
-        React.createElement CalloutStat, {value: @props.stats.characterSessionsStarted, description: 'Logins'}
-        React.createElement CalloutStat, {value: avgSession, description: 'Average Session Length', formatter: humanizeMinutes}
+      dom.div {className: 'col-md-9'},
+        dom.div {className: 'row'},
+          dom.div {className: 'col-md-8'},
+            React.createElement CalloutStat, {
+              value: @props.stats.characterMinutes,
+              description: 'Time Played',
+              formatter: humanizeMinutes
+            }
+            React.createElement CalloutStat, {
+              value: avgSession,
+              description: 'Average Session Length',
+              formatter: humanizeMinutes
+            }
+          dom.div {className: 'col-md-4'},
+            React.createElement CalloutStat, {
+              value: @props.stats.characterSessionsStarted,
+              description: 'Logins'
+            }
+            React.createElement CalloutStat, {
+              value: @props.stats.daysOfActivity,
+              description: 'Active Days'
+            }
 )

 CharacterAvatar = React.createClass(
@@ -674,11 +734,15 @@
   getDefaultProps: ->
     return {
       id: null
-      width: 256
+      width: 200
     }
   render: ->
-    url = "https://image.eveonline.com/Character/#{@props.id}_#{@props.width}.jpg"
-    dom.img {src: url, width: @props.width, height: @props.width, style: {boxShadow: '0 0 30px black'}}
+    url = "https://image.eveonline.com/Character/#{@props.id}_256.jpg"
+    dom.img {
+      src: url,
+      width: @props.width,
+      height: @props.width,
+    }
 )

 CalloutStat = React.createClass(


Helpful comments

Helpful comments

--- src/app/row-map.coffee
+++ src/app/row-map.coffee
@@ -5,9 +5,9 @@

   screenRowRangeForBufferRow: (targetBufferRow) ->
     { mapping, screenRow, bufferRow } = @traverseToBufferRow(targetBufferRow)
-    if mapping and mapping.bufferRows != mapping.screenRows
+    if mapping and mapping.bufferRows != mapping.screenRows # 1:n mapping
       [screenRow, screenRow + mapping.screenRows]
-    else
+    else                                                    # 1:1 mapping
       screenRow += targetBufferRow - bufferRow
       [screenRow, screenRow + 1]


Implement several update strategies

Implement several update strategies

* Break up the update function into manageable parts
* Refactor the special variables into the RESIN_SUPERVISOR_ namespace
* Use three update strategies: normal, kill before download, hand over

TO-DO: implement waitToKill in the hand over case.

--- src/application.coffee
+++ src/application.coffee
@@ -270,7 +270,9 @@
 				.catch ENOENT, _.noop
 				.catch (err) ->
 					release()
-					throw new Error("Updates are locked: #{err.message}")
+					err = new Error("Updates are locked: #{err.message}")
+					err.isLocked = true
+					throw err
 		.disposer (release) ->
 			Promise.try ->
 				lockFile.unlockAsync(lockName) if force != true
@@ -297,11 +299,13 @@
 	application.poll()

 specialActionEnvVars =
-	'RESIN_OVERRIDE_LOCK': null
-	'RESIN_VPN_CONTROL': utils.vpnControl
-	'RESIN_CONNECTIVITY_CHECK': utils.connectivityCheck
-	'RESIN_POLL_INTERVAL': apiPollInterval
-	'RESIN_LOG_CONTROL': utils.resinLogControl
+	'RESIN_SUPERVISOR_UPDATE_STRATEGY': null
+	'RESIN_SUPERVISOR_HANDOVER_TIMEOUT': null
+	'RESIN_SUPERVISOR_OVERRIDE_LOCK': null
+	'RESIN_SUPERVISOR_VPN_CONTROL': utils.vpnControl
+	'RESIN_SUPERVISOR_CONNECTIVITY_CHECK': utils.connectivityCheck
+	'RESIN_SUPERVISOR_POLL_INTERVAL': apiPollInterval
+	'RESIN_SUPERVISOR_LOG_CONTROL': utils.resinLogControl

 executedSpecialActionEnvVars = {}

@@ -322,6 +326,17 @@
 	return err if _.isError(err)
 	return new Error(err.message ? err)

+selectAndKill = (appId) ->
+	knex('app').select().where({ appId })
+	.then ([ app ]) ->
+		if !app?
+			throw new Error('App not found')
+		kill(app)
+
+waitToKill = (app, timeout) ->
+	# TO-DO
+	# Wait for app to signal it's ready to die, or timeout to complete (if it is defined and not-empty)
+
 UPDATE_IDLE = 0
 UPDATE_UPDATING = 1
 UPDATE_REQUIRED = 2
@@ -331,6 +346,119 @@
 	failed: 0
 	forceNext: false
 	intervalHandle: null
+
+updateStrategies =
+	'normal-update': (localApp, app, needsDownload, force, timeout) ->
+		Promise.try ->
+			fetch(app) if needsDownload
+		.then ->
+			Promise.using lockUpdates(localApp, force), ->
+				logSystemEvent(logTypes.updateApp, app) if localApp.imageId == app.imageId
+				selectAndKill(localApp.appId)
+				.then ->
+					start(app)
+			.catch (err) ->
+				logSystemEvent(logTypes.updateAppError, app, err) unless err.isLocked?
+				throw err
+	'kill-before-download': (localApp, app, needsDownload, force, timeout) ->
+		logSystemEvent(logTypes.updateApp, app) if localApp.imageId == app.imageId
+		Promise.using lockUpdates(localApp, force), ->
+			selectAndKill(localApp.appId)
+			.then ->
+				fetch(app) if needsDownload
+			.then ->
+				start(app)
+		.catch (err) ->
+			logSystemEvent(logTypes.updateAppError, app, err) unless err.isLocked?
+			throw err
+	'hand-over': (localApp, app, needsDownload, force, timeout) ->
+		Promise.using lockUpdates(localApp, force), ->
+			Promise.try ->
+				fetch(app) if needsDownload
+			.then ->
+				logSystemEvent(logTypes.updateApp, app) if localApp.imageId == app.imageId
+				start(app)
+			.then ->
+				waitToKill(localApp, timeout)
+			.then ->
+				selectAndKill(localApp.appId)
+		.catch (err) ->
+			logSystemEvent(logTypes.updateAppError, app, err) unless err.isLocked?
+			throw err
+
+
+updateUsingStrategy = (strategy, localApp, app, needsDownload, force, timeout) ->
+	if strategy not in _.keys(updateStrategies)
+		strategy = 'normal-update'
+	updateStrategies[strategy](localApp, app, needsDownload, force, timeout)
+
+getRemoteApps = (uuid, apiKey) ->
+	cachedResinApi.get
+		resource: 'application'
+		options:
+			select: [
+				'id'
+				'git_repository'
+				'commit'
+			]
+			filter:
+				commit: $ne: null
+				device:
+					uuid: uuid
+		customOptions:
+			apikey: apiKey
+
+getEnvAndFormatRemoteApps = (deviceId, remoteApps, uuid, apiKey) ->
+	Promise.map remoteApps, (app) ->
+		getEnvironment(app.id, deviceId, apiKey)
+		.then (environment) ->
+			app.environment_variable = environment
+			utils.extendEnvVars(app.environment_variable, uuid)
+		.then (env) ->
+			fullEnv = env
+			env = _.omit(env, _.keys(specialActionEnvVars))
+			return [
+				{
+					appId: app.id
+					env: fullEnv
+				},
+				{
+					appId: '' + app.id
+					commit: app.commit
+					imageId: "#{config.registryEndpoint}/#{path.basename(app.git_repository, '.git')}/#{app.commit}"
+					env: JSON.stringify(env) # The env has to be stored as a JSON string for knex
+				}
+			]
+	.then(_.zip)
+	.then ([ remoteAppEnvs, remoteApps ]) ->
+		return [_.mapValues(_.indexBy(remoteAppEnvs, 'appId'), 'env'), _.indexBy(remoteApps, 'appId')]
+
+formatLocalApps = (apps) ->
+	apps = _.indexBy(apps, 'appId')
+	localAppEnvs = {}
+	localApps = _.mapValues apps, (app) ->
+		localAppEnvs[app.appId] = JSON.parse(app.env)
+		app.env = JSON.stringify(_.omit(localAppEnvs[app.appId], _.keys(specialActionEnvVars)))
+		app = _.pick(app, [ 'appId', 'commit', 'imageId', 'env' ])
+	return { localApps, localAppEnvs }
+
+compareForUpdate = (localApps, remoteApps, localAppEnvs, remoteAppEnvs) ->
+	remoteAppIds = _.keys(remoteApps)
+	localAppIds = _.keys(localApps)
+	appsWithChangedEnvs = _.filter remoteAppIds, (appId) ->
+		return !localAppEnvs[appId]? or !_.isEqual(remoteAppEnvs[appId], localAppEnvs[appId])
+	toBeRemoved = _.difference(localAppIds, remoteAppIds)
+	toBeInstalled = _.difference(remoteAppIds, localAppIds)
+
+	toBeUpdated = _.intersection(remoteAppIds, localAppIds)
+	toBeUpdated = _.filter toBeUpdated, (appId) ->
+		return !_.isEqual(remoteApps[appId], localApps[appId])
+
+	toBeDownloaded = _.filter toBeUpdated, (appId) ->
+		return !_.isEqual(remoteApps[appId].imageId, localApps[appId].imageId)
+	toBeDownloaded = _.union(toBeDownloaded, toBeInstalled)
+	allAppIds = _.union(localAppIds, remoteAppIds)
+	return { toBeRemoved, toBeDownloaded, toBeInstalled, toBeUpdated, appsWithChangedEnvs, allAppIds }

 application.update = update = (force) ->
 	if updateStatus.state isnt UPDATE_IDLE
@@ -350,64 +478,13 @@
 			uuid = uuid.value

 			deviceId = device.getID()
-
-			remoteApps = cachedResinApi.get
-				resource: 'application'
-				options:
-					select: [
-						'id'
-						'git_repository'
-						'commit'
-					]
-					filter:
-						commit: $ne: null
-						device:
-							uuid: uuid
-				customOptions:
-					apikey: apiKey
-
-			remoteAppEnvs = {}
-			Promise.join deviceId, remoteApps, (deviceId, remoteApps) ->
-				return Promise.map remoteApps, (app) ->
-					getEnvironment(app.id, deviceId, apiKey)
-					.then (environment) ->
-						app.environment_variable = environment
-						utils.extendEnvVars(app.environment_variable, uuid)
-					.then (env) ->
-						remoteAppEnvs[app.id] = env
-						env = _.omit(env, _.keys(specialActionEnvVars))
-						return {
-							appId: '' + app.id
-							commit: app.commit
-							imageId: "#{config.registryEndpoint}/#{path.basename(app.git_repository, '.git')}/#{app.commit}"
-							env: JSON.stringify(env) # The env has to be stored as a JSON string for knex
-						}
-			.then (remoteApps) ->
-				remoteApps = _.indexBy(remoteApps, 'appId')
-				remoteAppIds = _.keys(remoteApps)
-
-				apps = _.indexBy(apps, 'appId')
-				localAppEnvs = {}
-				localApps = _.mapValues apps, (app) ->
-					localAppEnvs[app.appId] = JSON.parse(app.env)
-					app.env = JSON.stringify(_.omit(localAppEnvs[app.appId], _.keys(specialActionEnvVars)))
-					app = _.pick(app, [ 'appId', 'commit', 'imageId', 'env' ])
-				localAppIds = _.keys(localApps)
-				appsWithChangedEnvs = _.filter remoteAppIds, (appId) ->
-					return !localAppEnvs[appId]? or !_.isEqual(remoteAppEnvs[appId], localAppEnvs[appId])
-
-				toBeRemoved = _.difference(localAppIds, remoteAppIds)
-				toBeInstalled = _.difference(remoteAppIds, localAppIds)
-
-				toBeUpdated = _.intersection(remoteAppIds, localAppIds)
-				toBeUpdated = _.filter toBeUpdated, (appId) ->
-					return !_.isEqual(remoteApps[appId], localApps[appId])
-
-				toBeDownloaded = _.filter toBeUpdated, (appId) ->
-					return !_.isEqual(remoteApps[appId].imageId, localApps[appId].imageId)
-				toBeDownloaded = _.union(toBeDownloaded, toBeInstalled)
-
-				allAppIds = _.union(localAppIds, remoteAppIds)
+			remoteApps = getRemoteApps(uuid, apiKey)
+
+			Promise.join deviceId, remoteApps, uuid, apiKey, getEnvAndFormatRemoteApps
+			.then ([ remoteAppEnvs, remoteApps ]) ->
+				{ localApps, localAppEnvs } = formatLocalApps(apps)
+				resourcesForUpdate = compareForUpdate(localApps, remoteApps, localAppEnvs, remoteAppEnvs)
+				{ toBeRemoved, toBeDownloaded, toBeInstalled, toBeUpdated, appsWithChangedEnvs, allAppIds } = resourcesForUpdate

 				# Run special functions against variables if remoteAppEnvs has the corresponding variable function mapping.
 				Promise.map appsWithChangedEnvs, (appId) ->
@@ -428,17 +505,12 @@
 				.return(allAppIds)
 				.map (appId) ->
 					Promise.try ->
-						fetch(remoteApps[appId]) if _.includes(toBeDownloaded, appId)
-					.then ->
+						needsDownload = _.includes(toBeDownloaded, appId)
 						if _.includes(toBeRemoved, appId)
 							Promise.using lockUpdates(apps[appId], force), ->
 								# We get the app from the DB again in case someone restarted it
 								# (which would have changed its containerId)
-								knex('app').select().where({ appId })
-								.then ([ app ]) ->
-									if !app?
-										throw new Error('App not found')
-									kill(app)
+								selectAndKill(appId)
 								.then ->
 									knex('app').where('appId', appId).delete()
 							.catch (err) ->
@@ -448,25 +520,18 @@
 							app = remoteApps[appId]
 							# Restore the complete environment so that it's persisted in the DB
 							app.env = JSON.stringify(remoteAppEnvs[appId])
-							start(app)
+							Promise.try ->
+								fetch(remoteApps[appId]) if needsDownload
+							.then ->
+								start(app)
 						else if _.includes(toBeUpdated, appId)
-							localApp = apps[appId]
 							app = remoteApps[appId]
 							# Restore the complete environment so that it's persisted in the DB
 							app.env = JSON.stringify(remoteAppEnvs[appId])
-							logSystemEvent(logTypes.updateApp, app) if localApp.imageId == app.imageId
-							forceThisApp = remoteAppEnvs[appId]['RESIN_OVERRIDE_LOCK'] == '1'
-							Promise.using lockUpdates(localApp, force || forceThisApp), ->
-								knex('app').select().where({ appId })
-								.then ([ localApp ]) ->
-									if !localApp?
-										throw new Error('App not found')
-									kill(localApp)
-								.then ->
-									start(app)
-							.catch (err) ->
-								logSystemEvent(logTypes.updateAppError, app, err)
-								throw err
+							forceThisApp = remoteAppEnvs[appId]['RESIN_SUPERVISOR_OVERRIDE_LOCK'] == '1'
+							strategy = remoteAppEnvs[appId]['RESIN_SUPERVISOR_UPDATE_STRATEGY']
+							timeout = remoteAppEnvs[appId]['RESIN_SUPERVISOR_HANDOVER_TIMEOUT']
+							updateUsingStrategy(strategy, apps[appId], app, needsDownload, force || forceThisApp, timeout)
 					.catch(wrapAsError)
 		.filter(_.isError)
 		.then (failures) ->


Omit host config also when formatting local apps

--- src/application.coffee
+++ src/application.coffee
@@ -496,7 +496,9 @@
 	localAppEnvs = {}
 	localApps = _.mapValues apps, (app) ->
 		localAppEnvs[app.appId] = JSON.parse(app.env)
-		app.env = JSON.stringify(_.omit(localAppEnvs[app.appId], _.keys(specialActionEnvVars)))
+		app.env = _.omit localAppEnvs[app.appId], (v, k) ->
+			_.startsWith(k, device.hostConfigEnvVarPrefix)
+		app.env = JSON.stringify(_.omit(app.env, _.keys(specialActionEnvVars)))
 		app = _.pick(app, [ 'appId', 'commit', 'imageId', 'env' ])
 	return { localApps, localAppEnvs }


fix(input): fire validation only when dirty and blured once

--- src/client/scripts/controllers/components/input.coffee
+++ src/client/scripts/controllers/components/input.coffee
@@ -27,13 +27,13 @@
 			val.toString()

 		customValidation = (val) ->
-			valid: false,
-			message: "custom message"
+			valid: val is "rapid",
+			message: "must be rapid"

 		$scope.validations = [
 			'required'
 			'email'
-			minLength: 3
+			minLength: 2
 			customValidation
 		]

@@ -51,13 +51,9 @@

 		# Watches
 		# =======
-		markupWatch = $scope.$watch ->
+		markupWatch = $scope.$watch 'a', ->
 			$scope.markup = createMarkup()
-			# test = $element[0].querySelector('#markup rb-input')
-			# return unless test
-			# return unless $scope.a.validation
-			# test.validation = $scope.a.validation
-
+		, true

 		# Event Handlers
 		# ==============


Fix shortname of gates

Fix shortname of gates

At the concourse one of the exits shows as 'concourse' when you go to this location, you end up at the gates.

This (untested) PR is supposed to change the exit to 'gates' (assuming I understood the engine mechanics correctly)

--- packages/retropolis-spaceport/airportterminal/concourse/gates/gates.coffee
+++ packages/retropolis-spaceport/airportterminal/concourse/gates/gates.coffee
@@ -12,7 +12,7 @@
   @version: -> '0.0.1'

   @fullName: -> "Airport terminal gate"
-  @shortName: -> "concourse"
+  @shortName: -> "gates"
   @description: ->
     "
       You are at the airport gates that allow passengers to board the planes. All doors are closed as the


always return a Mode

always return a Mode

--- src/atom/editor.coffee
+++ src/atom/editor.coffee
@@ -47,6 +47,7 @@
       require("ace/mode/#{modeName}").Mode
     catch e
       console.error e
+      require("ace/mode/text").Mode

   setModeForURL: (url) ->
     @ace.session.setMode new (@modeForURL url)


:lipstick:

:lipstick:

--- src/atom/editor.coffee
+++ src/atom/editor.coffee
@@ -399,9 +399,6 @@
     fold.destroy()
     @setCursorBufferPosition(fold.start)

-  logLines: ->
-    @renderer.logLines()
-
   splitLeft: ->
     @split('row', 'before')

@@ -442,3 +439,6 @@

   getCurrentMode: ->
     @buffer.getMode()
+
+  logLines: ->
+    @renderer.logLines()


Pricing: log helper function name is changed to logHelper

--- node_modules_koding/koding-payment/lib/request.coffee
+++ node_modules_koding/koding-payment/lib/request.coffee
@@ -82,7 +82,7 @@
       ]
     }

-    log = (type, {paymentMethodId, data, operation}) ->
+    logHelper = (type, {paymentMethodId, data, operation}) =>
       if @loggedRequests.test operation
         paymentMethodInfo = if paymentMethodId then "PaymentMethodId: #{paymentMethodId}\n" else ""
         logger[type] "\n#{paymentMethodInfo}Operation: #{operation} \nData: #{data}"
@@ -104,7 +104,7 @@
       options.headers['Content-Type'] = 'application/xml; charset=UTF-8'

     console.time 'Recurly API request'
-    log "info", {paymentMethodId, operation, data}
+    logHelper "info", {paymentMethodId, operation, data}

     req = https.request options, (res)->
       res.setEncoding 'utf8'
@@ -120,10 +120,10 @@
         xml2js.parseString body, (err, result)=>
           if err
             callback [new models.Error 'xml', 'Invalid XML']
-            log "error",{paymentMethodId, operation, data: err}
+            logHelper "error",{paymentMethodId, operation, data: err}
           else if result.error
             callback [new models.Error result.error.symbol[0], result.error.description[0]._]
-            log "error", {paymentMethodId, operation, data: result.error}
+            logHelper "error", {paymentMethodId, operation, data: result.error}
           else if result.errors
             # See https://docs.recurly.com/api/basics/validation-errors for XML format
             errors = []
@@ -133,12 +133,12 @@
               else
                 errors.push new models.Error err1, err1
             callback errors
-            log "error", {paymentMethodId, operation, data: result.errors}
+            logHelper "error", {paymentMethodId, operation, data: result.errors}
           else
             # Find next page's URL if necessary
             nextUrl = parseNextUrl res  if res.headers.link
             callback null, result, nextUrl
-            log "info", {paymentMethodId, operation, data: JSON.stringify(result)}
+            logHelper "info", {paymentMethodId, operation, data: JSON.stringify(result)}

     req.on 'error', (e)->
       callback [new models.Error 'connection', 'Unable to connect to payment backend.']


Fixed problem with no match

Fixed problem with no match

--- src/qwest-resource-factory.coffee
+++ src/qwest-resource-factory.coffee
@@ -26,7 +26,7 @@

       params = merge defaultParams, params

-      matches = url.match /:([^:]+):/g
+      matches = url.match(/:([^:]+):/g) || []

       for match in matches
         paramName = match.substr(1, match.length - 2)


#460 Merging: Consider all bytes of a voxel; refactor

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -223,17 +223,30 @@
       @trigger("bucketLoaded", address)

-  setBucketData : (cube, bucketIndex, bucketData) ->
-
-    currentBucket = cube[bucketIndex]
-
-    if currentBucket?.temporal
-      # Merge new data with previously existing bucketData
-      for i in [0...@BUCKET_LENGTH]
-        bucketData[i] = currentBucket[i] or bucketData[i]
-
-    cube[bucketIndex] = bucketData
-    currentBucket?.bucketReplacedCallback?()
+  setBucketData : (cube, bucketIndex, newBucketData) ->
+
+    oldBucketData = cube[bucketIndex]
+
+    if cube[bucketIndex]?.temporal
+      newBucketData = @mergeBucketData(newBucketData, oldBucketData)
+
+    cube[bucketIndex] = newBucketData
+    oldBucketData?.bucketReplacedCallback?()
+
+
+  mergeBucketData : (newBucketData, oldBucketData) ->
+
+    voxelPerBucket = 1 << @BUCKET_SIZE_P * 3
+    for i in [0...voxelPerBucket]
+
+      voxelData = (oldBucketData[i * @BYTE_OFFSET + j] for j in [0...@BYTE_OFFSET])
+      voxelEmpty = _.reduce(voxelData, ((memo, v) => memo and v == 0), true)
+
+      unless voxelEmpty
+        for j in [0...@BYTE_OFFSET]
+          newBucketData[i * @BYTE_OFFSET + j] = oldBucketData[i * @BYTE_OFFSET + j]
+
+    return newBucketData

   setArbitraryBucketByZoomedAddress : ([bucket_x, bucket_y, bucket_z, zoomStep], bucketData) ->


even better spy handling

even better spy handling

--- lib/assets/javascripts/jasmine-gwt.js.coffee
+++ lib/assets/javascripts/jasmine-gwt.js.coffee
@@ -57,21 +57,35 @@

     describe @feature.name, ->
       describe scenarioName, ->
+        _this.spies_ = []
+
         jasmine.GWT.runHook('Before', _this)

-        for [ type, name, param ] in _statements
+        for index in [ 0..._statements.length ]
+          [ type, name, param ] = _statements[index]
+
           _this._type = type
           _this._name = name

-          runCode = (param, args = []) ->
+          isLast = (index + 1) == _statements.length
+
+          runCode = (param, isLast, args = []) ->
             it "#{type} #{name}", ->
+              this.spyOn = (args...) ->
+                this.spies_ = _this.spies_
+                jasmine.Spec.prototype.spyOn.apply(this, args)
+
+              this.removeAllSpies = ->
+                if isLast
+                  jasmine.Spec.prototype.removeAllSpies.apply(_this)
+
               param.apply(_this, args)

           args = []

           if param?
             if typeof param == "function"
-              runCode(param)
+              runCode(param, isLast)
               continue
             else
               args = [ param ]
@@ -83,13 +97,13 @@
               if match.constructor == RegExp
                 if result = name.match(match)
                   result.shift()
-                  runCode(code, result.concat(args))
+                  runCode(code, isLast, result.concat(args))
                   found = true

                   break
               else
                 if name == match
-                  runCode(code)
+                  runCode(code, isLast)
                   found = true

                   break


Set an eccessor on AssociationSets so we can do data-hideif: asssociation.loaded

--- src/model/associations/association_set.coffee
+++ src/model/associations/association_set.coffee
@@ -8,10 +8,14 @@
   load: (callback) ->
     return callback(undefined, @) unless @foreignKeyValue?
     @association.getRelatedModel().load @_getLoadOptions(), (err, records) =>
-      @loaded = true unless err
+      unless err
+        @set 'loaded', true
+        @loaded = true
       @fire 'loaded'
       callback(err, @)
   _getLoadOptions: ->
     loadOptions = {}
     loadOptions[@association.foreignKey] = @foreignKeyValue
     loadOptions
+
+  @accessor 'loaded', Batman.Property.defaultAccessor


fix undefined child

fix undefined child

--- lib/mixins/helpers.coffee
+++ lib/mixins/helpers.coffee
@@ -57,10 +57,10 @@
     return node.attributes.class.indexOf(name) isnt -1

   $.getChildren = (node, childTagNames) ->
-    if node.children instanceof Array
+    if node? and node.children instanceof Array
       for tagName in childTagNames
         for child in node.children
-          return child if child.tag is tagName
+          return child if child? and child.tag is tagName

       for child in node.children
         foundChild = $.getChildren(child, childTagNames)


compute client: don't remove socket from cache whenever there is any error; when compute is slow, there will be a lot of errors and adding a bunch of new connections only makes matters much worse; also increase timeout slightly

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -275,6 +275,7 @@
     remove_from_cache: (opts) =>
         opts = defaults opts,
             host : required
+        winston.debug("remove_from_cache(host=#{opts.host})")
         if @_socket_cache?
             delete @_socket_cache[opts.host]

@@ -965,7 +966,9 @@
                     cb      : (err, resp) =>
                         if err
                             dbg("error calling compute server -- #{err}")
-                            @compute_server.remove_from_cache(host:@host)
+                            # For heavily loaded systems, an error as above can happen a lot.
+                            # The server will get removed when the connection itself closes.
+                            #@compute_server.remove_from_cache(host:@host)
                             opts.cb(err)
                         else
                             dbg("got response #{misc.to_safe_str(resp)}")


Add help text for nested folder creation

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -342,6 +342,10 @@

     displayName : 'ProjectFiles-NoFiles'

+    create_folder : () ->
+        @props.actions.create_folder(@props.file_search, @props.current_path, (a) => setState(error: a))
+        @props.actions.setState(file_search : '', page_number: 0)
+
     create_file : (ext) ->
         @props.actions.create_file
             name         : @props.file_search
@@ -350,12 +354,12 @@
             on_download  : ((a) => @setState(download: a))
             # TODO: Render error in useful location
             on_error     : ((a) => @setState(error: a))
-        if not @state.error?
+        if not @state?.error?
             @props.actions.setState(file_search : '', page_number: 0)

     # Default to sagews if none is given.
     extension : ->
-        if @props.file_search?.indexOf('.') == -1
+        if @props.file_search?.lastIndexOf('.') <= @props.file_search?.lastIndexOf('/')
           "sagews"

     # Go to the new file tab if there is no file search
@@ -365,14 +369,18 @@
         else
             @create_file()

+    filename_text : ->
+        ext = @extension()
+        if ext and @props.file_search.slice(-1) isnt '/'
+            "#{@props.file_search}.#{ext}"
+        else
+            "#{@props.file_search}"
+
     button_text : ->
-        ext = @extension()
         if not @props.file_search?.length > 0
             "Create or upload files..."
-        else if ext and @props.file_search.slice(-1) isnt '/'
-            "Create #{@props.file_search}.#{ext}"
-        else
-            "Create #{@props.file_search}"
+        else
+            "Create #{@filename_text()}"

     render_new_button : ->
         <Button
@@ -381,10 +389,25 @@
             <Icon name='plus-circle' /> {@button_text()}
         </Button>

+    # TODO: Make better help text
+    render_help_text : ->
+        last_folder_index = @props.file_search?.lastIndexOf('/')
+        if @props.file_search?.length > 0 and last_folder_index > 1
+            if last_folder_index == @props.file_search.length - 1
+                text = "Will create the nested folder structure #{@props.file_search}"
+            else
+                text = "Will create #{@filename_text().slice(last_folder_index + 1)} under the folder path #{@props.file_search.slice(0, last_folder_index + 1)}"
+            style =
+                wordWrap:'break-word'
+                marginTop:'4px'
+            <Alert style={style} bsStyle='info'>
+                {text}
+            </Alert>
+
     render_file_type_selection : ->

Or select a file type

-            <FileTypeSelector create_file={@create_file} create_folder={@create_file} />
+            <FileTypeSelector create_file={@create_file} create_folder={@create_folder} />

     render : ->
@@ -397,6 +420,7 @@

                 {@render_new_button() if not @props.public_view}
+                {@render_help_text()}
                 {@render_file_type_selection() if @props.file_search?.length > 0}
             </Col>
             <Col sm=2>
@@ -1327,12 +1351,12 @@
     getDefaultProps : ->
         file_search : ''

-    render_warning : ->
-        if @props.file_search?.length > 0
+    render_help_info : ->
+        if @props.file_search?.length > 0 and @props.file_search?.lastIndexOf('/') < 1
             if @props.file_search == '/'
                 text = "Only showing folders"
             else
-                text = "Showing only files matching #{@props.file_search}"
+                text = "Showing files matching #{@props.file_search}"
             <Alert style={wordWrap:'break-word'} bsStyle='info'>
                 {text}
             </Alert>
@@ -1378,7 +1402,7 @@
                 on_submit     = {@search_submit}
             />
             {@render_create_file_warning()}
-            {@render_warning()}
+            {@render_help_info()}

 ProjectFilesNew = rclass


graph: remove JCache since we've nginx cache, also this inteferes with neo4j cache

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -2,8 +2,6 @@
 neo4j = require "neo4j-koding"
 {race} = require 'sinkrow'
 {Base, ObjectId, race} = require 'bongo'
-
-JCache = require '../cache.coffee'

 module.exports = class Graph
   constructor:({config, facets})->
@@ -222,13 +220,8 @@
               tempRes.push objected
               collectRelations objected

-      JCache.get query, (err, results)=>
-        if err or not results
-          @db.query query, {startDate}, (err, results)=>
-            JCache.add query, results
-            returnResults(err, results)
-        else
-          returnResults(err, results)
+      @db.query query, {startDate}, (err, results)=>
+        returnResults(err, results)

   fetchRelateds: (query, callback)->
     @db.query query, {}, (err, results) ->


Test new look in simple way

Test new look in simple way

--- ozaria/site/views/play/level/LevelDialogueView.coffee
+++ ozaria/site/views/play/level/LevelDialogueView.coffee
@@ -61,7 +61,7 @@
     @sessionID = options.sessionID
     @currentMessage = ''
     @doneAnimating = false
-    @SHEPHERD_TIMEOUT = 5 * 1000
+    @SHEPHERD_TIMEOUT = 25 * 1000
     # Resizing is debounced to avoid performance issues and spamming the text fitting.
     @onWindowResize = _.debounce(@onWindowResize, 100)
     $(window).on('resize', @onWindowResize)
@@ -152,20 +152,50 @@
     if runShepherd
       tour = new Shepherd.Tour({
         defaultStepOptions: {
+          classes: 'shepherd-rectangle',
           scrollTo: true
+        }
+        useModalOverlay: true
+      })
+
+      Shepherd.on('complete', ->
+        $('#level-dialogue-view').css('display', 'unset')
+      )
+
+      defaultButtons = [{
+        text: 'Next'
+        action: tour.next
+      }]
+
+      tour.addSteps([{
+        id: 'example-step'
+        text: 'This step is attached to the bottom of the .example-css-selector element.',
+        attachTo: {
+          element: '#goals-view'
+          on: 'bottom'
         },
-        useModalOverlay: true,
-        steps: [{
-          id: 'example-step',
-          attachTo: {
-            element: '.dialogue-area',
-            on: 'bottom'
-          },
-          classes: 'hidden-shepherd-box',
-        }]
-      })
+        buttons: defaultButtons
+      }, {
+        id: 'example-step2'
+        text: 'This step is attached to the bottom of the .example-css-selector element. asdf'
+        attachTo: {
+          element: '#goals-view'
+          on: 'left'
+        }
+        buttons: defaultButtons
+      }])
+
+      $('#level-dialogue-view').css('display', 'none')
+      tour.on('show', ->
+        # TODO: If the new current step is not a Moving Vega step, change the class, change the picture
+        console.log('in shepherd tour show')
+        $('.shepherd-content').prepend($(''))
+      )
+
       tour.start()
-      setTimeout(tour.cancel, @SHEPHERD_TIMEOUT)
+      $('.shepherd-content').prepend($(''))
+
+# setTimeout(tour.cancel, @SHEPHERD_TIMEOUT)

   resetFitty: ->
     @fitty?.element.removeEventListener('fit', @adjustText)


Updated tests.

Updated tests.

--- test/chalkboard_test.coffee
+++ test/chalkboard_test.coffee
@@ -19,43 +19,22 @@
     options =
       output: "test/"

-    outputOptions =
-      format: "markdown"
-      output: "test/"
-      join:   null
-      files:  []
-
-    test.deepEqual chalkboard.configure(options),
-      outputOptions,
-      "Setting output"
+    test.equal chalkboard.configure(options).output,
+      "test/", "Setting output"

     options1 =
       output: "test/"
       format: "html"

-    outputOptions1 =
-      format: "html"
-      output: "test/"
-      join:   null
-      files:  []
-
-    test.deepEqual chalkboard.configure(options1),
-      outputOptions1,
-      "Setting format"
+    test.equal chalkboard.configure(options1).format,
+      "html", "Setting format"

     options2 =
       output: "test/"
       args:   ["src/", "test/"]

-    outputOptions2 =
-      format: "markdown"
-      output: "test/"
-      join:   null
-      files:  ["src/", "test/"]
-
-    test.deepEqual chalkboard.configure(options2),
-      outputOptions2,
-      "Setting args and files"
+    test.deepEqual chalkboard.configure(options2).files,
+      ["src/", "test/"], "Setting args and files"

     test.done()


tableverse/dice: Don't snap to grid.

tableverse/dice: Don't snap to grid.

--- Oghma/tableverse/dice.coffee
+++ Oghma/tableverse/dice.coffee
@@ -65,6 +65,8 @@
       else
         super event

+    is_grid_controlled: -> false
+
   thingyverse.define(
      'dice',
     [


time() test for Server

time() test for Server

--- src/tests/spec/server.spec.coffee
+++ src/tests/spec/server.spec.coffee
@@ -219,3 +219,9 @@
 									'[1]'
 									'[2]'
 								])
+
+					describe 'when timed', ->
+						it 'should give precise incremental numbers representing the time', ->
+							# Bad test, but Jasmine's mock clock does not allow faking Date.now() results...
+							first = server.time()
+							expect(server.time() - first).not.toBeLessThan(0)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -43,7 +43,7 @@
   @param {Color} [color="black"] The color to fade from
   ###
   fadeIn: (options={}) ->
-    {alpha, color, duration} = Object.reverseMerge(options, )
+    {alpha, color, duration} = Object.reverseMerge(options, fadeInDefault)

     configureFade(duration, color, 0)


feat: silentErrors and nonRetryable in request async processor

--- src/processors/request.async.processor.coffee
+++ src/processors/request.async.processor.coffee
@@ -23,5 +23,5 @@
     resolveWithFullResponse: fullResponse
   }

-module.exports = ({ apiUrl, fullResponse = false }) ->
-  RequestProcessor builderRequest(apiUrl, fullResponse)+module.exports = ({ apiUrl, fullResponse = false, silentErrors, nonRetryable }) ->
+  RequestProcessor builderRequest(apiUrl, fullResponse), { silentErrors, nonRetryable }


improve wording

improve wording

--- src/client/auth.coffee
+++ src/client/auth.coffee
@@ -6,12 +6,10 @@
 isMobile = require '../utils/is-mobile'

 getNoUserPromise = ()->
-  promise = new Promise (resolve, reject)->
-    reject
-      reason: 'no_current_user',
-      message: 'User must be logged in to perform this action'
-  promise.catch(->)
-  promise
+  Promise.reject({
+    reason: 'no_current_user',
+    message: 'User must be logged in to perform this action'
+  })

 getUser = ()->
   user = Hull.currentUser()
@@ -184,7 +182,7 @@
       # Return promise even if login is in progress.
       msg = "Login in progress. Use `Hull.on('hull.user.login', callback)` to call `callback` when done."
       logger.info msg
-      return Promise.reject {error: {reason:'in_progress', message: msg}}
+      return Promise.reject {error: {reason:'in_progress', message: 'Login already in progress'}}

     # Handle Legacy Format,
     # Ensure New Format: Hash signature
@@ -198,7 +196,7 @@
       unless options.login? and options.password?
         msg = 'Seems like something is wrong in your Hull.login() call, We need a login and password fields to login. Read up here: http://www.hull.io/docs/references/hull_js/#user-signup-and-login'
         logger.warn msg
-        return Promise.reject({error:{reason:'invalid_params', message:msg }})
+        return Promise.reject({error:{ reason:'missing_parameters', message:'Empty login or password' }})


Use 'source'-prefixed scope names for Atom file-types config.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -36,12 +36,12 @@
   "tree-view": {}
   "autocomplete-plus": {}
   "file-types":
-    "^.gvimrc.after": "viml"
-    "^.profile$": "shell"
-    "^.shared_env.*": "shell"
-    "^.spacemacs$": "lisp"
-    "^.vimrc.before$": "viml"
-    "^.vimrc.after$": "viml"
+    "^.gvimrc.after": "source.viml"
+    "^.profile$": "source.shell"
+    "^.shared_env.*": "source.shell"
+    "^.spacemacs$": "source.lisp"
+    "^.vimrc.before$": "source.viml"
+    "^.vimrc.after$": "source.viml"
     ".zsh-theme$": "source.shell"
-    "^.zshenv.local.*$": "shell"
-    "^.zshrc.local.*$": "shell"
+    "^.zshenv.local.*$": "source.shell"
+    "^.zshrc.local.*$": "source.shell"


Set .git.scmbrc file type to "source.shell" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -45,6 +45,7 @@
     "^.amethyst": "source.js"
     "^.gitconfig.local": "source.git-config"
     "^.gitignore": "source.git-config"
+    "^.git.scmbrc": "source.shell"
     "^.gntrc": "source.ini"
     "^.gvimrc.after": "source.viml"
     "^.profile$": "source.shell"


Add `appSetsWindowTitle` configuration parameter to eliminate need for separate CODAP branch

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -749,7 +749,7 @@
     currentContent

   _setWindowTitle: (name) ->
-    if @appOptions?.ui?.windowTitleSuffix
+    if not @appOptions.appSetsWindowTitle and @appOptions?.ui?.windowTitleSuffix
       document.title = "#{if name?.length > 0 then name else (tr "~MENUBAR.UNTITLED_DOCUMENT")}#{@appOptions.ui.windowTitleSeparator}#{@appOptions.ui.windowTitleSuffix}"

   _getHashParams: (metadata) ->


Add GTE and LTE to EDSL

Add GTE and LTE to EDSL

--- src/coffee/edsl.coffee
+++ src/coffee/edsl.coffee
@@ -70,9 +70,15 @@
         gt: (req) ->
           (quality) -> (quality.value > req) ||
             "Requires #{quality.name} greater than #{req}. You have #{quality.value}."
+        gte: (req) ->
+          (quality) -> (quality.value >= req) ||
+            "Requires #{quality.name} at least #{req}. You have #{quality.value}."
         lt: (req) ->
           (quality) -> (quality.value < req) ||
             "Requires #{quality.name} less than #{req}. You have #{quality.value}."
+        lte: (req) ->
+          (quality) -> (quality.value <= req) ||
+            "Requires #{quality.name} no more than #{req}. You have #{quality.value}."
         range: (low, high) ->
           (quality) -> (low < quality.value < high) ||
             "Requires #{quality.name} between #{low} and #{high}. You have #{quality.value}."


Improve Names of Quality EDSL Parameters

--- src/coffee/edsl.coffee
+++ src/coffee/edsl.coffee
@@ -2,16 +2,16 @@
   .factory 'qbnEdsl', (qualities, storylets, frontalChoices, choiceFactory) ->
     api =
       quality: (id, name, description, args = {}) ->
-        {defaultValue, defaultProgress, maxProgress, hasProgress, progressEscalation,
+        {value, progress, maxProgress, hasProgress, progressEscalation,
          visible} = args
-        defaultValue ?= 0
-        defaultProgress ?= 0
+        value ?= 0
+        progress ?= 0
         maxProgress ?= if hasProgress then 100 else 0
         progressEscalation ?= 0.10
         visible ?= true
-        qualities.register id, name, description, defaultValue,
-                                defaultProgress, maxProgress, progressEscalation,
-                                visible
+        qualities.register id, name, description, value,
+                           progress, maxProgress, progressEscalation,
+                           visible
         return
       storylet: (id, title, text, choices...) ->
         storylets.register id, title, text, choices


fix(important): Icon does not update from nextProps

--- src/components/mail-important-icon.cjsx
+++ src/components/mail-important-icon.cjsx
@@ -38,7 +38,7 @@
       category = CategoryStore.getStandardCategory(props.thread.accountId, 'important')
       visible = category?

-    isImportant = category and _.findWhere(@props.thread.categories, {id: category.id})?
+    isImportant = category and _.findWhere(props.thread.categories, {id: category.id})?

     {visible, category, isImportant}


deprecate correctly

deprecate correctly

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -128,8 +128,13 @@
           for more details.
           '''
         type: 'object'
-        default: {}
         order: 14
+        collapsed: true
+        properties:
+          mock:
+            title: 'mock (not appear in the settings view)'
+            type: 'object'
+            default: {}

   uiOptions:
     title: 'UI Options'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -18,7 +18,6 @@
     y: 15

   player2 = engine.add
-    enemy: true
     x: 0
     y: 10


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -19,7 +19,8 @@
     y: 10

   obj4 = engine.add
-    rad: "Awe
+    rad: "Awesome"
+    x:

   indexSum = 0


Removed old test

Removed old test

--- test/expected/coffee.coffee
+++ test/expected/coffee.coffee
@@ -1,3 +0,0 @@
-define 'a', -> 5
-define 'b', ['a'], (a) -> a * 4
-define('c', -> 10)


Removing global optmizer.

Removing global optmizer.

--- src/toaster/core/optimizer.coffee
+++ src/toaster/core/optimizer.coffee
@@ -1,291 +0,0 @@
-require('source-map-support').install()
-
-module.exports = class Optimizer
-
-  optimize:()->
-
-      unless @config.browser?.optimize?
-        return
-
-      # clear release folder
-      @clear()
-
-      # if merge is set, optimization will just merge everything
-      if @config.browser.optimize.merge?
-        return @merge_everything()
-
-      log 'Optimizing project...'
-
-      paths = {}
-      layers = []
-      included = []
-      ordered = @reorder (@files.slice 0)
-
-      for layer_name, layer_deps of @config.browser.optimize.layers
-
-        layers.push layer_name
-
-        contents = ''
-        for dep in layer_deps
-
-          # gets dependency chain
-          found = (ArrayUtil.find ordered, 'filepath': "#{dep}.coffee")
-
-          # if nothing is found
-          unless found?
-            # checks if it was already included
-            is_included = (ArrayUtil.find included, 'filepath': "#{dep}.coffee")
-
-            # and if not..
-            unless is_included?
-              msg = "Cannot find module `#{dep}` for layer `#{layer_name}`."
-              msg += '\nCheck your `toaster.coffee` config file.'
-              error msg
-
-            continue
-
-          # scripts pack (all dependencies resolved)
-          pack = ordered.splice 0, found.index + 1
-
-          # adding all to included array
-          included = included.concat pack
-
-          # increments the layer contents and map the script location into paths
-          for script in pack
-            paths[script.filepath.replace '.coffee', ''] = layer_name
-            contents += script.compile_to_str @config
-
-        # if there's something to be written
-        if contents isnt ''
-
-          if @config.browser?.optimize?.minify
-            contents = MinifyUtil.min contents
-
-          # write layer
-          layer_path = path.join @config.release_dir, "#{layer_name}.js"
-          fs.writeFileSync layer_path, contents
-
-          # notify user through cli
-          relative_path = layer_path.replace @toaster.basepath, ''
-          relative_path = relative_path.substr 1 if relative_path[0] is path.sep
-          msg = "#{'✓ Layer optimized: '.bold}#{layer_name} -> #{relative_path}"
-          log msg.green
-
-        # otherwise if it's empty just inform user through the cli
-        else
-          msg = "#{'✓ Layer is empty: '.bold} #{layer_name} -> [skipped]"
-          log msg.yellow
-
-      # write toaster loader and initializer
-      @write_loader paths
-
-      # copy all vendors as well
-      @copy_vendors_to_release true, null, false
-
-
-
-  missing = {}
-  reorder: (files, cycling = false) ->
-    # log "Module.reorder"
-
-    # if cycling is true or @missing is null, initializes empty array
-    # for holding missing dependencies
-    #
-    # cycling means the reorder method is being called recursively,
-    # no other methods call it with cycling = true
-    @missing = {} if cycling is false
-
-    # looping through all files
-    for file, i in files
-
-      # if theres no dependencies, go to next file
-      continue if !file.dependencies.length && !file.baseclasses.length
-
-      # otherwise loop thourgh all file dependencies
-      for dep, index in file.dependencies
-
-        filepath = dep.path
-
-        # skip vendors
-        continue if dep.vendor
-
-        # search for dependency
-        dependency = ArrayUtil.find files, 'filepath': filepath
-        dependency_index = dependency.index if dependency?
-
-        # continue if the dependency was already initialized
-        continue if dependency_index < i && dependency?
-
-        # if it's found
-        if dependency?
-
-          # if there's some circular dependency loop
-          if (ArrayUtil.has dependency.item.dependencies, 'filepath': file.filepath)
-
-            # remove it from the dependencies
-            file.dependencies.splice index, 1
-
-            # then prints a warning msg and continue
-            warn "Circular dependency found between ".yellow +
-                 filepath.grey.bold + " and ".yellow +
-                 file.filepath.grey.bold
-
-            continue
-
-          # otherwise if no circular dependency is found, reorder
-          # the specific dependency and run reorder recursively
-          # until everything is beautiful
-          else
-            files.splice index, 0, dependency.item
-            files.splice dependency.index + 1, 1
-            @reorder files, true
-            break
-
-        # otherwise if the dependency is not found (for the first time)
-        else if @missing[filepath] != true
-
-          # then add it to the @missing hash (so it will be ignored
-          # until reordering finishes)
-          @missing[filepath] = true
-
-          # move it to the end of the dependencies array (avoiding
-          # it from being touched again)
-          file.dependencies.push filepath
-          file.dependencies.splice index, 1
-
-          # ..and finally prints a warning msg
-          warn "#{'Dependency'.yellow} #{filepath.bold.grey} " +
-             "#{'not found for file'.yellow} " +
-             file.filepath.grey.bold
-
-      # validate if all base classes was properly imported
-      file_index = ArrayUtil.find files, 'filepath': file.filepath
-      file_index = file_index.index
-
-      for bc in file.baseclasses
-        found = ArrayUtil.find files, bc, "classname"
-        not_found = (found == null) || (found.index > file_index)
-
-        if not_found && !@missing[bc]
-          @missing[bc] = true
-          warn "Base class ".yellow +
-             "#{bc} ".bold.grey +
-             "not found for class ".yellow +
-             "#{file.classname} ".bold.grey +
-             "in file ".yellow +
-             file.filepath.bold.grey
-
-    return files
-
-   merge_everything:->
-    console.log 'Merging files..'.grey
-    buffer = "//---------------------------------------- vendors\n"
-    buffer += @merge_vendors() + '\n\n'
-
-    if @config.browser.amd?
-      buffer += "//---------------------------------------- amd loader\n"
-      buffer += @_get_amd_loader()
-
-    buffer += "//---------------------------------------- files\n"
-    buffer += @merge_files() + '\n'
-
-    if @config.browser?.optimize?.minify
-      console.log 'Minifying..'.grey
-      buffer = MinifyUtil.min buffer
-
-    location = path.join @config.release_dir, @config.browser.optimize.merge
-    fs.writeFileSync location, buffer
-
-    location = location.replace @toaster.basepath, ''
-    log 'Project merged at: ' + location.green
-
-  merge_files:->
-    buffer = []
-    for file in (@reorder (@files.slice 0))
-      buffer.push file.compile_to_str @config
-
-    buffer.join '\n'
-
-  merge_vendors:->
-    buffer = []
-    for vname, vpath of @config.vendors
-      buffer.push (fs.readFileSync vpath)
-
-    buffer.join '\n'
-
-  write_loader:( paths )->
-    return unless @config.browser.optimize? and @config.browser.amd
-
-    # increment map with all remote vendors
-    paths or= {}
-    for name, url of @config.vendors
-      paths[name] = url if /^http/m.test url
-
-    # mounting main toaster file, contains the toaster builtin amd loader,
-    # all the necessary configs and a hash map containing the layer location
-    # for each module that was merged into it.
-
-    octopus = @_get_amd_loader()
-
-    if paths?
-      paths = (util.inspect paths).replace /\s/g, ''
-    else paths = ''
-
-    octopus += """\n\n
-      /*************************************************************************
-       * Automatic configuration by CoffeeToaster.
-      *************************************************************************/
-
-      require.config({
-        baseUrl: '#{@config.browser.amd.base_url}',
-        paths: #{paths}
-      });
-      require( ['#{@config.browser.amd.main}'] );
-
-      /*************************************************************************
-       * Automatic configuration by CoffeeToaster.
-      *************************************************************************/
-    """
-
-    # writing to disk
-    release_path = path.join @config.release_dir, @config.browser.amd.boot
-
-    if @config.browser.optimize.minify && @cli.r
-      octopus = MinifyUtil.min octopus
-
-    fs.writeFileSync release_path, octopus
-
-  _get_amd_loader:->
-    rjs_path = path.resolve __dirname
-    rjs_path = path.join rjs_path, '..', '..', '..', 'node_modules'
-    rjs_path = path.join rjs_path, 'requirejs', 'require.js'
-    fs.readFileSync rjs_path, 'utf-8'
-
-  # copy vendors to release folder
-  copy_vendors_to_release:( all = true, specific = null, log_time = true )->
-
-    return unless @config.browser.vendors?
-
-    for vname, vurl of @config.browser.vendors
-      unless /^http/m.test vurl
-
-        continue if all is false and vurl isnt specific
-
-        release_path = path.join @config.release_dir, "#{vname}.js"
-        fsu.cp vurl, release_path
-
-        from = vurl.replace @toaster.basepath, ''
-        to = release_path.replace @toaster.basepath, ''
-
-        from = from.substr 1 if from[0] is path.sep
-        to = to.substr 1 if to[0] is path.sep
-
-        # date for CLI notifications
-        now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0]
-
-        msg = if log_time then "[#{now}] " else ""
-        msg += "#{'✓ Vendor copied: '.bold}#{from} -> #{to}"
-
-        log msg.green
-
-        return if all is false and specific?


Context: Add feature spec to verify destroy calls will wait for combinations of commands and domain event publishes

--- src/context/context_feature.spec.coffee
+++ src/context/context_feature.spec.coffee
@@ -153,6 +153,16 @@
         expect(error.message).to.contain 'ExampleContext'

+    it 'should call destroy on the event bus', ->
+      EventBus = require '../event_bus'
+      sandbox.stub(EventBus::, 'destroy').returns Promise.resolve()
+      exampleContext.initialize()
+      .then ->
+        exampleContext.destroy()
+      .then ->
+        expect(EventBus::destroy).to.have.been.called
+
+
     it 'should wait to resolve given there are ongoing command operations', ->
       commandSpy = sandbox.spy()
       exampleContext.addCommandHandlers
@@ -186,7 +196,7 @@
         expect(domainEventHandlerSpy.callCount).to.equal 2

-    it 'should correctly resolve given previous command operations rejected', ->
+    it 'should resolve correctly given previous command operations rejected', ->
       commandSpy = sandbox.spy()
       exampleContext.addCommandHandlers
         DoSomething: (params) ->
@@ -201,11 +211,41 @@
         expect(commandSpy).to.have.been.called

-    it 'should call destroy on the event bus', ->
-      EventBus = require '../event_bus'
-      sandbox.stub(EventBus::, 'destroy').returns Promise.resolve()
-      exampleContext.initialize()
-      .then ->
-        exampleContext.destroy()
-      .then ->
-        expect(EventBus::destroy).to.have.been.called+    it 'should wait to resolve given a command which publishes a domain event which triggers another command with domain event', ->
+      exampleContext.addAggregate 'Example', class Example
+        create: ->
+          @$emitDomainEvent 'ExampleCreated', {}
+
+
+        modify: ->
+          @$emitDomainEvent 'ExampleModified', {}
+      exampleContext.defineDomainEvents
+        ExampleCreated: ->
+        ExampleModified: ->
+
+
+      exampleContext.addCommandHandlers
+        CreateExample: ->
+          @$aggregate.create 'Example'
+          .then (example) ->
+            example.$save()
+
+
+        ModifyExample: ({id}) ->
+          @$aggregate.load 'Example', id
+          .then (example) ->
+            example.modify()
+            example.$save()
+
+      exampleContext.subscribeToDomainEvent 'ExampleCreated', (domainEvent) ->
+        exampleContext.command 'ModifyExample', id: domainEvent.aggregate.id
+
+      domainEventHandlerStub = sandbox.stub()
+      exampleContext.subscribeToDomainEvent 'ExampleModified', domainEventHandlerStub
+
+      exampleContext.initialize()
+      .then ->
+        exampleContext.command 'CreateExample', {}
+        exampleContext.destroy()
+      .then ->
+        expect(domainEventHandlerStub).to.have.been.called


Add initial spec task support on linux

Add initial spec task support on linux

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -2,7 +2,6 @@
 path = require 'path'

 _ = require 'underscore-plus'
-
 async = require 'async'

 module.exports = (grunt) ->
@@ -10,18 +9,24 @@

   packageSpecQueue = null

+  getAppPath = ->
+    contentsDir = grunt.config.get('atom.contentsDir')
+    switch process.platform
+      when 'darwin'
+        path.join(contentsDir, 'MacOS', 'Atom')
+      when 'linux'
+        path.join(contentsDir, 'atom')
+      when 'win32'
+        path.join(contentsDir, 'atom.exe')
+
   runPackageSpecs = (callback) ->
     failedPackages = []
     rootDir = grunt.config.get('atom.shellAppDir')
-    contentsDir = grunt.config.get('atom.contentsDir')
     resourcePath = process.cwd()
-    if process.platform is 'darwin'
-      appPath = path.join(contentsDir, 'MacOS', 'Atom')
-    else if process.platform is 'win32'
-      appPath = path.join(contentsDir, 'atom.exe')
+    appPath = getAppPath()

     packageSpecQueue = async.queue (packagePath, callback) ->
-      if process.platform is 'darwin'
+      if process.platform in ['darwin', 'linux']
         options =
           cmd: appPath
           args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
@@ -57,15 +62,11 @@
     packageSpecQueue.drain = -> callback(null, failedPackages)

   runCoreSpecs = (callback) ->
-    contentsDir = grunt.config.get('atom.contentsDir')
-    if process.platform is 'darwin'
-      appPath = path.join(contentsDir, 'MacOS', 'Atom')
-    else if process.platform is 'win32'
-      appPath = path.join(contentsDir, 'atom.exe')
+    appPath = getAppPath()
     resourcePath = process.cwd()
     coreSpecsPath = path.resolve('spec')

-    if process.platform is 'darwin'
+    if process.platform in ['darwin', 'linux']
       options =
         cmd: appPath
         args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
@@ -90,7 +91,7 @@

     # TODO: This should really be parallel on both platforms, however our
     # fixtures step on each others toes currently.
-    if process.platform is 'darwin'
+    if process.platform in ['darwin', 'linux']
       method = async.parallel
     else if process.platform is 'win32'
       method = async.series


@me next time

@me next time

--- app/assets/javascripts/components/articles.js.coffee
+++ app/assets/javascripts/components/articles.js.coffee
@@ -21,7 +21,7 @@
 				className: 'table table-bordered'
 				React.DOM.thead null
 					React.DOM.th null, "Article URL"
-						onClick: {reverseArticles}
+						onClick: {@reverseArticles}
 					React.DOM.th null, "Status"
 					React.DOM.th null, "Mood Music URL"
 				React.DOM.tbody null,


player instantly dead when a word hits left

--- src/kick.coffee
+++ src/kick.coffee
@@ -3,6 +3,7 @@

 pool_words = []
 shoot_word = null
+player_die = false

 class ShootingWord
     constructor: (@full) ->
@@ -32,8 +33,7 @@
         @show.empty()
              .append($('<u>').html(@done()))
              .append($('<b>').html(@remain))
-
-    # TODO player die when word reach left side
+        @left <= 0

 pool_words.push(new ShootingWord('kick neizod'))
@@ -45,12 +45,14 @@
     $('#playground').empty()
     if shoot_word?
         shoot_word.move()
-        shoot_word.update()
+        player_die = true if shoot_word.update()
         $('#playground').append(shoot_word.show)
     for word in pool_words
         word.move()
-        word.update()
+        player_die = true if word.update()
         $('#playground').append(word.show)
+    if player_die
+        $('#playground').css('background-color', 'darkred')

 $(document).ready ->


Content Gallery - Disable nav arrow on mobile

--- app/assets/javascripts/martian_components/components/content_gallery.coffee
+++ app/assets/javascripts/martian_components/components/content_gallery.coffee
@@ -28,6 +28,11 @@
       slidesToShow:   1
       slidesToScroll: 1
       focusOnSelect:  true
+      responsive: [
+        {
+          breakpoint: 768
+          settings: {arrows: false}
+        }]

     @slider.addClass('single-slide') if $('.component-content', @el).length == 1
     @slider.slick options


Set up default width to match parent or screen

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -9,12 +9,13 @@

 		_.defaults options,
 			backgroundColor: "transparent"
-			width: 300
-			height: 50
 			html: "Add text"
 			color: "#888"

 		super options
+
+		# Set default width
+		@width = if @parent? then @parent.width else Screen.width

 		# Set type defaults
 		if not @fontFamily


added forms general error message display

--- app/assets/javascripts/rails_admin_cms/cms.js.coffee
+++ app/assets/javascripts/rails_admin_cms/cms.js.coffee
@@ -44,7 +44,9 @@
     @data_js('cms-flash').fadeIn().delay(3500).fadeOut(800)

   @validate: =>
-    $.validate({ modules: 'security, file', validateOnBlur: false})
+    $.validate({ modules: 'security, file', validateOnBlur: false, onError: (e) ->
+      that.data_js('general-error').show()
+    })

   @validate_mailchimp: =>
     $.validate(form: '#mailchimp_form', validateOnBlur: false)


Update forum.coffee

Update forum.coffee

fix loading text on markdown preview

--- resources/assets/coffee/forum.coffee
+++ resources/assets/coffee/forum.coffee
@@ -254,6 +254,8 @@
         $('#preview-box').html content
         emojify.run document.getElementById('preview-box')
         return
+    else
+      $('#preview-box').text 'Content is empty.'
     return

   hookPreview: (switcher, textarea) ->


Fix a typing error in option checking: printBackgrounds => printBackground

--- atom/browser/api/lib/web-contents.coffee
+++ atom/browser/api/lib/web-contents.coffee
@@ -97,7 +97,7 @@
       printingSetting.marginsType = options.marginsType
     if options.printSelectionOnly
       printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
-    if options.printBackgrounds
+    if options.printBackground
       printingSetting.shouldPrintBackgrounds = options.printBackground

     if options.pageSize and PDFPageSize[options.pageSize]


added test for class method support

added test for class method support

--- test/heterarchy.spec.coffee
+++ test/heterarchy.spec.coffee
@@ -58,42 +58,49 @@
         constructor: ->
             @a = 'a'
         method: -> "A"
+        @method: -> "A"

     class B extends A
         constructor: ->
             super
             @b = 'b'
         method: -> "B>#{super}"
+        @method: -> "B>#{super}"

     class C extends A
         constructor: ->
             super
             @c = 'c'
         method: -> "C>#{super}"
+        @method: -> "C>#{super}"

     class D extends multi B, C
         constructor: ->
             super
             @d = 'd'
         method: -> "D>#{super}"
+        @method: -> "D>#{super}"

     class E extends A
         constructor: ->
             super
             @e = 'e'
         method: -> "E>#{super}"
+        @method: -> "E>#{super}"

     class F extends multi C, E
         constructor: ->
             super
             @f = 'f'
         method: -> "F>#{super}"
+        @method: -> "F>#{super}"

     class G extends multi D, F
         constructor: ->
             super
             @g = 'g'
         method: -> "G>#{super}"
+        @method: -> "G>#{super}"

     # Hierarchy of classes where classes that only inherit from
     # `object` magically get a superclass in a multiple inheritance
@@ -143,15 +150,18 @@

     describe 'multi', ->

-        it 'calls super properly in multi case', ->
+        it 'calls super of instance methods properly in multi case', ->
             obj = new D
             (mro D).should.eql [D, B, C, A, Object]
             obj.method().should.equal "D>B>C>A"

-        it 'calls super properly in recursive multi case', ->
+        it 'calls super of instance methods properly in recursive multi case', ->
             obj = new G
             (mro G).should.eql [G, D, B, F, C, E, A, Object]
             obj.method().should.equal "G>D>B>F>C>E>A"
+
+        it 'calls super of class methods properly in recursive multi case', ->
+            G.method().should.equal "G>D>B>F>C>E>A"

         it 'gets constructed properly', ->
             obj = new D
@@ -165,7 +175,7 @@
             (hierarchy inherited D).should.not.eql mro(D)[1..]
             (hierarchy inherited inherited D).should.eql mro(D)[2..]

-        it 'it memoizes generated superclasses', ->
+        it 'memoizes generated superclasses', ->
             (inherited D).should.equal multi B, C

         it 'throws error on inconsistent hierarchy', ->


Fix missing close paren in variantAutocomplete

--- backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee
+++ backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee
@@ -33,7 +33,7 @@

     formatResult: formatVariantResult
     formatSelection: (variant, container, escapeMarkup) ->
-      if !!variant.options_text
-        Select2.util.escapeMarkup("#{variant.name} (#{variant.options_text}")
+      if variant.options_text
+        Select2.util.escapeMarkup("#{variant.name} (#{variant.options_text})")
       else
         Select2.util.escapeMarkup(variant.name)


Tweak nested field code

Tweak nested field code

--- vendor/assets/javascripts/fustrate/generic_form.coffee
+++ vendor/assets/javascripts/fustrate/generic_form.coffee
@@ -13,8 +13,8 @@
       element = $ domObject
       name = element.prop 'name'

-      if captures = name.match /\[([a-z_]+)\]$/
-        @fields[captures[1]] = element
+      if captures = name.match /\[([a-z0-9_]+)\]/g
+        @_setNestedField(captures, element)
       else
         @fields[name] = element

@@ -28,3 +28,17 @@
     setTimeout (=> $.rails.enableFormElements(@root)), 100

     false
+
+  # Modified to remove recursion - no need to pass elements around endlessly.
+  _setNestedField: (path, element) ->
+    context = @fields
+    first = path.shift()
+    piece = first.substring(1, first.length - 1)
+
+    while path.length > 0
+      context[piece] ?= {}
+      context = context[piece]
+      next = path.shift()
+      piece = next.substring(1, next.length - 1)
+
+    context[piece] = element


don't have assertions

don't have assertions

--- examples/3.cson
+++ examples/3.cson
@@ -42,27 +42,3 @@
       revoke : key : "e"
     },
   ]
-assertions : [
-  {
-    type : "family",
-    time : sum : [ "now", 94 ]
-    subkeys : [ "s" ]
-    sibkeys : [ "e" ]
-    eldest : [ "e" ]
-  },
-  {
-    type : "family"
-    time : sum : [ "now", 101 ]
-    subkeys : [ "s", "sub1" ]
-    sibkeys : [ "e", "sib1" ]
-    eldest : [ "e" ]
-  },
-  {
-    type : "family"
-    time : sum : [ "now", 204 ]
-    subkeys : [ "s", "sub1" ]
-    sibkeys : [ "sib1" ]
-    eldest : [ "e" ]
-  }
-]
-


webterm: fixed joint recursion on termination

--- client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
@@ -21,7 +21,7 @@
       webTermView.terminal?.setFocused yes

       webTermView.on "WebTerm.terminated", (server) =>
-        if @tabView.getActivePane() == pane
+        if not pane.isDestroyed and @tabView.getActivePane() is pane
           @tabView.removePane pane

   _windowDidResize:->


Remove TODO

Remove TODO

--- grammars/csound-document.cson
+++ grammars/csound-document.cson
@@ -39,7 +39,6 @@
     beginCaptures: 1: name: 'entity.name.tag.csound-document'
     endCaptures:   1: name: 'entity.name.tag.csound-document'
     patterns: [
-      # TODO: What kinds of comments are available?
       {
         name:  'comment.line.cabbage-gui.csound-document'
         begin: ';'


copy over turtle events

copy over turtle events

--- src/config/game.coffee
+++ src/config/game.coffee
@@ -1,3 +1,35 @@
 exports.game = {
   _LABEL: "game"
+  step: (world, args) ->
+    turtles = world.find_parent 'turtles'
+    counter = world.get('program_counter')
+    program = world.get('program')
+    if counter >= program.length()
+      program = world.call('reload_program', {name: 'default'})
+      counter = 1
+    assert signal = program.at(counter), "No signal"
+    assert action = world.get('signals')[signal], "No action"
+    turtles.map_children (child) -> child.call(action['do'], action)
+    world.put('program_counter', counter + 1)
+  interval: 500
+  speed: 0
+  run: (world, args) ->
+    step = world.get_raw('step')
+    {speed} = args
+    assert _.isFunction(step), "step is not a function"
+    assert speed?, "run: requires speed"
+    world.owner('speed').put('speed', speed)
+    step_and_repeat = (self) ->
+      speed = world.get('speed')
+      if speed > 0
+        delay = world.get('interval') / speed
+        step(world, args)
+        setTimeout((-> self(self)), delay)
+    step_and_repeat(step_and_repeat)
+  reset: (world, args) ->
+    world.put 'program_counter', 0
+    turtles = world.find_parent 'turtles'
+    turtles.map_children (child) ->
+      child.put 'i', child.get('i_0')
+      child.put 'j', child.get('j_0')
 }


Fix resource model conversion

Fix resource model conversion

--- src/legacy/api-blueprint-adapter.coffee
+++ src/legacy/api-blueprint-adapter.coffee
@@ -184,8 +184,13 @@

     unless !resource.model
       legacyResource.model = resource.model
-      legacyResource.model.description = markdown.toHtmlSync resource.model.description
-      legacyResource.model.headers = legacyHeadersFrom1AHeaders resource.model.headers
+
+      if resource.model.description && resource.model.description.length
+        legacyResource.model.description = markdown.toHtmlSync resource.model.description
+
+      if resource.model.headers && resource.model.headers.length
+        legacyResource.model.headers = legacyHeadersFrom1AHeaders resource.model.headers
+
     else
       legacyResource.model = {}


remove fetchByGroupId from UserRecordsInterface

--- app/records-interfaces/user-records-interface.coffee
+++ app/records-interfaces/user-records-interface.coffee
@@ -3,8 +3,4 @@
     model: UserModel
     constructor: (recordStore) ->
       @baseConstructor recordStore
-      @restfulClient.apiPrefix = config.apiPrefix
-    fetchByGroupId: (groupId) ->
-      @fetch
-        params:
-          group_id: groupId+      @restfulClient.apiPrefix = config.apiPrefix


Always parse number fields as floats

Always parse number fields as floats

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -84,10 +84,7 @@
   parseValue: (type, value) ->
     if value == ''
       value = undefined
-    else if type == 'int'
-      intValue = parseInt(value)
-      value = intValue unless isNaN(intValue)
-    else if type == 'float'
+    else if type == 'number'
       floatValue = parseFloat(value)
       value = floatValue unless isNaN(floatValue)
     else if type == 'array'
@@ -134,7 +131,7 @@
 appendEditor = (namespace, name, value) ->
   keyPath = "#{namespace}.#{name}"
   if _.isNumber(value)
-    type = if value % 1 == 0 then 'int' else 'float'
+    type = 'number'
   else
     type = 'string'


suppress error in normal shutdown case

suppress error in normal shutdown case

--- app/coffee/PackWorker.coffee
+++ app/coffee/PackWorker.coffee
@@ -47,7 +47,7 @@
 				callback(err, result)
 			, DOCUMENT_PACK_DELAY
 	, (err, results) ->
-		if err?
+		if err? and err.message != "shutdown"
 			logger.error {err}, 'error in pack worker processUpdates'
 		finish()


test: remove unused dependency

test: remove unused dependency

--- test/option.encoding.coffee
+++ test/option.encoding.coffee
@@ -1,6 +1,5 @@

 parse = require '../lib'
-{promises: fs} = require 'fs'

 describe 'Option `encoding`', ->


Add docstrings

Add docstrings

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -18,6 +18,7 @@
     ]

     IndexCtrl = ($scope) ->
+        ### Index page controller ###
         $scope.isAuthenticated = window.isAuthenticated
         $scope.successColor = "#5cb85c"
         $scope.failedColor = "#d9534f"
@@ -38,6 +39,7 @@
     ]

     DashboardCtrl = ($scope, $http, Tasks) ->
+        ### Dashboard controller ###
         $http.get('/api/v1/projects/project/?limit=0').success (data) =>
             $scope.projects = data.objects

@@ -50,6 +52,7 @@
     ]

     ManageCtrl = ($scope, $http) ->
+        ### Manage projects page controller ###
         $scope.loading = true
         $http.get('/api/v1/projects/project/?fetch=true&limit=0').success (data) =>
             $scope.projects = data.objects
@@ -64,6 +67,7 @@
     ]

     ProjectCtrl = ($scope, $http, $routeParams, Tasks) ->
+        ### Single project page controller ###
         projectName = _.sprintf '%s/%s', $routeParams['owner'], $routeParams['name']
         projectUrl = _.sprintf '/api/v1/projects/project/%s/', projectName


Move tests next to their modules

Move tests next to their modules

--- src/main.coffee
+++ src/main.coffee
@@ -22,7 +22,7 @@
   else
     rx = require 'reactive-coffee'
 else
-  rx = require './test/rx_mock'
+  rx = require './render/rx_mock'
 sys = require 'sys'

 # Dependencies


payment: make sure we have the subscription info without starting the workflow

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -8,18 +8,12 @@

     super options, data

-    { paymentController } = KD.singletons

     @state = {}

+    @loadPlan()
     @initViews()
     @initEvents()
-
-    paymentController.subscriptions (err, subscription) =>
-      { planTitle } = subscription
-
-      @state.currentPlan = planTitle
-      @plans.planViews[planTitle].disable()

   initViews: ->
@@ -57,8 +51,25 @@
     @plans.on 'PlanSelected', @bound 'planSelected'

+  loadPlan: (callback = noop) ->
+
+    { paymentController } = KD.singletons
+
+    paymentController.subscriptions (err, subscription) =>
+      { planTitle } = subscription
+
+      @state.currentPlan = planTitle
+      @plans.planViews[planTitle].disable()
+
+      callback()
+
+
   planSelected: (planTitle, monthPrice, yearPrice) ->
+
+    return @loadPlan @bound 'planSelected'  unless @state.currentPlan?
+
     @workflowController = new PaymentWorkflow
+      state      : @state
       planTitle  : planTitle
       monthPrice : monthPrice
       yearPrice  : yearPrice


Return a Promise from SystemChecker

Return a Promise from SystemChecker

--- lib/system-checker.coffee
+++ lib/system-checker.coffee
@@ -30,7 +30,8 @@

   check: (args, text) ->
     @deferredInit()
-    {invertIncorrectAsCorrect: true, incorrect: @spellchecker.checkSpelling(text)}
+    @spellchecker.checkSpellingAsync(text).then (incorrect) =>
+      {invertIncorrectAsCorrect: true, incorrect}

   suggest: (args, word) ->
     @deferredInit()