Add includePaths to sass options.

Add includePaths to sass options.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -8,6 +8,7 @@
     sass:
       options:
         sourceMap: true
+        includePaths: ['bower_components']
       dist:
         files: 'build/main.css': 'stylesheets/main.sass'


update Grunt config: generate .js files into `js/` directory

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -16,7 +16,7 @@
             ext: '.js'
             src: ['**/*.coffee']
             cwd: 'lib'
-            dest: 'lib'
+            dest: 'js/lib'
           }
         ],
       test:
@@ -26,13 +26,12 @@
             ext: '.js'
             src: ['**/*.coffee']
             cwd: 'test'
-            dest: 'test'
+            dest: 'js/test'
           }
         ],

     clean:
-      lib: ["lib/**/*.{js,js.map,map}"]
-      test: ["test/**/*.{js,js.map,map}"]
+      js: ["js/**"]

     simplemocha:
       options:
@@ -41,16 +40,15 @@
         ignoreLeaks: false
         ui: 'bdd'
         reporter: 'spec'
-        compilers: 'coffee:coffee-script'
       all:
-        src: "test/**/*.js"
+        src: "js/test/**/*.js"

     watch:
       coffee:
         files: "lib/**/*.coffee",
         tasks: ['coffee']
       test:
-        files: ['**/*.coffee'],
+        files: ['lib/**/*.coffee', 'test/**/*.coffee'],
         tasks: ['simplemocha']

   grunt.registerTask 'build', ['clean', 'coffee']


addes a few jslove comments - fixes #31

addes a few jslove comments - fixes #31

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,6 +15,7 @@
 					'./dist_test/'
 					'./.temp/'
 				]
+			# Used for those that desire plain old JavaScript.
 			jslove:
 				src: [
 					'**/*.coffee'
@@ -41,6 +42,7 @@
 					# Don't include a surrounding Immediately-Invoked Function Expression (IIFE) in the compiled output.
 					# For more information on IIFEs, please visit http://benalman.com/news/2010/11/immediately-invoked-function-expression/
 					bare: true
+			# Used for those that desire plain old JavaScript.
 			jslove:
 				files: [
 					cwd: './'
@@ -356,6 +358,7 @@
 	]

 	# Compiles all CoffeeScript files in the project to JavaScript then deletes all CoffeeScript files.
+	# Used for those that desire plain old JavaScript.
 	# Enter the following command at the command line to execute this build task:
 	# grunt jslove
 	grunt.registerTask 'jslove', [


Fixing platform dependant setup

Fixing platform dependant setup

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,13 +3,15 @@
 cp = require 'child_process'
 path = require 'path'

+PYTHON_EXEC = if /^win/.test(process.platform) then 'Scripts/python3.exe' else 'bin/python'
+
 PYTHON_ENV_FOLDER = 'env'

 pythonExecSync = (args...) ->
-  cp.spawnSync.apply(cp, [path.join(PYTHON_ENV_FOLDER, 'bin/python')].concat(args))
+  cp.spawnSync.apply(cp, [path.join(PYTHON_ENV_FOLDER, PYTHON_EXEC)].concat(args))

 pythonExec = (args...) ->
-  cp.spawn.apply(cp, [path.join(PYTHON_ENV_FOLDER, 'bin/python')].concat(args))
+  cp.spawn.apply(cp, [path.join(PYTHON_ENV_FOLDER, PYTHON_EXEC)].concat(args))

 lazyCopyFile = (source, target, callback) ->
@@ -94,6 +96,7 @@
       throw new TypeError('expected list of objects or strings, or object')

   grunt.registerTask 'install-virtualenv', 'Install a python virtual environment', ->
+# TODO Check return code for virtualenv installation and report errors
     cp.spawnSync 'python3', ['-m', 'pip', 'install', 'virtualenv']
     cp.spawnSync 'python3', ['-m', 'virtualenv', PYTHON_ENV_FOLDER]


Do not attempt to download selenium jar if it already exists

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -171,9 +171,10 @@

     'curl-dir':
       downloadfiles:
-        src: [
-          "http://selenium-release.storage.googleapis.com/#{seleniumMajorVersion}/selenium-server-standalone-#{seleniumMajorVersion}.#{seleniumMinorVersion}.jar"
-        ],
+        src: (->
+          jarName = "selenium-server-standalone-#{seleniumMajorVersion}.#{seleniumMinorVersion}.jar";
+          if grunt.file.exists("lib/" + jarName) then [] else ["http://selenium-release.storage.googleapis.com/#{seleniumMajorVersion}/" + jarName]
+        )()
         dest: 'lib'

     parallel_spec_runner:


Docs parser: fix events names.

Docs parser: fix events names.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -242,7 +242,7 @@
 				text = text.replace /^@class\s+/mg, ''

 			title = null
-			m = /@event ([\w\|]+)/.exec text  # Event
+			m = /@event ([\w\.]+)/.exec text  # Event
 			if m
 				title = m[1]
 				text = text.replace /^@event.*$/mg, ''


Improve build

Improve build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,7 +3,7 @@

 module.exports = (grunt) ->
   pkg = grunt.file.readJSON 'package.json'
-
+
   # Parts of the index we wish to replace on deploy
   replaceMap = {}
   replaceMap[pkg.paths[0] + '/'] = "/"
@@ -20,8 +20,10 @@
     open: false
     copyIgnore: ['!script/**/*.js', '!script/{countries}']

-  webpackPlugins = [ new webpack.optimize.CommonsChunkPlugin("vtex-phone.js") ]
-  webpackPlugins = []
+  webpackPlugins = [
+    new webpack.optimize.UglifyJsPlugin(),
+    new webpack.BannerPlugin('front-phone - v'+pkg.version+' - https://vtex.github.io/front.phone/', {entryOnly: true})
+  ]

   # Add custom configuration here as needed
   customConfig =
@@ -63,10 +65,10 @@
       options:
         module:
           loaders: [
-            { test: /\.coffee$/, loader: "coffee-loader" }
-            { test: /\.less$/, loader: "style!css!less" }
-            { test: /\.png$/, loader: "url-loader?limit=100000" }
-            { test: /\.jpg$/, loader: "file-loader" }
+            { test: /\.coffee$/, loader: "coffee-loader", exclude: /node_modules/ }
+            { test: /\.less$/, loader: "style!css!less", exclude: /node_modules/ }
+            { test: /\.png$/, loader: "url-loader?limit=100000", exclude: /node_modules/ }
+            { test: /\.jpg$/, loader: "file-loader", exclude: /node_modules/ }
           ]
         devtool: "source-map"
       main:
@@ -98,4 +100,4 @@
   grunt.config.init defaultConfig
   grunt.config.merge customConfig
   grunt.loadNpmTasks name for name of pkg.devDependencies when name[0..5] is 'grunt-' and name isnt 'grunt-vtex'
-  grunt.registerTask taskName, taskArray for taskName, taskArray of tasks+  grunt.registerTask taskName, taskArray for taskName, taskArray of tasks


Add my flavor

Add my flavor

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -53,6 +53,13 @@
           stderr: true
           failOnError: true

+    release:
+      options:
+        bump: false
+        add: false
+        push: false
+        tagName: "v<%= version %>"
+
   grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-shell')
   grunt.loadNpmTasks('grunt-coffeelint')


updated grunt bump config

updated grunt bump config

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,8 +22,8 @@
             createTag: true,
             tagName: 'v%VERSION%',
             tagMessage: 'Version %VERSION%',
-            push: false,
-            pushTo: 'upstream',
+            push: true,
+            pushTo: 'origin',
             gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d',
             globalReplace: false


Switch to grunt-release.

Switch to grunt-release.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -38,18 +38,7 @@
                 options:
                     reporter: 'spec'

-    @registerTask 'npmPack', 'Create NPM package.', ->
-        done = @async()
-
-        grunt.util.spawn
-            cmd: 'npm'
-            args: ['pack']
-        , (error, result, code) ->
-            grunt.log.writeln(result.stderr) if result.stderr
-            grunt.log.writeln(result.stdout) if result.stdout
-            done(!error)
-
     @registerTask 'default', ['test']
     @registerTask 'build', ['clean', 'coffee']
-    @registerTask 'package', ['build', 'npmPack']
+    @registerTask 'package', ['build', 'release']
     @registerTask 'test', ['build', 'mkdir', 'mochacli']


feat(Gruntfile.coffee): #898 - reference multipart requesters, rename initiate requester

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -66,9 +66,11 @@
         './client/js/s3/util.js',
         './client/js/s3/uploader.basic.js',
         './client/js/s3/uploader.js',
-        './client/js/s3/policysignature.ajax.requester.js',
+        './client/js/s3/signature.ajax.requester.js',
         './client/js/s3/uploadsuccess.ajax.requester.js',
         './client/js/s3/multipart.initiate.ajax.requester.js',
+        './client/js/s3/multipart.complete.ajax.requester.js',
+        './client/js/s3/multipart.abort.ajax.requester.js',
         './client/js/s3/handler.xhr.js',
         './client/js/s3/handler.form.js',
         './client/js/s3/jquery-plugin.js'


created coffeescript task | rewrote sass task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -7,6 +7,7 @@
   grunt.loadNpmTasks 'grunt-contrib-watch'
   grunt.loadNpmTasks 'grunt-contrib-csslint'
   grunt.loadNpmTasks 'grunt-contrib-sass'
+  grunt.loadNpmTasks 'grunt-contrib-coffee'
   grunt.loadNpmTasks 'grunt-exec'
   grunt.initConfig

@@ -22,16 +23,44 @@
         bowerOptions: forceLatest:true
       } ]

+    coffee:
+      dist:
+        options: [ {
+          sourceMap: false
+        } ]
+        files: [ {
+          expand: true
+          flatten: true
+          cwd: '_assets/js/'
+          src: ['*.coffee']
+          dest: '_site/dist/js/'
+          ext: '.js'
+        } ]
+
     sass:
-      dist: files: [ {
-        # TODO
-        # documentation is incorrect
-        # https://github.com/gruntjs/grunt-contrib-sass
-        # current solution is to hard code file path
-         '_site/assets/custom.css': '_assets/stylesheets/custom.sass'
+      dist:
+        options: [ {
+          sourcemap: 'none'
+          style: 'compressed'
+          bundleExec: true
+        } ]
+        files: [ {
+          expand: true
+          cwd: '_assets/css/'
+          src: ['*.sass']
+          dest: '_site/dist/css/'
+          ext: '.css'
+        } ]
+
+    copy:
+      image: files: [ {
+        stdout: false
+        expand: true
+        cwd: '_assets/img/'
+        src: '*'
+        dest: 'dist/img/'
       } ]

-    copy:
       jquery: files: [ {
         stdout: false
         expand: true
@@ -39,15 +68,6 @@
         src: 'jquery.min.js'
         dest: 'vendor/js/'
       } ]
-
-      js: files: [ {
-        stdout: false
-        expand: true
-        cwd: '_assets/javascripts/'
-        src: '*.js'
-        dest: '_site/assets/'
-      } ]
-

       simpleSearch: files: [ {
         stdout: false
@@ -125,9 +145,9 @@
         files: '_assets/**/*.sass'
         tasks: [ 'sass' ]

-      js:
-        files: '_assets/**/*.js'
-        tasks: [ 'copy:js' ]
+      coffee:
+        files: '_assets/**/*.coffee'
+        tasks: [ 'coffee' ]

       source:
         files: [
@@ -135,7 +155,7 @@
           '_includes/**/*'
           '_layouts/**/*'
           '_posts/**/*'
-          '_assets/images/*'
+          '_assets/img/*'
           '_config.yml'
           '*.html'
           '*.md'
@@ -153,6 +173,8 @@
     'bower'
     'copy'
     'exec:jekyll'
+    'sass'
+    'coffee'
   ]
   grunt.registerTask 'serve', [
     'build'


copy bower components

copy bower components

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -201,6 +201,7 @@
           src: [
             "_locales/{,*/}*.json"
             "css/lib/*.css"
+            "components/*.*"
             "fonts/*.*"
             "images/*.png"
             "scripts/lib/*.js"


Don't use development mode for grunt-noflo-browser

Seems to mess up .coffee -> .js conversions for components

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -25,7 +25,6 @@
     noflo_browser:
       build:
         options:
-          development: true
           debug: true
         files:
           'browser/noflo-browser-app.js': ['component.json']


Add watcher for landing page coffeescript

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -13,7 +13,7 @@

     watch:
       coffee:
-        files: ['coffee/odometer.coffee', 'sass/*']
+        files: ['coffee/odometer.coffee', 'coffee/landing-page.coffee', 'sass/*']
         tasks: ["coffee", "uglify", "compass"]

     uglify:


Removed Gruntfile

Removed Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,164 +0,0 @@
-"use strict"
-lrSnippet = require("grunt-contrib-livereload/lib/utils").livereloadSnippet
-mountFolder = (connect, dir) ->
-  connect.static require("path").resolve(dir)
-
-module.exports = (grunt) ->
-
-  # load all grunt tasks
-  require("matchdep").filterDev("grunt-*").forEach grunt.loadNpmTasks
-
-  # configurable paths
-  yeomanConfig =
-    app: "app"
-    dist: "dist"
-
-  grunt.initConfig
-    yeoman: yeomanConfig
-    watch:
-      coffee:
-        files: ["<%= yeoman.app %>/scripts/**/*.coffee"]
-        tasks: ["coffee:dist"]
-
-      coffeeTest:
-        files: ["test/spec/*.coffee"]
-        tasks: ["coffee:test"]
-
-      compass:
-        files: ["<%= yeoman.app %>/styles/*.{scss,sass}"]
-        tasks: ["compass"]
-
-      livereload:
-        files: ["<%= yeoman.app %>/*.html", "{.tmp,<%= yeoman.app %>}/styles/*.css", "{.tmp,<%= yeoman.app %>}/scripts/**/*.js", "<%= yeoman.app %>/images/*.{png,jpg,jpeg}"]
-        tasks: ["livereload"]
-
-    connect:
-      options:
-        port: 9000
-        hostname: ""
-
-      livereload:
-        options:
-          middleware: (connect) ->
-            [lrSnippet, mountFolder(connect, ".tmp"), mountFolder(connect, "app")]
-
-      test:
-        options:
-          middleware: (connect) ->
-            [mountFolder(connect, ".tmp"), mountFolder(connect, "test")]
-
-      dist:
-        options:
-          middleware: (connect) ->
-            [mountFolder(connect, "dist")]
-
-    clean:
-      dist: [".tmp", "<%= yeoman.dist %>/*"]
-      server: ".tmp"
-
-    coffee:
-      options:
-        sourceMap: yes
-        sourceRoot: ""
-
-      dist:
-        files: [
-          expand: yes
-          cwd: "<%= yeoman.app %>/scripts/"
-          src: "**/*.coffee"
-          dest: ".tmp/scripts"
-          ext: '.js'
-        ]
-
-      test:
-        files: [
-          expand: true
-          cwd: ".tmp/spec"
-          src: "*.coffee"
-          dest: "test/spec"
-        ]
-
-    compass:
-      options:
-        sassDir: "<%= yeoman.app %>/styles"
-        cssDir: ".tmp/styles"
-        imagesDir: "<%= yeoman.app %>/images"
-        javascriptsDir: "<%= yeoman.app %>/scripts"
-        fontsDir: "<%= yeoman.app %>/styles/fonts"
-        importPath: "app/components"
-        relativeAssets: yes
-
-      dist: {}
-      server:
-        options:
-          debugInfo: true
-
-    uglify:
-      dist:
-        files:
-          "<%= yeoman.dist %>/scripts/main.js": [".tmp/scripts/*.js", "<%= yeoman.app %>/scripts/*.js"]
-
-    useminPrepare:
-      html: "<%= yeoman.app %>/index.html"
-      options:
-        dest: "<%= yeoman.dist %>"
-
-    usemin:
-      html: ["<%= yeoman.dist %>/*.html"]
-      css: ["<%= yeoman.dist %>/styles/*.css"]
-      options:
-        dirs: ["<%= yeoman.dist %>"]
-
-    imagemin:
-      dist:
-        files: [
-          expand: true
-          cwd: "<%= yeoman.app %>/images"
-          src: "*.{png,jpg,jpeg}"
-          dest: "<%= yeoman.dist %>/images"
-        ]
-
-    cssmin:
-      dist:
-        files:
-          "<%= yeoman.dist %>/styles/main.css": [".tmp/styles/*.css", "<%= yeoman.app %>/styles/*.css"]
-
-    htmlmin:
-      dist:
-        options: {}
-
-        #removeCommentsFromCDATA: true,
-        #                    // https://github.com/yeoman/grunt-usemin/issues/44
-        #                    //collapseWhitespace: true,
-        #                    collapseBooleanAttributes: true,
-        #                    removeAttributeQuotes: true,
-        #                    removeRedundantAttributes: true,
-        #                    useShortDoctype: true,
-        #                    removeEmptyAttributes: true,
-        #                    removeOptionalTags: true
-        files: [
-          expand: true
-          cwd: "<%= yeoman.app %>"
-          src: "*.html"
-          dest: "<%= yeoman.dist %>"
-        ]
-
-    copy:
-      dist:
-        files: [
-          expand: true
-          dot: true
-          cwd: "<%= yeoman.app %>"
-          dest: "<%= yeoman.dist %>"
-          src: ["*.{ico,txt}", ".htaccess"]
-        ]
-
-  grunt.renameTask "regarde", "watch"
-
-  grunt.registerTask "server", (target) ->
-    return grunt.task.run(["connect:dist:keepalive"])  if target is "dist"
-    grunt.task.run ["clean:server", "coffee:dist", "compass:server", "livereload-start", "connect:livereload", "watch"]
-
-  grunt.registerTask "test", ["clean:server", "coffee", "compass", "connect:test", "mocha"]
-  grunt.registerTask "build", ["clean:dist", "jshint", "test", "coffee", "compass:dist", "useminPrepare", "imagemin", "cssmin", "htmlmin", "concat", "uglify", "copy", "usemin"]
-  grunt.registerTask "default", ["build"]


add 'grunt test' command

add 'grunt test' command

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -39,3 +39,4 @@

   grunt.registerTask 'default', ['jshint', 'qunit', 'concat', 'uglify', 'jquerymanifest']
   grunt.registerTask 'build', ['default']
+  grunt.registerTask 'test', ['jshint', 'qunit']


check in the first draft of the parasiting functionality for AUX

--- library/membrane/auxes.coffee
+++ library/membrane/auxes.coffee
@@ -56,6 +56,56 @@
     # Once inherited from, the inheritee is not abstract anymore.
     @abstract yes

+    # Register current service that invokes the method as parasite.
+    # This means that the service will be automatically included to
+    # every services as an auxilliary, if it satisfies the condition.
+    # The argument has to be a key/value signature, where the key is
+    # the variable name that will be used for the aux service and the
+    # value is a function that evaluates inclusions conditions every
+    # time when any auxilliary-powered services is being executed.
+    @parasite: (signature) ->
+        vector = Auxiliaries.$parasites ?= Array()
+        return vector unless arguments.length >= 1
+        identity = try this?.identify?().toString?()
+        abused = "an argument has to be a key/value"
+        incorrect = "got incorrect decision function"
+        assert _.isObject(signature or null), abused
+        assert token = try _.first _.keys(signature)
+        assert decides = _.first _.values(signature)
+        assert _.isFunction(decides or 0), incorrect
+        notZombie = "not a zombie child: #{identity}"
+        notScreen = "has no visual core: #{identity}"
+        assert this.derives(Screenplay), notScreen
+        assert this.derives(Zombie), notZombie
+        return vector.push # append parasite
+            token: token.toString()
+            target: this or null
+            decides: decides
+
+    # A part of the internal implementation of the auxilliaries. It
+    # takes care of revewing and connecting the parasiting peers, by
+    # polling the registered parasites and seeing if conditions fit.
+    # Please see the implementation for understanding the detailing
+    # on how the parasiting functionality is implemented and works.
+    reviewParasites: (seeds, request, callback) ->
+        assert parasites = try Auxiliaries.parasite()
+        selfomit = (par) => par.target is @constructor
+        assert parasites = _.reject parasites, selfomit
+        assert _.isObject polygone = _.clone seeds or {}
+        assert _.isFunction(callback), "got no callback"
+        ask = (xi, fun) => xi.decides this, request, fun
+        log = (xi) => logger.debug inf, idc(xi), hosting
+        idc = (xi) => try xi.target.identify().underline
+        inf = "Parasiting %s service into %s hosting".blue
+        hosting = try @constructor.identify().underline
+        async.filter parasites or [], ask, (results) =>
+            _.each results or new Object(), log # notify
+            assert tokens = _.map results, (r) -> r.token
+            assert targets = _.map results, (r) -> r.target
+            assert polished = try _.object(tokens, targets)
+            assert merged = try _.merge polygone, polished
+            return callback polygone or new Object()
+
     # A hook that will be called once the Connect middleware writes
     # off the headers. Please refer to this prototype signature for
     # information on the parameters it accepts. Beware, this hook
@@ -63,18 +113,20 @@
     # Please be sure invoke the `next` arg to proceed, if relevant.
     headers: (request, response, resource, domain, next) ->
         assert auxiliaries = @constructor.aux() or {}
-        hosting = try @constructor.identify().underline
-        mapper = (closure) -> _.map auxiliaries, closure
-        routines = mapper (value, key) -> (callback) ->
-            assert _.isObject singleton = value.obtain()
-            message = "Cascading headers from %s to %s @ %s"
-            headers = singleton.downstream headers: ->
-                identity = value.identify().underline
-                template = [hosting, identity, key]
-                logger.debug message.grey, template...
-                assert singleton is this; callback()
-            headers request, response, resource, domain
-        return async.series routines, next
+        @reviewParasites auxiliaries, request, (poly) =>
+            assert auxiliaries = poly # replace the vector
+            hosting = try @constructor.identify().underline
+            mapper = (closure) -> _.map auxiliaries, closure
+            routines = mapper (value, key) -> (callback) ->
+                assert _.isObject singleton = value.obtain()
+                message = "Cascading headers from %s to %s @ %s"
+                headers = singleton.downstream headers: ->
+                    identity = value.identify().underline
+                    template = [hosting, identity, key]
+                    logger.debug message.grey, template...
+                    assert singleton is this; callback()
+                headers request, response, resource, domain
+            return async.series routines, next

     # This server side method is called on the context prior to the
     # context being compiled and flushed down to the client site. The
@@ -83,17 +135,19 @@
     # Pay attention that most implementations side effect the context.
     prelude: (symbol, context, request, next) ->
         assert auxiliaries = @constructor.aux() or {}
-        context.externals.push _.keys(auxiliaries)...
-        mapper = (closure) -> _.map auxiliaries, closure
-        routines = mapper (value, key) => (callback) =>
-            assert _.isObject singleton = value.obtain()
-            assert _.isObject ecc = context.caching ?= {}
-            assert _.isString qualified = "#{symbol}.#{key}"
-            assembler = singleton.assembleContext.bind singleton
-            stock = Object nsp: qualified, caching: context.caching
-            assembler qualified, request, yes, stock, (assembled) =>
-                @mergeContexts key, context, assembled, callback
-        return async.series routines, next
+        @reviewParasites auxiliaries, request, (poly) =>
+            assert auxiliaries = poly # replace the vector
+            context.externals.push _.keys(auxiliaries)...
+            mapper = (closure) -> _.map auxiliaries, closure
+            routines = mapper (value, key) => (callback) =>
+                assert _.isObject singleton = value.obtain()
+                assert _.isObject ecc = context.caching ?= {}
+                assert _.isString qualified = "#{symbol}.#{key}"
+                assembler = singleton.assembleContext.bind singleton
+                stock = Object nsp: qualified, caching: context.caching
+                assembler qualified, request, yes, stock, (assembled) =>
+                    @mergeContexts key, context, assembled, callback
+            return async.series routines, next

     # A complementary part of the auxiliaries substem implementation.
     # This routine is invoked once a compiled context is obtained of


don't forget to call super ctor

don't forget to call super ctor

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -160,6 +160,7 @@
     # the purpose of setting up the configurations will never be
     # changed, such as the kernel self identification tokens.
     constructor: (initializer) ->
+        try super if @constructor.__super__
         assert not _.isEmpty @token = uuid.v4()
         nconf.env().argv(); @setupLoggingFacade()
         assert @package = @constructor.PACKAGE or {}


Fix usage of buildKeydownEvent helper

Fix usage of buildKeydownEvent helper

--- spec/keymap-manager-spec.coffee
+++ spec/keymap-manager-spec.coffee
@@ -702,9 +702,9 @@
       it "falls back to the non-alt key if other modifiers are combined with ALtGraph on Linux", ->
         mockProcessPlatform('linux')
         currentKeymap = require('./helpers/keymaps/linux-swiss-german')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', altGraphKey: true, ctrlKey: true})), 'ctrl-alt-g')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', altGraphKey: true, metaKey: true})), 'alt-cmd-g')
-        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', altGraphKey: true, altKey: true})), 'alt-g')
+        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', ctrlKey: true, modifierState: {AltGraph: true}})), 'ctrl-alt-g')
+        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', metaKey: true, modifierState: {AltGraph: true}})), 'alt-cmd-g')
+        assert.equal(keymapManager.keystrokeForKeyboardEvent(buildKeydownEvent({key: '@', code: 'KeyG', altKey: true, modifierState: {AltGraph: true}})), 'alt-g')

       it "uses the keymap to fix incorrect KeyboardEvent.key values when ctrlKey is true", ->
         mockProcessPlatform('linux')


Add context menu to atom-text-editor selector

--- menus/encoding-selector.cson
+++ menus/encoding-selector.cson
@@ -7,7 +7,7 @@
 ]

 'context-menu':
-  '.overlayer': [
+  'atom-text-editor': [
     'label': 'Change Encoding'
     'command': 'encoding-selector:show'
   ]


Increase concurrency to 2

Increase concurrency to 2

--- tasks/test-task.coffee
+++ tasks/test-task.coffee
@@ -34,5 +34,5 @@
       continue unless isAtomPackage(packagePath)
       queue.push(packagePath)

-    queue.concurrency = 1
+    queue.concurrency = 2
     queue.drain = -> done(passed)


Adding tests for unauthorized user

Adding tests for unauthorized user

--- test/credentials.spec.coffee
+++ test/credentials.spec.coffee
@@ -873,6 +873,17 @@
       expect(creds.cacheId.calls.length).to.equal(0)
       expect(creds.getStorage('id')).not.to.exist

+    it 'clears cache if getId fails for unauthorized user', ->
+      creds.setStorage('id', 'MYID')
+      helpers.mockResponses [
+        {data: {IdentityId: 'IDENTITY-ID'}, error: null},
+        {data: null, error: {message : 'INVALID SERVICE', code: 'NotAuthorizedException'} }
+      ]
+      helpers.spyOn(creds.webIdentityCredentials, 'refresh').andCallFake(->)
+      creds.refresh (err) -> expect(err.message).to.equal('INVALID SERVICE')
+      expect(creds.cacheId.calls.length).to.equal(0)
+      expect(creds.getStorage('id')).not.to.exist
+
     it 'does not clear cache if getId fails for authorized user', ->
       creds.setStorage('id', 'MYID')
       helpers.mockResponses [
@@ -884,6 +895,17 @@
       expect(creds.cacheId.calls.length).to.equal(0)
       expect(creds.getStorage('id')).to.exist

+    it 'clears cache if getOpenIdToken fails for unauthorized user', ->
+      creds.setStorage('id', 'MYID')
+      helpers.mockResponses [
+        {data: {IdentityId: 'IDENTITY-ID'}, error: null},
+        {data: null, error: {message : 'INVALID SERVICE', code: 'NotAuthorizedException'}}
+      ]
+      helpers.spyOn(creds.webIdentityCredentials, 'refresh').andCallFake(->)
+      creds.refresh (err) -> expect(err.message).to.equal('INVALID SERVICE')
+      expect(creds.cacheId.calls.length).to.equal(0)
+      expect(creds.getStorage('id')).not.to.exist
+
     it 'does not clear cache if getOpenIdToken fails for authorized user', ->
       creds.setStorage('id', 'MYID')
       helpers.mockResponses [
@@ -894,6 +916,17 @@
       creds.refresh (err) -> expect(err.message).to.equal('INVALID SERVICE')
       expect(creds.cacheId.calls.length).to.equal(0)
       expect(creds.getStorage('id')).to.equal('MYID')
+
+    it 'clears cache if getCredentialsForIdentity fails for unauthorized user', ->
+      delete creds.cognito.config.params.RoleArn
+      creds.setStorage('id', 'MYID')
+      helpers.mockResponses [
+        {data: {IdentityId: 'IDENTITY-ID'}, error: null},
+        {data: null, error: {message : 'INVALID SERVICE', code: 'NotAuthorizedException'}}
+      ]
+      creds.refresh (err) -> expect(err.message).to.equal('INVALID SERVICE')
+      expect(creds.cacheId.calls.length).to.equal(0)
+      expect(creds.getStorage('id')).not.to.exist

     it 'does not clear cache if getCredentialsForIdentity fails for authorized user', ->
       delete creds.cognito.config.params.RoleArn


Renamed some methods regarding the namig convention

--- assets/javascripts/ember-couchdb-attachment-adapter.coffee
+++ assets/javascripts/ember-couchdb-attachment-adapter.coffee
@@ -16,7 +16,7 @@
     document = document_class.find(hash.doc_id)

     unless document.get('_data.attributes._rev') == rev
-      if @get_int_revision(document.get('_data.attributes._rev')) < @get_int_revision(rev)
+      if @getIntRevision(document.get('_data.attributes._rev')) < @getIntRevision(rev)
         document.set('_data.attributes._rev', rev)

     record.materializeAttribute("document", document)
@@ -25,7 +25,7 @@
     this._super.apply(this, arguments)

-  get_int_revision: (revision) ->
+  getIntRevision: (revision) ->
     parseInt(revision.split("-")[0])

   extract: (loader, json, type) ->


Finder: Added setReadOnly method to stop file tree interactions.

--- client/finder/filetree/controllers/findercontroller.coffee
+++ client/finder/filetree/controllers/findercontroller.coffee
@@ -311,6 +311,9 @@
           return machineItem

+  setReadOnly: (state) -> @treeController.isReadOnly = state
+
+
   # Settings helpers
   #
   updateMountState:(uid, state)->


obtain current request type from isRequesting

--- client/lanes/components/shared/NetworkActivityOverlay.cjsx
+++ client/lanes/components/shared/NetworkActivityOverlay.cjsx
@@ -38,7 +38,7 @@
             if @state.hasError
                 errorMsg = @model.errorMessage
                 if _.isString(errorMsg) then errorMsg else "Error"
-            else if @model.requestInProgress?.method is 'GET'
+            else if @props.visible or @state.isRequesting is 'GET'
                 'Loading…'
             else
                 'Saving…'


Be smarter about choosing executables

Be smarter about choosing executables

--- metal/install_manager.coffee
+++ metal/install_manager.coffee
@@ -227,7 +227,21 @@

   launch: ->
     @set_state InstallState.RUNNING
-    require("./launcher").launch(@executables[0]).then((res) =>
+    console.log "Launching #{@game.title}, #{@executables.length} available"
+
+    # try to launch top-most executable
+    candidates = for orig_path in @executables
+      exec_path = path.normalize orig_path
+      {
+        exec_path
+        depth: exec_path.split(path.sep).length
+      }
+
+    candidates.sort (a, b) -> a.depth - b.depth
+
+    console.log "choosing #{candidates[0].exec_path} out of candidates\n #{JSON.stringify candidates}"
+
+    require("./launcher").launch(candidates[0].exec_path).then((res) =>
       console.log res
       AppActions.notify res
     ).catch((e) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -61,6 +61,7 @@
     height: 20

   equals player.distance(enemy), Point.distance(player.position(), enemy.position())
+  # =>
   </pre></code>

   @name distance


Respond bad request if email validation fails

--- servers/lib/server/handlers/validateemail.coffee
+++ servers/lib/server/handlers/validateemail.coffee
@@ -18,6 +18,8 @@
     JUser.login clientId, { username : email, password, tfcode }, (err, info) ->

       { isValid : isEmail } = JUser.validateAt 'email', email, yes
+
+      return res.status(400).send 'Bad request'  unless isEmail

       if err?.name is 'VERIFICATION_CODE_NEEDED'
         return res.status(400).send 'TwoFactor auth Enabled'


RecentFiles: Fixed ace tab title on a recent file opened.

--- client/app/Applications/StartTab.kdapplication/views/recentfileview.coffee
+++ client/app/Applications/StartTab.kdapplication/views/recentfileview.coffee
@@ -33,11 +33,8 @@

   click:(event)->

-    path = @getData()
-
-    vmName = FSHelper.getVMNameFromPath path
-    path   = FSHelper.plainPath path
-    file   = FSHelper.createFile {path, vmName, type:'file'}
+    path   = @getData()
+    file   = FSHelper.createFileFromPath path

     @loader.show()
     file.fetchContents (err, contents)=>


silly error checking

silly error checking

--- test/helpers/map_exec.coffee
+++ test/helpers/map_exec.coffee
@@ -8,7 +8,7 @@
     exec item, (err, stdout, stderr) ->
       console.log "ran: #{item}"
       console.log "got: #{stdout}"
-      if err?
+      if err
         console.log "STDERR: #{stderr}"
         console.log err
       cbk err, stdout


Fix mocha complaining about returning Promise-like object

--- services/web/test/smoke/coffee/SmokeTests.coffee
+++ services/web/test/smoke/coffee/SmokeTests.coffee
@@ -51,6 +51,7 @@
 							return done(err) if err?
 							logger.log "smoke test: finishing setup"
 							convertCookieFile done
+		return

 	after (done)->
 		logger.log "smoke test: cleaning up"
@@ -61,6 +62,7 @@
 			if err?
 				return done(err)
 			fs.unlink cookeFilePath, done
+		return

 	it "a project", (done) ->
 		logger.log "smoke test: Checking can load a project"


rename method: countMembersFromGraph -> fetchMemberCountFromGraph.

--- client/landing/app/Applications/Members.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppController.coffee
@@ -45,7 +45,7 @@
               group = KD.getSingleton('groupsController').getCurrentGroup()
               group.fetchMembersFromGraph options, callback
               if group.slug isnt 'koding'
-                group.countMembersFromGraph options, (err, count)=>
+                group.fetchMemberCountFromGraph options, (err, count)=>
                   unless err
                     @setCurrentViewNumber 'all', count
               else


Just load the semanticblock plugin, no need to do path config.

I'm not sure why this plugin was excluded, it certainly is loaded
this way by remix. Loading it as a plugin solves the naming problem
and allows minified code to work without requiring different path
configurations.

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -30,12 +30,9 @@
         # jquery-ui and bootstrap conflict in a few cases (buttons,
         # tooltip) our copy has those removed.
         jqueryui: '../../oerpub/js/jquery-ui-1.9.0.custom-aloha'
-        contenthandler: '../plugins/common/contenthandler/lib'
-        semanticblock: '../plugins/oer/semanticblock'

       map:
         '*':
-          'semanticblock/semanticblock-plugin': 'semanticblock/lib/semanticblock-plugin'
           'ui/ui': 'toolbar/toolbar-plugin'

       waitSeconds: 42
@@ -59,6 +56,7 @@
         'oer/mathcheatsheet'
         'oer/assorted'
         'ghbook/image'
+        'oer/semanticblock'
         'oer/note'
         'oer/example'
         'oer/exercise'


remove warning about archiving user's funds being removed from buckets


support named color for message attachments

--- packages/rocketchat-message-attachments/client/messageAttachment.coffee
+++ packages/rocketchat-message-attachments/client/messageAttachment.coffee
@@ -21,3 +21,10 @@

 	getImageHeight: (height) ->
 		return height or 200
+
+	color: ->
+		switch @color
+			when 'good' then return '#35AC19'
+			when 'warning' then return '#FCB316'
+			when 'danger' then return '#D30230'
+			else return @color


Psuedocode the snap functionality.

Psuedocode the snap functionality.

Next we need to figure out what to replace the psuedocode with.

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -6,7 +6,6 @@
     display.title = slot.displayName
     $scope.flowOptions = Store.flowOptions
     $scope.slot = slot
- #    $scope.newComment = ""
     $scope.volume = slot.volume
     $scope.editing = editing # $scope.editing (but not editing) is also a modal (toolbar) indicator
     $scope.mode = if editing then 'edit' else 'view'
@@ -47,6 +46,27 @@
       fullRange.l = finite(slot.segment.l, l, 0)
       fullRange.u = finite(slot.segment.u, u, 0)
       return
+
+
+    snapping = (selectionEnd) ->
+     # placeholder pseudo-code, figure out what to replace next
+     listOfAllPlacements = []
+
+     # Make a list of all the stuff that is within four pixels
+     smallPlacements = listOfAllPlacements.filter (i) -> getDistance(i) <= 4
+
+     # It's not inconcievable that we could have multiple items
+     # that are within four pixels.  Subsequently, let's sort so we
+     # can get a handle on the closest
+     smallPlacements.sort (a, b) -> getDistance(a) - getDistance(b)
+
+     # Now that everything is sorted, let's separate out the first element
+     # of the array, which should be the closest.
+     [closest, rest...] = smallPlacements
+
+     # "snap" the element by setting position to the closest
+     dragPosition = closest.position
+

     offsetPosition = (offset) ->
       return offset unless isFinite offset
@@ -612,6 +632,7 @@
             return

       dragLeft: (event) ->
+        do snapping
         @segment.l = positionOffset(event.clientX)
         if event.type != 'mousemove'
           $scope.form.position.$setDirty()


Add modeling parent variables.

Add modeling parent variables.

--- app/javascripts/router.coffee
+++ app/javascripts/router.coffee
@@ -1,35 +1,16 @@
-define ['angular', 'lodash', 'underscore.string', 'moment', 'helpers', 'image', 'resources'],
-  (ng, _, _s, moment) ->
+define ['angular', 'lodash', 'underscore.string', 'moment', 'rest', 'helpers',
+        'image', 'resources'],
+  (ng, _, _s, moment, REST) ->
     router = ng.module 'qiprofile.router', ['qiprofile.resources',
-                                            'qiprofile.helpers', 'qiprofile.image']
-
-    router.factory 'Router', ['Subject', 'Session', 'Image', 'ObjectHelper', 'DateHelper',
+                                            'qiprofile.helpers',
+                                            'qiprofile.image']
+
+    router.factory 'Router', ['Subject', 'Session', 'Image', 'ObjectHelper',
+      'DateHelper',
       (Subject, Session, Image, ObjectHelper, DateHelper) ->
         # The Subject search fields.
         SUBJECT_SECONDARY_KEY_FIELDS = ['project', 'collection', 'number']
-
-        # Formats the {where: condition} Eve REST query parameter.
-        # Each key in the condition parameters is quoted.
-        # The condition value is unquoted for numbers, quoted otherwise.
-        # This function is called for every REST request.
-        # @param params the input parameters
-        # @returns the REST condition query parameter
-        where = (params) ->
-          # @param key the request key
-          # @param value the request value
-          # @returns the formatted Eve parameter string
-          format_pair = (key, value) ->
-            if typeof value is 'number'
-               "\"#{ key }\":#{ value }"
-            else
-               "\"#{ key }\":\"#{ value }\""
-
-          # Quote keys and values.
-          paramStrs = (format_pair(pair...) for pair in _.pairs(params))
-          cond = paramStrs.join(',')
-          # Return the {where: condition} object.
-          where: "{#{ cond }}"
-
+
         # @returns a collection/subject/session title string
         subjectTitle = (session) ->
           "#{ session.subject.collection }" +
@@ -60,11 +41,35 @@
           # @param subject the subject without detail
           # @returns a promise which resolves to the subject with detail
           getSubjectDetail = (subject) ->
+            # Creates an array of {source, key, modeling} objects ordered
+            # by key within source type (scan followed by registration)
+            #
+            # @returns the [{source, key, modeling}] array
+            subjectModeling = (detail) ->
+              # The scan modeling objects.
+              scanSets = detail.scanSets
+              scanTypes = _.keys(scanSets).sort()
+              scanSetsSorted = (scanSets[key] for key in scanTypes)
+              scanMdls = (scanSet.modeling for scanSet in scanSetsSorted \
+                          when scanSet.modeling?)
+
+              # The registration modeling objects.
+              regCfgs = detail.registrationConfigurations
+              regKeys = _.keys(regCfgs).sort()
+              regCfgsSorted = (regCfgs[key] for key in regKeys)
+              regMdls = (regCfg.modeling for regCfg in regCfgsSorted \
+                          when regCfg.modeling?)
+
+              # Return the sorted scan modeling array followed by the sorted
+              # registration modeling array.
+              scanMdls.concat(regMdls)
+
             # If the subject has no detail, then complain.
             if not subject.detail
               throw new ReferenceError("#{ subject.collection }" +
                                        " Subject #{ subject.number }" +
                                        " does not reference a detail object")
+
             Subject.detail(id: subject.detail).$promise.then (detail) ->
               # Add the subject age property, if necessary.
               if detail.birthDate? and
@@ -80,26 +85,50 @@
                 # Fix the acquisition date.
                 sess.acquisitionDate = DateHelper.asMoment(sess.acquisitionDate)

+              # Add the scan type and modeling source properties.
+              for scanType, scanSet of detail.scanSets
+                scanSet.scanType = scanType
+                if scanSet.modeling?
+                  scanSet.modeling.source = scanSet
+
+              # Add the registration modeling source property.
+              for key, regCfg of detail.registrationConfigurations
+                if regCfg.modeling?
+                  regCfg.modeling.source = regCfg
+
+              # Order the modeling objects by input type and key.
+              subject.modeling = subjectModeling(detail)
+
+              # Add the modeling results session reference.
+              for mdl in subject.modeling
+                for mdlResult, i in mdl.results
+                  mdlResult.session = detail.sessions[i]
+
               # Fix the encounter dates.
               for enc in detail.encounters
                 if enc.date?
                   enc.date = DateHelper.asMoment(enc.date)
+
               # Fix the treatment dates.
               for trt in detail.treatments
-                trt.begin_date = DateHelper.asMoment(trt.begin_date)
-                trt.end_date = DateHelper.asMoment(trt.end_date)
+                trt.beginDate = DateHelper.asMoment(trt.beginDate)
+                trt.endDate = DateHelper.asMoment(trt.endDate)
+
               # Copy the detail content into the subject.
               ObjectHelper.aliasPublicDataProperties(detail, subject)
+
               # Set a flag indicating whether there is more than one
               # session.
               subject.isMultiSession = subject.sessions.length > 1
+
               # Resolve to the subject.
               subject

           if condition.detail?
             getSubjectDetail(condition)
           else
-            Subject.query(where(condition)).$promise.then (subjects) ->
+            cond = REST.where(condition)
+            Subject.query(cond).$promise.then (subjects) ->
               if not subjects.length
                 throw new ReferenceError("#{ subject.collection }" +
                                          " Subject #{ subject.number }" +
@@ -138,67 +167,67 @@
           # the given image container.
           #
           # @param container the image container
-          addImageContainerContent = (container) ->
-            # The unique container id for cacheing.
-            container.id = "#{ session.detail }.#{ container._cls }#{ container.name }"
+          # @param key the scan type or registration name
+          addImageContainerContent = (container, key) ->
+            # The unique container id for caching.
+            container.id = "#{ session.detail }.#{ container._cls }.#{ key }"
             # The container session property.
             container.session = session
             # Encapsulate the image files.
             container.images = Image.imagesFor(container)
-
+
           if not session.detail?
-            throw new ReferenceError "Subject #{ subject.number }" +
+            throw new ReferenceError "Subject #{ session.subject.number }" +
                                      " Session #{ session.number }" +
                                      " does not reference a detail object"
           Session.detail(id: session.detail).$promise.then (detail) =>
             # Copy the fetched detail into the session.
             ObjectHelper.aliasPublicDataProperties(detail, session)
-            for scan in _.values(session.scans)
-              addImageContainerContent(scan, session)
-              for reg in scan.registrations
-                addImageContainerContent(reg, session)
+            for key, scan of session.scans
+              addImageContainerContent(scan, key)
+              for key, reg of scan.registrations
+                addImageContainerContent(reg, key)
             # Resolve to the augmented session object.
             session

         # @param session the session to search
-        # @param name the scan name
+        # @param scan_type the scan type, t1 or t2
         # @return the scan object or a promise which resolves
         #   to the scan object
-        getScan: (session, name) ->
-          # Every session detail has a scan property. When the detail
-          # is fetched, the detail properties are copied into the session
-          # object. Therefore, the presence of the session scan
-          # determines whether the session detail was loaded.
+        getScan: (session, scan_type) ->
+          # Every session detail has a scan property. When the detail is
+          # fetched, the detail properties are copied into the session object.
+          # Therefore, the presence of the session scan determines whether
+          # the session detail was loaded.
           #
           # @returns whether the session detail is loaded
           isSessionDetailLoaded = ->
             session.scans?

-          # If the session detail is loaded, then find the scan or
-          # registration container based on the name parameter,
-          # which is 'scan' or the registration name.
+          # If the session detail is loaded, then find the scan or registration
+          # container based on the name parameter.
           if isSessionDetailLoaded()
-            scan = session.scans[name]
+            scan = session.scans[scan_type]
             # If no such scan, then complain.
             if not scan
               throw new ReferenceError "#{ subjectTitle(session) }" +
-                                       " does not have a #{ name } scan."
+                                       " does not have a #{ scan_type } scan."
             scan
           else
             # Load the session detail...
-            this.getSessionDetail(session).then =>
+            @getSessionDetail(session).then =>
               if not session.scans
                 throw new ReferenceError "#{ subjectTitle(session) }" +
                                          " does not have any scans."
               # ...and recurse.
-              this.getScan(session, name)
+              @getScan(session, scan_type)

         # @param scan the scan to search
         # @param name the registration name
         # @return the registration object or a promise which resolves
         #   to the registration object
         getRegistration: (scan, name) ->
-          reg = _.find(scan.registrations, (reg) -> reg.name is name)
+          reg = scan.registrations[name]
           # If no such registrations, then complain.
           if not reg
             throw new ReferenceError "#{ subjectTitle(session) }" +


help text addition to visability page  (#2889)

* add in help line for results page

add in help line for results page

* fixed the accidental R deletion

fixed the accidental R deletion

* added in help description for how to reorder workflows

added in a line of text to the workflow page help to tell project
builders how to reorder the listing of their workflows on the project
landing page.

* add in a description of ! in header of manifest file

! in the field header then the values will be shown in Talk only and
not in the classification interface. Adding in to the subject set help
info about this for project owners.

* added an explicit help line about how to swap subject sets

It still wasn’t clear to me that I didn’t need to bring the site back
to development to swap subject sets. I think an explicit line to remind
project owners/collaborators especially when they haven’t been to the
project builder in a while might be helpful. Feel free to reject if you
think the text on the visibility page is already sufficient

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -211,4 +211,5 @@

       }</PromiseRenderer>

In a live project active workflows are available to volunteers and cannot be edited. Inactive workflows can be edited if a project is live or in development.

+
On a live project, if you want to switch which subjects sets are associated with an active workflow: set the workflow to inactive, next change which subject sets are linked in the Workflow Section within the Project Builder, then return the workflow to active.


Add "live" state, add constraints on beta and launch

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -15,13 +15,23 @@

   render: ->

-
Visibility

+
Project state and visibility

       {if @state.error

{@state.error.toString()}
}

+

+
+
+                <input type="checkbox" name="live" value={true} data-json-value={true} checked={@props.project.live} disabled={@state.setting.live} onChange={(e) => @set 'live', e.target.checked} />
+                Live
+
+
+            No workflow changes can be made a "live" project.
+

+

@@ -39,10 +49,12 @@

-              <button type="button" className="standard-button" disabled={@state.setting.beta or @props.project.beta} onClick={@set.bind this, 'beta', true}>Apply for beta</button>
+              <button type="button" className="standard-button full" disabled={not @props.project.live or @state.setting.beta or @props.project.beta} onClick={@set.bind this, 'beta', true}>Apply for beta</button>{' '}

Expose this project to beta testers, pending approval.

+              {unless @props.project.live
+
Only live projects can apply for a beta.
}
               {if @props.project.beta_approved

Beta status for this project has been approved.

               else if @props.project.beta
@@ -52,10 +64,12 @@

-              <button type="button" className="standard-button" disabled={@state.setting.launch or @props.project.launch} onClick={@set.bind this, 'launch', true}>Apply for full launch</button>
+              <button type="button" className="standard-button full" disabled={not @props.project.beta_approved or @state.setting.launch or @props.project.launch} onClick={@set.bind this, 'launch', true}>Apply for full launch</button>

Expose this project to the entire Zooniverse, pending approval.

+              {unless @props.project.beta_approved
+
Only project in beta can apply for a full launch.
}
               {if @props.project.launch_approved

Beta status for this project has been approved.

               else if @props.project.launch


#641 - fixes failing unit test

#641 - fixes failing unit test

--- dkobo/koboform/static/js/sut/tests/Controller.Tests.coffee
+++ dkobo/koboform/static/js/sut/tests/Controller.Tests.coffee
@@ -306,6 +306,8 @@

   describe "Asset Editor Controller", ->
     beforeEach inject(($controller, $rootScope) ->
+      resourceStub =
+        get: sinon.spy()

       initializeController $controller, "AssetEditor", $rootScope
     )


Add some failing tests for adding new languages

--- test/humanizer.coffee
+++ test/humanizer.coffee
@@ -30,3 +30,34 @@

   it "is a named function", ->
     humanizer().name.should.equal "humanizer"
+
+  it "can add a new language", ->
+    h = humanizer(language: "cool language")
+    h.languages["cool language"] =
+      year: -> "y"
+      month: -> "mo"
+      week: -> "w"
+      day: -> "d"
+      hour: -> "h"
+      minute: -> "mi"
+      second: -> "s"
+      millisecond: -> "ms"
+    h(1000).should.equal "1 s"
+    anotherH = humanizer(language: "cool language")
+    (-> h(1000)).should.throw Error
+
+  it "can overwrite an existing language", ->
+    h = humanizer(language: "en")
+    h(1000).should.equal "1 second"
+    h.languages["en"] =
+      year: -> "y"
+      month: -> "mo"
+      week: -> "w"
+      day: -> "d"
+      hour: -> "h"
+      minute: -> "mi"
+      second: -> "s"
+      millisecond: -> "ms"
+    h(1000).should.equal "1 s"
+    anotherH = humanizer(language: "en")
+    h(1000).should.equal "1 second"


removing extra className parameter

removing extra className parameter

--- tutor/src/components/performance-forecast/info-link.cjsx
+++ tutor/src/components/performance-forecast/info-link.cjsx
@@ -34,7 +34,7 @@
   render: ->
     <Icon
       className="info-link"
-      tooltipProps={placement: 'right', className: "info-link"}
+      tooltipProps={placement: 'right'}
       tooltip={MESSAGES[@props.type]}
     />


grammar: removed extra arrays

grammar: removed extra arrays

--- grammar.coffee
+++ grammar.coffee
@@ -195,7 +195,7 @@
   ParamList: [
     o "",                                       -> []
     o "Param",                                  -> [$1]
-    o "ParamList , Param",                      -> $1.concat [$3]
+    o "ParamList , Param",                      -> $1.concat $3
   ]

   # A single parameter in a function definition can be ordinary, or a splat
@@ -265,8 +265,8 @@
   AssignList: [
     o "",                                       -> []
     o "AssignObj",                              -> [$1]
-    o "AssignList , AssignObj",                 -> $1.concat [$3]
-    o "AssignList OptComma TERMINATOR AssignObj", -> $1.concat [$4]
+    o "AssignList , AssignObj",                 -> $1.concat $3
+    o "AssignList OptComma TERMINATOR AssignObj", -> $1.concat $4
     o "AssignList OptComma INDENT AssignList OptComma OUTDENT", -> $1.concat $4
   ]

@@ -363,8 +363,8 @@
   # (i.e. comma-separated expressions). Newlines work as well.
   ArgList: [
     o "Arg",                                    -> [$1]
-    o "ArgList , Arg",                          -> $1.concat [$3]
-    o "ArgList OptComma TERMINATOR Arg",        -> $1.concat [$4]
+    o "ArgList , Arg",                          -> $1.concat $3
+    o "ArgList OptComma TERMINATOR Arg",        -> $1.concat $4
     o "INDENT ArgList OptComma OUTDENT",        -> $2
     o "ArgList OptComma INDENT ArgList OptComma OUTDENT", -> $1.concat $4
   ]
@@ -380,8 +380,7 @@
   # having the newlines wouldn't make sense.
   SimpleArgs: [
     o "Expression"
-    o "SimpleArgs , Expression",                ->
-      if $1 instanceof Array then $1.concat([$3]) else [$1].concat([$3])
+    o "SimpleArgs , Expression",                -> [].concat $1, $3
   ]

   # The variants of *try/catch/finally* exception handling blocks.
@@ -527,8 +526,8 @@
   # rules are necessary.
   Operation: [
     o "UNARY Expression",                       -> new Op $1, $2
-    o("- Expression",                           (-> new Op('-', $2)), {prec: 'UNARY'})
-    o("+ Expression",                           (-> new Op('+', $2)), {prec: 'UNARY'})
+    o "- Expression",                          (-> new Op '-', $2), prec: 'UNARY'
+    o "+ Expression",                          (-> new Op '+', $2), prec: 'UNARY'

     o "-- Expression",                          -> new Op '--', $2
     o "++ Expression",                          -> new Op '++', $2


fix type rule to not recurse forever

fix type rule to not recurse forever

thanks @maxbrunsfeld

--- grammar.coffee
+++ grammar.coffee
@@ -167,7 +167,7 @@

     type_list: -> commaSep1(@type)

-    type: -> choice(@type_name, @type_literal, "(", @type, ")")
+    type: -> choice(@type_name, @type_literal, seq("(", @type, ")"))
     type_name: -> choice(@_identifier, @qualified_ident)
     type_literal: -> choice(
       @array_type,


test for relations array

test for relations array

--- test/yayson/presenter.coffee
+++ test/yayson/presenter.coffee
@@ -85,6 +85,77 @@
             data:
               type: 'cars'
               id: '1'
+      ]
+
+  it 'should serialize relations array', ->
+    class WheelPresenter extends Presenter
+      type: 'wheels'
+      relationships: ->
+        bike: BikePresenter
+
+    class BikePresenter extends Presenter
+      type: 'bikes'
+      relationships: ->
+        wheel: WheelPresenter
+
+    wheel =[
+      {
+        id: 2
+        bike: null
+      },
+      {
+        id: 3
+        bike: null
+      }
+    ]
+
+
+    bike =
+      id: 1
+      wheel: wheel
+
+    for w in wheel
+      w.bike = bike
+
+    json = BikePresenter.toJSON(bike)
+    expect(json).to.deep.equal
+      data:
+        type: 'bikes'
+        id: '1'
+        attributes: {}
+        relationships:
+          wheel:
+            data:[
+              {
+                type: 'wheels'
+                id: '2'
+              },
+              {
+                type: 'wheels'
+                id: '3'
+              }
+            ]
+      included: [
+        {
+          type: 'wheels'
+          id: '2'
+          attributes: {}
+          relationships:
+            bike:
+              data:
+                type: 'bikes'
+              id: '1'
+        },
+        {
+          type: 'wheels'
+          id: '3'
+          attributes: {}
+          relationships:
+            bike:
+              data:
+                type: 'bikes'
+                id: '1'
+        }
       ]

   it 'should include self link', ->


Fix error on form load introduced by acf956

--- src/resources/forms/orbeon/builder/resources/cell/control-resources-editor/actions.coffee
+++ src/resources/forms/orbeon/builder/resources/cell/control-resources-editor/actions.coffee
@@ -39,6 +39,7 @@
     editor.textfield = $('<input type="text">')
     editor.checkbox  = $('<input type="checkbox">')
     editor.container = $('<div class="fb-label-editor">').append(editor.textfield).append(editor.checkbox)
+    editor.container.hide()
     $('.fb-main').append(editor.container)
     # Register event listeners
     editor.checkbox.on('click', -> labelHintEditor().textfield.focus())


reinstate key events

reinstate key events

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -368,21 +368,7 @@

   drag_events: ->
     @log "drag_events()"
-
-    # this kills dragging...
-    # $(".draggable").live 'dragstart', =>
-    #   @keys_enabled = false
-    # $(".draggable").live 'dragstop', =>
-    #   @keys_enabled = true
-
-    # and this kills all input fields...
-    # $(document).bind 'mousedown', =>
-    #   @keys_enabled = false
-    # $(document).bind 'mouseup', =>
-    #   @keys_enabled = true
-
-    # wtf.
-
+    # TODO: kill key events during drag?

   drop_events: ->
     ref = this
@@ -394,10 +380,11 @@
       false

   field_events: ->
-    $('textarea, input').bind 'blur', =>
-      @esc_enabled = true
-    $('textarea, input').bind 'focus', =>
-      @esc_enabled = false
+    @log "field_events()"
+    $('textarea, input').live 'blur', =>
+      @keys_enabled = true
+    $('textarea, input').live 'focus', =>
+      @keys_enabled = false

   modal_events: ->
     @modal_active = false
@@ -429,7 +416,7 @@
     down = 40
     escape = 27
     $(document).keydown (e) =>
-      # return unless @keys_enabled or @active_panel
+      return unless @keys_enabled
       switch e.keyCode
         when previous
           $(@active_panel).amplePanels('previous')
@@ -441,6 +428,7 @@
           @next()
         when escape
           @toggle() unless @modal_active
+      e.stopPropagation();

   tpl: (view) ->
     @tpls()[view]
@@ -467,7 +455,6 @@
           Previous
           Next

-

'
     handle: '{{ title }}'


Remove debug print

Remove debug print

--- app/assets/javascripts/oxalis/model/binary/ping_strategy_3d.coffee
+++ app/assets/javascripts/oxalis/model/binary/ping_strategy_3d.coffee
@@ -89,7 +89,6 @@

       pullQueue.push(bucket: [bucket_x, bucket_y, bucket_z, zoomStep], priority: 0)

-    console.log(pullQueue.slice(0, 3).map((a) -> a.bucket.toString()).join("   "))
     return pullQueue
     # priority 0 is highest


[runbooks] correctly display multiple results

--- app/assets/javascripts/states/runbooks/views/run_command.js.coffee
+++ app/assets/javascripts/states/runbooks/views/run_command.js.coffee
@@ -29,10 +29,13 @@

       @ui.run.addClass("disabled")
       @ui.spinner.show().addClass("fa-spin")
+      @$("div.results").html("")
       command.run hosts, args, stdin, env, (res) =>
         _.each res, (r, host_id) =>
+          clazz = "result#{host_id}"
+          @$("div.results").append("

")
           host = @hosts.get(host_id)
-          @partial B.CommandResponse, {host: host, response: r}, "div.results"
+          @partial B.CommandResponse, {host: host, response: r}, "div.#{clazz}"
         @ui.spinner.hide().removeClass("fa-spin")
         @ui.run.removeClass("disabled")


send messages to social iframe

send messages to social iframe

--- app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
@@ -15,6 +15,14 @@

         @wrap.addClass 'uploadcare-dialog-remote-iframe-wrap'
         @dialogApi.onSwitchedToMe.add @__createIframe
+
+        @dialogApi.onSwitched.add (_, switchedToMe) =>
+          @__sendMessage
+            type: 'visibility-changed'
+            visible: switchedToMe
+
+      __sendMessage: (messageObj) ->
+        @iframe?[0]?.contentWindow?.postMessage JSON.stringify(messageObj), '*'

       __createIframe: =>
         unless @iframe
@@ -38,6 +46,10 @@
             goodOrigin = nos(e.origin) is nos(@settings.socialBase)
             goodSource = e.source is @iframe?[0]?.contentWindow
             if goodOrigin and goodSource
-              message = JSON.parse e.data
-              if message.type is 'file-selected'
+              try
+                message = JSON.parse e.data
+              if message?.type is 'file-selected'
                 @dialogApi.addFiles 'url', message.url
+                @__sendMessage
+                  type: 'file-selected-received'
+                  url: message.url


Limit viewport.scale values on zoom

Limit viewport.scale values on zoom

--- app/frontend/javascripts/views/visualization-canvas-base.js.coffee
+++ app/frontend/javascripts/views/visualization-canvas-base.js.coffee
@@ -569,7 +569,12 @@

   # Override with Canvas or SVG zoom
   zoom: (value) ->
-    @viewport.scale = value
+    if value > 2
+      @viewport.scale = 2
+    else if value < 0.5
+      @viewport.scale = 0.5
+    else
+      @viewport.scale = value

   # Events Methods


WIP: websocket support

WIP: websocket support

--- drivers/javascript/net.coffee
+++ drivers/javascript/net.coffee
@@ -1405,6 +1405,191 @@
         xhr.send view
         @xhr = xhr

+
+# ### WebSocketConnection class
+#
+# This class allows web apps to connect to a websocket proxy for RethinkDb
+#
+class WebSocketConnection extends Connection
+    # We defined the default protocol in case the user doesn't specify
+    # one.
+    DEFAULT_PROTOCOL: 'ws'
+
+    # A static method used by `r.connect` to decide which kind of
+    # connection to create in a given environment. Here we check if
+    # XHRs are defined. If not, we aren't in the browser and shouldn't
+    # be using `HttpConnection`
+    @isAvailable: -> typeof WebSocket isnt "undefined"
+
+    # #### WebSocketConnection constructor method
+    #
+    constructor: (host, callback) ->
+        # A quick check to ensure we can create an `HttpConnection`
+        unless WebSocketConnection.isAvailable()
+            throw new err.RqlDriverError "WebSocket is not available in this environment"
+        # Call the superclass constructor. This initializes the
+        # attributes `@host`, `@port`, `@db`, `@authKey`, `@timeout`,
+        # `@outstandingCallbacks`, `@nextToken`, `@open`, `@closing`,
+        # and `@buffer`. It also registers the callback to be invoked
+        # once the `"connect"` event is emitted.
+        super(host, callback)
+
+        # A protocol may be supplied in the host object. If the host
+        # doesn't have a protocol key, (or is a string, which it's
+        # also allowed to be), then use the default protocol, which is
+        # 'ws'
+        protocol = if host.protocol is 'wss' then 'wss' else @DEFAULT_PROTOCOL
+
+        # Next we construct an XHR with the path to the reql query
+        # endpoint on the http server.
+        url = "#{protocol}://#{@host}:#{@port}#{host.pathname}"
+
+        @socket = new (host.provider || WebSocket)(url)
+        @socket.binaryType = "arraybuffer"
+
+        @socket.onopen = (e) =>
+            # The protocol specifies that the magic number for the
+            # version should be given as a little endian 32 bit
+            # unsigned integer. The value is given in the `proto-def`
+            # module, but it is just a random number that's unlikely
+            # to be accidentally the first few bytes of an erroneous
+            # connection on the socket.
+            version = new Buffer(4)
+            version.writeUInt32LE(protoVersion, 0)
+
+            # Since the auth key has a variable length, we need to
+            # both encode its length in the wire format (little endian
+            # 32 bit), and the bytes themselves.
+            auth_buffer = new Buffer(@authKey, 'ascii')
+            auth_length = new Buffer(4)
+            auth_length.writeUInt32LE(auth_buffer.length, 0)
+
+            # Send the protocol type that we will be using to
+            # communicate with the server. This can be either
+            # protobuf, or json. The protobuf protocol is deprecated
+            # however.
+            protocol = new Buffer(4)
+            protocol.writeUInt32LE(protoProtocol, 0)
+
+            # Write the version, auth key length, auth key, and
+            # protocol number to the socket, in that order.
+            @socket.send Buffer.concat([version, auth_length, auth_buffer, protocol])
+
+        @socket.onmessage = (e) =>
+            buf = e.data
+            # Once we receive a response, extend the current
+            # buffer with the data from the server. The reason we
+            # extend it, vs. just setting it to the value of `buf`
+            # is that this callback may be invoked several times
+            # before the server response is received in full. Each
+            # time through we check if we've received the entire
+            # response, and only disable this event listener at
+            # that time.
+            @buffer = Buffer.concat([@buffer, buf])
+
+            # Next we read bytes until we get a null byte. This is
+            # the response string from the server and should just
+            # be "SUCCESS\0". Anything else is an error.
+            for b,i in @buffer
+                if b is 0
+                    # Once we get the null byte, the server
+                    # response has been received and we can turn
+                    # off the handshake callback
+                    @socket.onmessage = null
+
+                    # Here we pull the status string out of the
+                    # buffer and convert it into a string.
+                    status_buf = @buffer.slice(0, i)
+                    @buffer = @buffer.slice(i + 1)
+                    status_str = status_buf.toString()
+
+                    # Finally, check the status string.
+                    if status_str == "SUCCESS"
+                        # Set up the `_data` method to receive all
+                        # further responses from the server. This
+                        # callback is only for the initial
+                        # connection, all future responses will be
+                        # in reply to queries.
+                        #
+                        # We wrap @_data in a function instead of
+                        # just giving it as a callback directly so
+                        # that it gets bound to the correct
+                        # `this`.
+                        @socket.onmessage = (e) => @_data(e.data)
+
+                        # Notify listeners we've connected
+                        # successfully. Notably, the Connection
+                        # constructor registers a listener for the
+                        # `"connect"` event that sets the `@open`
+                        # flag on the connection and invokes the
+                        # callback passed to the `r.connect`
+                        # function.
+                        @emit 'connect'
+                        return
+                    else
+                        # The protocol dictates that any other
+                        # string but `SUCCESS` is an error
+                        # indicating the problem, and that we
+                        # should report the error to the user.
+                        @emit 'error', new err.RqlDriverError "Server dropped connection with message: \"" + status_str.trim() + "\""
+                        return
+
+    # #### WebSocketConnection cancel method
+    #
+    # This sends a POST to the `close-connection` endpoint, so the
+    # server can deallocate resources related to the connection. This
+    # throws out any data for unconsumed cursors etc and invalidates
+    # this connection id.
+    cancel: ->
+        @socket.close()
+        super()
+
+    # #### WebSocketConnection close method
+    #
+    # Closes the websocket connection. Does nothing new or special beyond
+    # what the superclass already does. The server connection is
+    # closed in `cancel` above, which the superclass calls for us.
+    close: (varar 0, 2, (optsOrCallback, callback) ->
+        # This would simply be `super(opts, wrappedCb)`, if we were
+        # not in the varar anonymous function
+        WebSocketConnection.__super__.close.call(this, opts, cb)
+    )
+
+    # #### WebSocketConnection _writeQuery method
+    #
+    # This creates the buffer to send to the server, writes the token
+    # and data to it, then passes the data and buffer down to `write`
+    # to write the data itself to the xhr request.
+    #
+    # This differs from `TcpConnection`'s version of `_writeQuery`
+    # which only writes the token.
+    _writeQuery: (token, data) ->
+        # We use encodeURI to get the length of the unicode
+        # representation of the data.
+        buf = new Buffer(encodeURI(data).split(/%..|./).length - 1 + 8)
+
+        # Again, the token is encoded as a little-endian 8 byte
+        # unsigned integer.
+        buf.writeUInt32LE(token & 0xFFFFFFFF, 0)
+        buf.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4)
+
+        # Write the data out to the bufferm then invoke `write` with
+        # the buffer and token
+        buf.write(data, 8)
+        @write buf, token
+
+    # #### WebSocketConnection write method
+    #
+    # This method takes a Node Buffer (or more accurately, the Buffer
+    # class that Browserify polyfills for us) and turns it into an
+    # ArrayBufferView, then sends it over the XHR with the current
+    # connection id.
+    #
+    # Despite not starting with an underscore, this is not a public
+    # method.
+    write: (chunk, token) ->
+      @socket.send(chunk)
+
 # ## isConnection
 #
 # This is an exported function that determines whether something is a
@@ -1442,6 +1627,8 @@
         create_connection = (host, callback) =>
             if TcpConnection.isAvailable()
                 new TcpConnection host, callback
+            else if WebSocketConnection.isAvailable() and host.protocol.match(/^ws/)
+                new WebSocketConnection host, callback
             else if HttpConnection.isAvailable()
                 new HttpConnection host, callback
             else


Remove History mixin

Remove History mixin

--- app/subjects/comment-form.cjsx
+++ app/subjects/comment-form.cjsx
@@ -1,5 +1,5 @@
 React = require 'react'
-{History, Link} = require 'react-router'
+{Link} = require 'react-router'
 talkClient = require 'panoptes-client/lib/talk-client'
 NewDiscussionForm = require '../talk/discussion-new-form'
 QuickSubjectCommentForm= require '../talk/quick-subject-comment-form'
@@ -9,7 +9,9 @@

 module.exports = React.createClass
   displayName: 'SubjectCommentForm'
-  mixins: [History]
+
+  contextTypes:
+    router: React.PropTypes.object.isRequired

   componentWillMount: ->
     Promise.all([@getBoards(), @getSubjectDefaultBoard()]).then =>
@@ -31,7 +33,7 @@
     {owner, name} = @props.params
     board = createdDiscussion.board_id
     discussion = createdDiscussion.id
-    @history.pushState(null, "/projects/#{owner}/#{name}/talk/#{board}/#{discussion}")
+    @context.router.push null, "/projects/#{owner}/#{name}/talk/#{board}/#{discussion}"

   linkToClassifier: (text) ->
     [owner, name] = @props.project.slug.split('/')


Pass the config file name on the command line

This lets the treesrv installation be seperate from its config

--- treesrv.coffee
+++ treesrv.coffee
@@ -1,9 +1,20 @@
 http = require('http')
 mime = require('mime')
-config = require('./config')
 git_blob = require('./git_blob')

 ParsedRequest = require('./request').ParsedRequest
+
+# Make sure we have the right number of args
+if process.argv.length != 3
+  console.error("Usage: node treesrv.coffee config_File")
+  process.exit(1)
+
+configFile = process.argv[2]
+
+try
+  config = require('./' + configFile)
+catch e
+  config = require(configFile)

 # Used to finish an HTTP response with an error
 # Don't include user controlled strings in 'phrase'


Fix tests

Fix tests

--- tests/test_formatters.coffee
+++ tests/test_formatters.coffee
@@ -37,8 +37,8 @@
       f("img: ![](http://example.com/image_link)").should.equal('
img:
\n')

     it "should insert images previews", ->
-      f("img: ![test](http://example.com/1.jpg)").should.equal('
img:
\n')
-      f("img: ![Test][id]\n[id]: http://example.com/1.gif").should.equal('
img:
\n')
+      f("img: ![test](http://example.com/1.jpg)").should.equal('
img:
\n')
+      f("img: ![Test][id]\n[id]: http://example.com/1.gif").should.equal('
img:
\n')

     it "should allow whitelisted protocols", ->
       f("[nyan](xmpp:jid@jabber.org)").should.equal('
nyan
\n')


Blob test: use test repository

Blob test: use test repository

--- test/test.blob.coffee
+++ test/test.blob.coffee
@@ -5,24 +5,24 @@
 utils = require "./utils"
 fixtures = require "./fixtures"

-secondCommit = fixtures.projectRepo.secondCommit
+secondCommit = fixtures.testRepo.secondCommit

 describe "Blob", ->
 	repo = null
 	blob = null

-	describe "Using the project repo...", ->
-		it "can find the wscript blob (#{secondCommit.wscriptBlob}) file in second commit", (done) ->
-			gitteh.openRepository fixtures.projectRepo.path, (err, _repo) ->
+	describe "Using the test repo...", ->
+		it "can find the README blob (#{secondCommit.readmeBlob}) file in second commit", (done) ->
+			gitteh.openRepository fixtures.testRepo.path, (err, _repo) ->
 				repo = _repo
-				repo.blob secondCommit.wscriptBlob, (err, _blob) ->
+				repo.blob secondCommit.readmeBlob, (err, _blob) ->
 					should.not.exist err
 					blob = _blob
 					blob.should.be.an.instanceof gitteh.Blob
 					done()
 		describe "#id", ->
 			it "is correct", ->
-				blob.id.should.equal secondCommit.wscriptBlob
+				blob.id.should.equal secondCommit.readmeBlob
 			it "is immutable", -> utils.checkImmutable blob, "id"
 		describe "#data", ->
 			it "is a Buffer", ->
@@ -32,4 +32,4 @@
 				shasum.update "blob #{blob.data.length}\u0000"
 				shasum.update blob.data.toString("binary")
 				shasum.digest("hex").should.equal blob.id
-			it "is immutable", -> utils.checkImmutable blob, "data"+			it "is immutable", -> utils.checkImmutable blob, "data"


http dep not required

http dep not required

--- test/webserver.coffee
+++ test/webserver.coffee
@@ -1,6 +1,5 @@
 assert = require 'assert'
 request = require 'request'
-http = require 'http'
 webserver = require "../lib/webserver.coffee"
 model = require("../lib/model.coffee")
 model.ttl = 5


Remove unneeded log related functions from repo-actions

--- app/views/repo-actions.coffee
+++ app/views/repo-actions.coffee
@@ -1,5 +1,4 @@
 `import Ember from 'ember'`
-`import { plainTextLog as plainTextLogUrl } from 'travis/utils/urls'`
 `import Job from 'travis/models/job'`
 `import config from 'travis/config/environment'`
 `import BasicView from 'travis/views/basic'`
@@ -35,20 +34,6 @@
           else
             Travis.flash(error: 'An error occured when canceling the build')

-
-    removeLog: ->
-      @popupCloseAll()
-      if @get('canRemoveLog')
-        job = @get('_job') || @get('build.jobs.firstObject')
-        job.removeLog().then ->
-          Travis.flash(success: 'Log has been successfully removed.')
-        , (xhr) ->
-          if xhr.status == 409
-            Travis.flash(error: 'Log can\'t be removed')
-          else if xhr.status == 401
-            Travis.flash(error: 'You don\'t have sufficient access to remove the log')
-          else
-            Travis.flash(error: 'An error occured when removing the log')

     cancelJob: ->
       if @get('canCancelJob')
@@ -94,44 +79,6 @@
     @get('displayRequeueJob') && @get('hasPermission')
   ).property('displayRequeueJob', 'hasPermission')

-  showDownloadLog: (->
-    @get('jobIdForLog') && (@get('isJobTab') || @get('isBuildTab'))
-  ).property('jobIdForLog', 'isJobTab', 'isBuildTab')
-
-  _job: (->
-    if id = @get('jobIdForLog')
-      store =Travis.__container__.lookup('store:main')
-      store.find('job', id)
-      store.recordForId('job', id)
-  ).property('jobIdForLog')
-
-  jobIdForLog: (->
-    job = @get('job.id')
-    unless job
-      if @get('build.jobs.length') == 1
-        job = @get('build.jobs').objectAt?(0).get?('id')
-    job
-  ).property('job.id', 'build.jobs.firstObject.id', 'build.jobs.length')
-
-  plainTextLogUrl: (->
-    if id = @get('jobIdForLog')
-      url = plainTextLogUrl(id)
-      if config.pro
-        token = @get('job.log.token') || @get('build.jobs.firstObject.log.token')
-        url += "&access_token=#{token}"
-      url
-  ).property('jobIdForLog', 'job.log.token', 'build.jobs.firstObject.log.token')
-
-  canRemoveLog: (->
-    @get('displayRemoveLog')
-  ).property('displayRemoveLog')
-
-  displayRemoveLog: (->
-    if job = @get('_job')
-      (@get('isJobTab') || (@get('isBuildTab') && @get('build.jobs.length') == 1)) &&
-        job.get('canRemoveLog') && @get('hasPermission')
-  ).property('isJobTab', 'isBuildTab', 'build.jobs.length', '_job.canRemoveLog', 'jobIdForLog', 'canRemoveLog', 'hasPermission')
-
   canCancelBuild: (->
     @get('displayCancelBuild') && @get('hasPermission')
   ).property('displayCancelBuild', 'hasPermission')


building and simulator launching from CLI

--- main.coffee
+++ main.coffee
@@ -73,7 +73,7 @@
         message

-getBundleId = ({name}) ->
+getBundleId = (name) ->
   try
     if line = readFile("iOS/iOS/#{name}.xcodeproj/project.pbxproj").match /PRODUCT_BUNDLE_IDENTIFIER = (.+);/
       line[1]
@@ -201,11 +201,39 @@
         ]
       ]

+    testId = readFile("#{projName}.xcodeproj/project.pbxproj")
+      .match(new RegExp "([0-9A-F]+) \/\\* #{projName}Tests \\*\/ = \\{")[1]
+
+    editSync \
+      "#{projName}.xcodeproj/xcshareddata/xcschemes/#{projName}.xcscheme",
+      [
+        [
+          /\<Testables\>\n\s*\<\/Testables\>/
+          """
+          <Testables>
+             <TestableReference
+                skipped = "NO">
+                <BuildableReference
+                   BuildableIdentifier = "primary"
+                   BlueprintIdentifier = "#{testId}"
+                   BuildableName = "#{projName}Tests.xctest"
+                   BlueprintName = "#{projName}Tests"
+                   ReferencedContainer = "container:#{projName}.xcodeproj">
+                </BuildableReference>
+             </TestableReference>
+          </Testables>
+          """
+        ]
+      ]
+
     log 'Creating Natal config'
     process.chdir '../..'
-    writeConfig
+    config =
       name:   projName
       device: pluckUuid getDeviceList().find (line) -> /iPhone 6/.test line
+
+    writeConfig config
+    launch config

     log '\nWhen Xcode appears, click the play button to run the app on the simulator.', 'yellow'
     log 'Then run the following for an interactive workflow:', 'yellow'
@@ -233,6 +261,24 @@
         message

+launch = ({name, device}) ->
+  log 'Building Xcode project'
+  try
+    execSync "
+             xcodebuild
+             -workspace iOS/iOS/#{name}.xcworkspace
+             -scheme #{name}
+             -destination platform='iOS Simulator',OS=latest,id='#{device}'
+             clean test
+             ", stdio: 'ignore'
+
+    log 'Launching simulator'
+    execSync "xcrun simctl launch #{device} #{getBundleId name}", stdio: 'ignore'
+
+  catch {message}
+    logErr message
+
+
 openXcode = (name) ->
   try
     execSync "open iOS/iOS/#{name}.xcworkspace", stdio: 'ignore'


Default to 60 size rectangles while we experiment

--- main.coffee
+++ main.coffee
@@ -1,4 +1,4 @@
-[rectWidth, rectHeight] = [20, 20]
+[rectWidth, rectHeight] = [60, 60]

 # Initialised by reset().
 checkDoneInterval = null


Animated lines.

Animated lines.

--- main.coffee
+++ main.coffee
@@ -54,19 +54,6 @@
             .attr("stroke", "blue")
             .attr("stroke-width", 2)
             .attr("fill", "none");
-
-    circx: (X, Y)->
-        @plot.selectAll('circle')
-            .data([{x:X, y:Y, r:100}])
-            .enter()
-            .append('circle')
-            .attr('cx', (d) -> d.x)
-            .attr('cy', (d) -> d.y)
-            .attr('r', (d) -> d.r)
-            .transition()
-            .delay(500)
-            .attr('r', 50)
-            #    .remove()

     circ: (m)->
         @plot.insert('circle')
@@ -82,7 +69,36 @@
             .style('stroke-opacity', 1e-6)
             .remove()

-
+    hline: (m)->
+        @plot.insert('line')
+            .attr('x1', m[0])
+            .attr('y1', m[1])
+            .attr('x2', width)
+            .attr('y2', m[1])
+            .style('stroke', d3.hsl(i = (i + 1) % 360, 1, .5))
+            .style('stroke-opacity', 1)
+            .transition()
+            .duration(2000)
+            .ease(Math.sqrt)
+            .attr('x1', width)
+            .style('stroke-opacity', 1e-6)
+            .remove()
+
+    vline: (m)->
+        @plot.insert('line')
+            .attr('x1', m[0])
+            .attr('y1', m[1])
+            .attr('x2', m[0])
+            .attr('y2', height)
+            .style('stroke', d3.hsl(i = (i + 1) % 360, 1, .5))
+            .style('stroke-opacity', 1)
+            .transition()
+            .duration(2000)
+            .ease(Math.sqrt)
+            .attr('y1', height)
+            .style('stroke-opacity', 1e-6)
+            .remove()
+

     initAxes: ->
         @xScale = d3.scale.linear() # sim units -> screen units
@@ -281,10 +297,11 @@
         Canvas.clear()
         @sun.emit()
         @h.update(count)
-        console.log("???", [c.x, c.y]) for c in colPos
+        #console.log("???", [c.x, c.y]) for c in colPos
         #colPos = []
         #console.log "???", colPos[0]
-        @p.circ([colPos[0].x,colPos[0].y])
+        @p.hline([colPos[0].x,colPos[0].y])
+        @p.vline([colPos[0].x,colPos[0].y])
         #@p.circ([c.x,c.y]) for c in colPos
         #colPos = []
         colPos.pop() for c in colPos


hook notifications handler up to make request which doesn't make request if url not set

--- services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
+++ services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
@@ -6,7 +6,7 @@

 makeRequest = (opts, callback)->
 	if !settings.apis.notifications?.url?
-		return callback()
+		return callback(null, statusCode:200)
 	else
 		request(opts, callback)

@@ -18,7 +18,7 @@
 			json: true
 			timeout: oneSecond
 			method: "GET"
-		request opts, (err, res, unreadNotifications)->
+		makeRequest opts, (err, res, unreadNotifications)->
 			statusCode =  if res? then res.statusCode else 500
 			if err? or statusCode != 200
 				e = new Error("something went wrong getting notifications, #{err}, #{statusCode}")
@@ -40,7 +40,7 @@
 				templateKey:templateKey
 			}
 		logger.log opts:opts, "creating notification for user"
-		request opts, callback
+		makeRequest opts, callback

 	markAsReadWithKey: (user_id, key, callback)->
 		opts =
@@ -51,7 +51,7 @@
 				key:key
 			}
 		logger.log user_id:user_id, key:key, "sending mark notification as read with key to notifications api"
-		request opts, callback
+		makeRequest opts, callback

 	markAsRead: (user_id, notification_id, callback)->
@@ -60,4 +60,4 @@
 			uri: "#{settings.apis.notifications?.url}/user/#{user_id}/notification/#{notification_id}"
 			timeout:oneSecond
 		logger.log user_id:user_id, notification_id:notification_id, "sending mark notification as read to notifications api"
-		request opts, callback
+		makeRequest opts, callback


drvr tmp var

drvr tmp var

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -16,7 +16,7 @@

 	describe 'Init', ->

-		@timeout 38000
+		@timeout 12000

 		beforeEach ->
 			@driver.get 'http://127.0.0.1/'
@@ -26,16 +26,18 @@
 			@timeout 4000, done()

 		it 'is the name of the default list', (done) ->
-			@driver.wait((()-> @driver.executeScript('return document.readyState')), 10000).then ()->
-				text = @driver.findElement(id: 'listName').getText()
+			drvr = @driver
+			@driver.wait((()-> drvr.executeScript('return document.readyState')), 10000).then ()->
+				text = drvr.findElement(id: 'listName').getText()
 				expect(text).to.eventually.equal 'Public list 1'
-				@timeout 10000, done()
+				@timeout 4000, done()

 		it 'is the length of the default list items = 2', (done) ->
-			@driver.wait((()-> @driver.executeScript('return document.readyState')), 10000).then ()->
-				lis = @driver.findElement(id: 'todo-list').findElements(tagName: 'li')
+			drvr = @driver
+			@driver.wait((()-> drvr.executeScript('return document.readyState')), 10000).then ()->
+				lis = drvr.findElement(id: 'todo-list').findElements(tagName: 'li')
 				expect(lis.size()).to.eventually.equal 2
-				@timeout 10000, done()
+				@timeout 4000, done()

 	describe 'List actions', ->


added CHANGES file to list of files

added CHANGES file to list of files

--- test/lib/fspath.coffee
+++ test/lib/fspath.coffee
@@ -51,7 +51,8 @@
   ]

   rootFiles = [
-    '.gitignore', '.npmignore', '.travis.yml', 'LICENSE', 'README.md', 'package.json'
+    '.gitignore', '.npmignore', '.travis.yml', 'LICENSE', 'CHANGES.md',
+    'README.md', 'package.json'
   ]
   rootDirs = [ '.git', 'lib', 'test', 'node_modules' ]
   rootPaths = [].concat(rootDirs).concat(rootFiles)


more tests

more tests

--- test/model.test.coffee
+++ test/model.test.coffee
@@ -40,6 +40,19 @@
         should.not.exist err
         done()

+    it 'should has `createdAt` by default', (done) ->
+      user2 = new User getFakeUserData()
+      user2.save (err) ->
+        should.not.exist err
+        user2.should.have.property 'createdAt'
+        r.db(app.config.rethinkdb.db)
+          .table(User.tableName)
+          .get(user2.id).run app.db.getConn(), (err, data) ->
+            should.not.exist err
+            data.should.have.property 'createdAt'
+            done()
+
+
     it 'should delete a model from db', (done) ->
       user2 = new User getFakeUserData()
       user2.save (err) ->
@@ -77,6 +90,5 @@
       it 'should load hasMany relation - style #2 (the shortest)', (done) ->
         User.get(user.id).with 'posts', (err, u) ->
           u.posts.should.be.an('array').with.lengthOf 3
-          # console.log 2, u.posts.length, typeOf u.posts
           should.not.exist err
           done()


remove unnecessary property

remove unnecessary property

--- src/comment.coffee
+++ src/comment.coffee
@@ -1,14 +1,14 @@
 {Node} = require './node'

 class Comment extends Node
-  constructor: ({ level, @content }) ->
+  constructor: ({ level }) ->
     super { level }
     @type = 'comment'

   @parse: (level, node) ->
     m = node.match /^<!.*$/
     if m?
-      new Comment { level, content: m[0] }
+      new Comment { level }

   write: ->
     ''


hide add/remove controls when editing

hide add/remove controls when editing

--- src/components/task-plan/homework/exercises.cjsx
+++ src/components/task-plan/homework/exercises.cjsx
@@ -12,14 +12,15 @@
 ExerciseCardMixin =
   getExerciseActions: ->
     actions = {}
-    if @isSelected?()
-      actions.exclude =
-        message: 'Remove question'
-        handler: @toggleExercise
-    else
-      actions.include =
-        message: 'Add question'
-        handler: @toggleExercise
+    unless @props.canEdit?  # canEdit means that it's already selected and can't be removed
+      if @isSelected?()
+        actions.exclude =
+          message: 'Remove question'
+          handler: @toggleExercise
+      else
+        actions.include =
+          message: 'Add question'
+          handler: @toggleExercise
     if @state?.displayFeedback
       actions['feedback-off'] =
         message: 'Hide Feedback'


fix nested contexts

fix nested contexts

--- src/context.coffee
+++ src/context.coffee
@@ -111,7 +111,7 @@
         nested_context = _.extend {}, run_context,
           output: output $item
         nested_context.current_context = nested_context
-        nested_context.cli = bind_cli nested_context, cli
+        nested_context.cli = bind_cli nested_context, ops
         fn.apply nested_context, args

       handle_exception: (e, compiled) ->


Clean up redundant code.

Clean up redundant code.

--- src/control.coffee
+++ src/control.coffee
@@ -318,9 +318,6 @@
         renderMultipleDivisions: (_path, context) ->
             if context.query.ocdId.length > 0
                 @_renderDivisions context.query.ocdId, context
-
-        _getWindowLocationPath: ->
-            new URI(window.location.toString()).segment()

         renderAddress: (municipality, street, numberPart, context) ->
             level = @_getLevel context, defaultLevel='none'
@@ -365,9 +362,8 @@
                                 position = exactMatch[0]

                         slug = position.slugifyAddress()
-                        [originalMuni, originalStreet, originalNumber] = @_getWindowLocationPath()[-3..]
-                        [muni, street, number] = slug.split('/')
-                        if originalMuni != muni
+                        newMunicipality = slug.split('/')[0]
+                        if newMunicipality != municipality
                             # If the original slug was in the wrong language, run full
                             # command cycle including URL navigation to change the URL language.
                             # For example in Finland, the slug should be in Swedish if the UI is in Swedish,


Adjusted min spend warning message

Adjusted min spend warning message

--- src/core/javascripts/directives/basket.js.coffee
+++ src/core/javascripts/directives/basket.js.coffee
@@ -67,5 +67,5 @@
         return true
       else
         AlertService.clear()
-        AlertService.add("danger", { msg: "You need to spend at least #{$scope.min_spend/100} to make a booking." })
+        AlertService.add("warning", { msg: "You need to spend at least £#{$scope.min_spend/100} to make a booking." })
         return false


Fixed bug inside attached event

Fixed bug inside attached event

--- source/paper-carousel.coffee
+++ source/paper-carousel.coffee
@@ -786,8 +786,6 @@
 		@_onResize()

 	ready: ->
-		# set vars
-		module = @
 		@itemsToAppend = []
 		@itemsToPrepend = []

@@ -795,6 +793,9 @@
 		@_createOnMoveEvent()

 	attached: ->
+		# set vars
+		module = @
+
 		# waiting for load
 		@async(->
 			setTimeout (->


Updating Atom settings

Updating Atom settings

--- files/atom/config.cson
+++ files/atom/config.cson
@@ -3,7 +3,7 @@
     saveOnBuild: true
   core:
     themes: [
-      "native-ui"
+      "atom-material-ui"
       "an-old-hope-syntax"
     ]
   editor:
@@ -89,5 +89,9 @@
       autoClose: true
   "tool-bar":
     position: "Left"
+  "tree-view":
+    hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
+    squashDirectoryNames: true
   welcome:
     showOnStartup: false


Improve the openLink function to open target="_blank" links in a new window

--- src/chaplin/views/layout.coffee
+++ src/chaplin/views/layout.coffee
@@ -98,12 +98,15 @@
       el = event.currentTarget
       $el = $(el)
       href = el.getAttribute('href') || $(el).data('href') || null
+      target = $(el).attr('target')
+

       # Link test ---------------
       hrefTest = if _.isFunction(@settings.linkTest) then @settings.linkTest(href) else @settings.linkTest
       return if href is null or
                 href is '' or
                 href.charAt(0) is '#' or
+                target == "_blank" or
                 hrefTest


Create new method for adding new participant to channel.

--- client/activity/lib/views/privatemessage/privatemessagepane.coffee
+++ client/activity/lib/views/privatemessage/privatemessagepane.coffee
@@ -432,14 +432,19 @@
         accountIds        : [participant.socialApiId]
         participantStatus : @getOptions().initialParticipantStatus

-      {channel} = kd.singleton 'socialapi'
-      channel.addParticipants options, (err, result) =>
-        if err
-          showError err
-          @autoComplete.reset()
-          return
-
-        @emit 'AddedParticipant', participant
+      @addParticipant options, participant
+
+
+  addParticipant: (options, participant) ->
+
+    {channel} = kd.singleton 'socialapi'
+    channel.addParticipants options, (err, result) =>
+      if err
+        showError err
+        @autoComplete.reset()
+        return
+
+      @emit 'AddedParticipant', participant

   viewAppended: ->


Like when likeLink clicked

Like when likeLink clicked

--- client/landing/app/CommonViews/comments/commentlistitemview.coffee
+++ client/landing/app/CommonViews/comments/commentlistitemview.coffee
@@ -132,9 +132,17 @@
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
+
     # super unless @_partialUpdated

   click:(event)->
+
+    if $(event.target).is("a.action-link")
+      if KD.isLoggedIn()
+        @getData().like (err)=>
+          if err
+            log "Something went wrong while like:", err
+
     if $(event.target).is("span.collapsedtext a.more-link")
       @$("span.collapsedtext").addClass "show"
       @$("span.collapsedtext").removeClass "hide"
@@ -189,11 +197,12 @@

         {{> @avatar}}

-          {{> @deleteLink}}

             {{> @author}}
             {{@utils.applyTextExpansions #(body), yes}}

+          {{> @deleteLink}}
+          {{> @likeLink}}{{> @likeCount}}
           <time>{{$.timeago #(meta.createdAt)}}</time>


activity: fix validator reference error

activity: fix validator reference error

--- client/activity/lib/components/channelparticipantsinputwidget/container.coffee
+++ client/activity/lib/components/channelparticipantsinputwidget/container.coffee
@@ -2,6 +2,7 @@
 View              = require './view'
 React             = require 'kd-react'
 ReactDOM          = require 'react-dom'
+validator         = require 'validator'
 immutable         = require 'immutable'
 ActivityFlux      = require 'activity/flux'
 getGroup          = require 'app/util/getGroup'


adding support for snippets back.. which resolves the shift on drag&drop, as well as allowing the snippet to be placed at the cursor position that's updated while dragging.

--- vendor/assets/javascripts/mercury/regions/editable.js.coffee
+++ vendor/assets/javascripts/mercury/regions/editable.js.coffee
@@ -56,13 +56,16 @@

     @element.bind 'dragenter', (event) =>
       return if @previewing
-      event.preventDefault()
+      event.preventDefault() unless Mercury.snippet
       event.originalEvent.dataTransfer.dropEffect = 'copy'

     @element.bind 'dragover', (event) =>
       return if @previewing
-      event.preventDefault()
+      event.preventDefault() unless Mercury.snippet
       event.originalEvent.dataTransfer.dropEffect = 'copy'
+      if jQuery.browser.webkit
+        clearTimeout(@dropTimeout)
+        @dropTimeout = setTimeout((=> @element.trigger('possible:drop')), 10)

     @element.bind 'drop', (event) =>
       return if @previewing
@@ -76,6 +79,18 @@
       event.preventDefault()
       @focus()
       Mercury.uploader(event.originalEvent.dataTransfer.files[0])
+
+    # possible:drop custom event: we have to do this because webkit doesn't fire the drop event unless both dragover and
+    # dragstart default behaviors are canceled.. but when we do that and observe the drop event, the default behavior
+    # isn't handled (eg, putting the image where it was dropped,) so to allow the browser to do it's thing, and also do
+    # our thing we have this little hack.  *sigh*
+    # read: http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
+    @element.bind 'possible:drop', (event) =>
+      return if @previewing
+      if snippet = @element.find('img[data-snippet]').get(0)
+        @focus()
+        Mercury.Snippet.displayOptionsFor(jQuery(snippet).data('snippet'))
+        @document.execCommand('undo', false, null)

     # custom paste handling: we have to do some hackery to get the pasted content since it's not exposed normally
     # through a clipboard in firefox (heaven forbid), and to keep the behavior across all browsers, we manually detect