timeRangeInputView: use utils in `_percentValues`

--- coffee/src/views/time_range_input_view.coffee
+++ coffee/src/views/time_range_input_view.coffee
@@ -147,22 +147,25 @@

   _percentValues: (vals) =>
-    range = @_max - @_min
-    fromPercent = (p) => Number (p * range / 100 + @_min).toFixed 1
-    toPercent = (v) => (v - @_min) * 100 / range
+    params = (decimalDigits) =>
+      low: @_min
+      high: @_max
+      bound: true
+      decimalDigits: decimalDigits

     if athena.lib.util.isStrictObject vals
       if vals.start?
-        vals.start = fromPercent vals.start
+        vals.start = util.fromPercent vals.start, params 1

       if vals.end?
-        vals.end = fromPercent vals.end
+        vals.end = util.fromPercent vals.end, params 1

       vals = _.defaults {}, vals, @values()
       @values vals

     vals = @values()
-    start: toPercent(vals.start), end: toPercent vals.end
+    start: util.toPercent vals.start, params()
+    end: util.toPercent vals.end, params()

   # focal point for all changes. direct and announce changes to start and/or


add some more coverage

add some more coverage

--- test/phabs_admin_test.coffee
+++ test/phabs_admin_test.coffee
@@ -193,25 +193,7 @@
                   'id': '1402',
                   'phid': 'PHID-PROJ-qhmexneudkt62wc7o3z4',
                   'name': 'Bug Report',
-                  'profileImagePHID': 'PHID-FILE-2dsjotf2zgtbludzlk4s',
-                  'icon': 'bugs',
-                  'color': 'yellow',
-                  'members': [
-                    'PHID-USER-3yc34eijivr6rqs4vgiw',
-                    'PHID-USER-7k37pmi3jffv46mzs5te'
-                  ],
-                  'slugs': [
-                    'bug_report'
-                  ],
-                  'dateCreated': '1449275954',
-                  'dateModified': '1468138110'
                 }
-              },
-              'slugMap': [ ],
-              'cursor': {
-                'limit': 100,
-                'after': null,
-                'before': null
               }
             } })
             .get('/api/maniphest.query')
@@ -240,6 +222,126 @@
             expect(room.robot.brain.data.phabricator.projects['Bug Report'].phid)
               .to.eql 'PHID-PROJ-qhmexneudkt62wc7o3z4'

+
+      context 'and is known to phabricator', ->
+        beforeEach ->
+          room.robot.brain.data.phabricator.projects = {
+            'project with phid': { phid: 'PHID-PROJ-1234567' },
+          }
+          room.robot.brain.data.phabricator.aliases = {
+            bugs: 'project with phid',
+            bug: 'project with phid'
+          }
+          do nock.disableNetConnect
+          nock(process.env.PHABRICATOR_URL)
+            .get('/api/project.query')
+            .query({
+              'names[0]': 'project1',
+              'api.token': 'xxx'
+            })
+            .reply(200, { result: {
+              'data': {
+                'PHID-PROJ-qhmexneudkt62wc7o3z4': {
+                  'id': '1402',
+                  'phid': 'PHID-PROJ-qhmexneudkt62wc7o3z4',
+                  'name': 'Bug Report',
+                }
+              }
+            } })
+            .get('/api/maniphest.query')
+            .query({
+              'projectPHIDs[0]': 'PHID-PROJ-qhmexneudkt62wc7o3z4',
+              'status': 'status-any',
+              'order': 'order-modified'
+            })
+            .reply(200, { result: {
+              'PHID-TASK-llyghhtxzgc25wbsn7lk': {
+                'id': '12',
+                'phid': 'PHID-TASK-llyghhtxzgc25wbsn7lk'
+              },
+              'PHID-TASK-lnpaaqlyvkuar5yf7qk6': {
+                'id': '13',
+                'phid': 'PHID-TASK-lnpaaqlyvkuar5yf7qk6'
+              }
+            } })
+            .get('/api/maniphest.gettasktransactions')
+            .query({
+              'ids[]': [ 12, 13 ]
+            })
+            .reply(200, { result: {
+              '12': [
+                {
+                  'taskID': '12',
+                  'transactionType': 'status',
+                  'oldValue': 'open',
+                  'newValue': 'resolved'
+                },
+                {
+                  'taskID': '12',
+                  'transactionType': 'core:columns',
+                  'oldValue': null,
+                  'newValue': [
+                    {
+                      'boardPHID': 'PHID-TASK-llyghhtxzgc25wbsn7lk',
+                      'columnPHID': 'PHID-PCOL-ikeu5quydkkw55cqlbmx'
+                    }
+                  ]
+                }
+              ],
+              '13': [
+                {
+                  'taskID': '13',
+                  'transactionType': 'status',
+                  'oldValue': 'open',
+                  'newValue': 'resolved'
+                },
+                {
+                  'taskID': '13',
+                  'transactionType': 'core:columns',
+                  'oldValue': null,
+                  'newValue': [
+                    {
+                      'boardPHID': 'PHID-TASK-llyghhtxzgc25wbsn7lk',
+                      'columnPHID': 'PHID-PCOL-ikeu5quydkkw55cqlb00'
+                    }
+                  ]
+                }
+              ]
+            } })
+            .get('/api/phid.lookup')
+            .query({
+              'names[]': [
+                'PHID-PCOL-ikeu5quydkkw55cqlbmx',
+                'PHID-PCOL-ikeu5quydkkw55cqlb00'
+              ]
+            })
+            .reply(200, { result: {
+              'PHID-PCOL-ikeu5quydkkw55cqlbmx': {
+                'phid': 'PHID-PCOL-ikeu5quydkkw55cqlbmx',
+                'name': 'Back Log'
+              },
+              'PHID-PCOL-ikeu5quydkkw55cqlb00': {
+                'phid': 'PHID-PCOL-ikeu5quydkkw55cqlb00',
+                'name': 'Done'
+              }
+             } })
+
+
+        afterEach ->
+          room.robot.brain.data.phabricator = { }
+          nock.cleanAll()
+
+        context 'phad info Bug Report', ->
+          hubot 'phad info Bug Report'
+          it 'should reply with proper info', ->
+            expect(hubotResponse())
+              .to.eql "'Bug Report' is 'Bug Report', with no alias, " +
+                      'with no feed, columns back_log, done.'
+          it 'should remember the phid from asking to phabricator', ->
+            expect(room.robot.brain.data.phabricator.projects['Bug Report'].phid)
+              .to.eql 'PHID-PROJ-qhmexneudkt62wc7o3z4'
+            expect(room.robot.brain.data.phabricator.projects['Bug Report'].columns.done)
+              .to.eql 'PHID-PCOL-ikeu5quydkkw55cqlb00'

     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     context 'when project has no phid recorded', ->


Break out private functions in the transition

--- javascripts/application.js.coffee
+++ javascripts/application.js.coffee
@@ -97,17 +97,22 @@
     this

 class View.Transition extends Backbone.View
+  distance = (from, to)->
+    from.offset()[dimension] - to.offset()[dimension] for dimension in ['left', 'top']
+
+  translate = (x, y)->
+    {translateX: "#{x}px", translateY: "#{y}px"}
+
   initialize: (options)->
     @target = options.target

   render: ->
-    left = @target.offset().left - @$el.offset().left
-    top  = @target.offset().top  - @$el.offset().top
-    @$el.animate {translateX: "#{left}px", translateY: "#{top}px"},
-      duration: 500
+    [x, y] = distance @target, @$el
+    @$el.animate translate(x, y),
+      duration: 250
       complete: =>
         @$el.remove()
-          .animate {translateX: "0px", translateY: "0px"}, duration: 0, complete: =>
+          .animate translate(0, 0), duration: 0, complete: =>
             @$el.prependTo @target

 class View.Flatware extends Backbone.View


* move method getEncoding from static/class to virtual method. * use parent getEncoding if parent exists and no encoding self

--- src/type-info.coffee
+++ src/type-info.coffee
@@ -106,9 +106,12 @@
     encode: JSON.stringify
     decode: JSON.parse
   @DEFAULT_ENCODING: @JSON_ENCODING
-  @getEncoding: (encoding)->
+  getEncoding: (encoding)->
     if !encoding or encoding is Type.DEFAULT_ENCODING.name
-      return Type.DEFAULT_ENCODING
+      if @parent
+        return @parent.getEncoding()
+      else
+        return Type.DEFAULT_ENCODING
     if isString encoding
       if Codec
         encoding = Codec encoding
@@ -123,7 +126,6 @@
         encoding should have name property, encode and decode functions.
       "
     encoding
-
   constructor: (aTypeName, aOptions)->
     return super
   initialize: (aOptions)->
@@ -137,12 +139,12 @@
   assign: (aOptions)->
     @errors = []
     if aOptions
-      @encoding = Type.getEncoding aOptions.encoding
+      @parent   = aOptions.parent if aOptions.parent
+      @encoding = @getEncoding aOptions.encoding
       @required = aOptions.required if aOptions.required?
-      @parent   = aOptions.parent if aOptions.parent
       @name = aOptions.name if aOptions.name
     else
-      @encoding = Type.getEncoding()
+      @encoding = @getEncoding()
     @_assign aOptions if @_assign
     @
   #TODO: serialize the encoding etc attributes to custom value here.


Fix error in init.coffee file in recommended usage

Closes #76

--- docs/recommended-usage/.atom/init.coffee
+++ docs/recommended-usage/.atom/init.coffee
@@ -24,13 +24,13 @@

   # Hide tree-view when package is activated
   try
-    treeView = atom.packages.getActivePackage('tree-view').mainModule.createView()
-    if treeView.isVisible()
-      treeView.toggle() # i.e. hide it
+    treeViewMainModule = atom.packages.getActivePackage('tree-view').mainModule
+    treeViewMainModule.createOrDestroyTreeViewIfNeeded()
+    treeViewMainModule.getTreeViewInstance().hide()
   catch err
     console.error(err)
     atom.notifications.addWarning 'Could not hide tree view', {
-      description: 'See the console for the error'
+      detail: 'See the console for the error'
       dismissable: true
     }


Publish does not really send data ordered so there is no need for such query.

--- server/annotation.coffee
+++ server/annotation.coffee
@@ -5,7 +5,3 @@

   Annotations.find
     'publication._id': publicationId
-  ,
-    sort: [
-      ['page', 'asc']
-    ]


ensure the brackets are included when are asserting the code was sent

--- gulp-espruino/test/gulp-espruino-spec.coffee
+++ gulp-espruino/test/gulp-espruino-spec.coffee
@@ -34,7 +34,7 @@
     serialPort = serialPortBuilder()
       .on(receive: /reset/, send: 'ESPRUINO v3.1')
       .on(receive: /echo.0./, send: 'done')
-      .on(receive: /duuuude/, send: 'done')
+      .on(receive: /{ duuuude }/, send: 'done')
       .on(receive: /echo.1./, send: 'done')
       .on(receive: /save/, send: 'Checking...\nDone!')
       .build()
@@ -52,7 +52,7 @@
     serialPort = serialPortBuilder()
       .on(receive: /reset/, send: 'ESPRUINO v3.1\n')
       .on(receive: /echo.0./, send: 'echo off\n')
-      .on(receive: /code/, send: 'code uploaded\n')
+      .on(receive: /{ code }/, send: 'code uploaded\n')
       .on(receive: /echo.1./, send: 'echo on\n')
       .on(receive: /save/, send: 'saved!')
       .build()
@@ -71,7 +71,7 @@
     serialPort = serialPortBuilder()
       .on(receive: /reset/, send: 'ESPRUINO v3.1\n')
       .on(receive: /echo.0./, send: 'echo off\n')
-      .on(receive: /code/, send: 'code uploaded\n')
+      .on(receive: /{ code }/, send: 'code uploaded\n')
       .on(receive: /echo.1./, send: 'echo on\n')
       .build()

@@ -87,7 +87,7 @@
   it 'should ignore reset when specified', (done) ->
     serialPort = serialPortBuilder()
       .on(receive: /echo.0./, send: 'echo off\n')
-      .on(receive: /code/, send: 'code uploaded\n')
+      .on(receive: /{ code }/, send: 'code uploaded\n')
       .on(receive: /echo.1./, send: 'echo on\n')
       .on(receive: /save/, send: 'saved!')
       .build()
@@ -104,7 +104,7 @@
   it 'should ignore echo when specified', (done) ->
     serialPort = serialPortBuilder()
       .on(receive: /reset/, send: 'ESPRUINO v3.1\n')
-      .on(receive: /code/, send: 'code uploaded\n')
+      .on(receive: /{ code }/, send: 'code uploaded\n')
       .on(receive: /save/, send: 'saved!')
       .build()


Sim: Find spectator correctly.

Sim: Find spectator correctly.

--- server/bw/battle_controller.coffee
+++ server/bw/battle_controller.coffee
@@ -78,7 +78,7 @@
   messageSpectators: (user, message) ->
     # Find the user in the battle just in case its masked
     # If it doesn't exist, this means its an outside user
-    userInBattle = @battle.spectators.find((s) -> s.id == user.id)
+    userInBattle = _(@battle.spectators).find((s) -> s.id == user.id)
     user = userInBattle  if userInBattle

     for spectator in @battle.spectators


Corrected setting of log level in pub/sub connect

--- src/pubsub.coffee
+++ src/pubsub.coffee
@@ -24,7 +24,7 @@
     @pingInterval = @options.pingInterval ? 15000
     @logLevel = @options.logLevel ? 'error'

-    logger.setLevel @logLevel
+    logger.setLogLevel @logLevel
     logger.info "Set logger level to '#{@logLevel}'"
     logger.info "Options based to new Pub/Sub WebSocket:\n
         #{JSON.stringify(options, null, 2)}"


Don't b0rk back button

Don't b0rk back button

--- angular/core/components/poll/common/start_form/poll_common_start_form.coffee
+++ angular/core/components/poll/common/start_form/poll_common_start_form.coffee
@@ -3,7 +3,7 @@
   restrict: 'E'
   templateUrl: 'generated/components/poll/common/start_form/poll_common_start_form.html'
   replace: true
-  controller: ($scope, AppConfig, Records, ModalService, PollCommonFormModal, PollService) ->
+  controller: ($scope, AppConfig, Records, ModalService, PollCommonStartModal, PollService) ->
     $scope.discussion = $scope.discussion or {}
     $scope.group      = $scope.group or {}

@@ -11,7 +11,7 @@
       _.keys PollService.activePollTemplates()

     $scope.startPoll = (pollType) ->
-      ModalService.open PollCommonFormModal, poll: ->
+      ModalService.open PollCommonStartModal, poll: ->
         Records.polls.build
           pollType:              pollType
           discussionId:          $scope.discussion.id


Add More Test for Response Transform

Add More Test for Response Transform

--- test/unit/config_spec.coffee
+++ test/unit/config_spec.coffee
@@ -22,30 +22,45 @@
       _data = [
         jim
         arch_enemy
-        {name: "Some Dude"}
+        {name: "Some Dude", occupation: 'Sofa Tester'}
       ]
+
+      # URL schema: `/winners/:id`
+      winnerDetailUrl = /^\/winners\/(\d+)$/

       $httpBackend.whenGET('/winners').respond (method, url, data) ->
         log "GET /winners"
         [200, _data, {}]

-    it "works", (done) ->
+      $httpBackend.whenGET(winnerDetailUrl).respond (method, url, data, headers) ->
+        id = +winnerDetailUrl.exec(url)[1]
+        log "GET /winner/#{id}"
+        [200, _data[id], {}]
+
+      $httpBackend.whenPOST(winnerDetailUrl).respond (method, url, data, headers) ->
+        id = +winnerDetailUrl.exec(url)[1]
+        log "POST /winner/#{id}"
+        [200, _data[id], {}]
+
+    Winners = null
+    beforeEach inject (Collection) ->
       Winners = Collection.new "Winners",
         transformResponse: (data) ->
           if _.isArray(data)
             _.filter data, (item) ->
-              item.name isnt "Arch Enemy"
+              item.name isnt arch_enemy.name
+          else if _.isObject(data)
+            _.extend data, {age: 42}
           else data

+    it "works with list resources", (done) ->
       winners = Winners.all()
       winners.$promise.then (response) ->
         expect(winners.all).to.have.length.above 0
         expect(winners.all).to.contain jim
         expect(winners.all).to.not.contain arch_enemy

-        expect(response.data).to.have.length.above 0
-        expect(response.data).to.contain jim
-        expect(response.data).to.not.contain arch_enemy
+        expect(response.data).to.deep.eql(winners.all)

         done(null)
       .then null, (err) ->
@@ -53,3 +68,24 @@

       tick()

+    it 'works with detail resource', (done) ->
+      winner = Winners.get(id: 1)
+      winner.$promise.then ->
+        expect(winner.data.age).to.exist
+
+        done(null)
+      .then null, (err) ->
+        done new Error JSON.stringify err
+
+      tick()
+
+    it 'works with saved resource', (done) ->
+      Winners.save(id: 1, name: "New Friend")
+      .then (winner) ->
+        expect(winner.age).to.exist
+
+        done(null)
+      .then null, (err) ->
+        done new Error JSON.stringify err
+
+      tick()


Pokemon: Some failsafes to make tests happy.

--- server/bw/pokemon.coffee
+++ server/bw/pokemon.coffee
@@ -62,22 +62,23 @@
     # a record of whether the pokemon has officially fainted or not.
     @fainted = false

-  getForme: ->
+  getForme: (newForme) ->
     availableFormes = FormeData[@name] || {}
-    availableFormes[@forme]
+    availableFormes[newForme || @forme]

   isInForme: (forme) ->
     @forme == forme

   changeForme: (newForme) ->
+    return  if !@getForme(newForme)
     @changeSprite(newForme)
     @forme = newForme
     @resetForme()

   resetForme: ->
-    forme      = @getForme()
-    @baseStats = _.clone(forme.stats)
-    @types     = _.clone(forme.types)
+    forme      = @getForme() || {}
+    @baseStats = _.clone(forme.stats) || {}
+    @types     = _.clone(forme.types) || []
     @weight    = forme.weight

   changeSprite: (newSpecies, newForme) ->


Set Content-Type of /version to text/plain

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -1076,6 +1076,7 @@
 app.get '/logout', ensureAuthenticated, logout

 app.get '/version', (req, res) ->
+  res.set 'Content-Type', 'text/plain'
   child_process.exec "git log -n1 2>&1", (err, stdout, _stderr) =>
     if err
       return res.send 500, stdout


SocialWorker: JAccount: fix coffeelint error

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1426,7 +1426,7 @@
     limit ?= 10

     JSession = require './session'
-    JSession.one { clientId : sessionToken }, (err, session) =>
+    JSession.one { clientId : sessionToken }, (err, session) ->
       return callback err  if err
       return callback new KodingError 'Invalid session.'  unless session


Moved EDDSA priv key operations to use keybase-nacl

--- src/ecc/eddsa.iced
+++ src/ecc/eddsa.iced
@@ -1,5 +1,4 @@
 kbnacl = require 'keybase-nacl'
-tweetnacl = require 'tweetnacl'
 {SlicerBuffer} = require '../openpgp/buffer'
 {uint_to_buffer} = require '../util'
 {BaseKeyPair,BaseKey} = require '../basekeypair'
@@ -106,11 +105,11 @@
     sb = new SlicerBuffer raw
     pre = sb.rem()
     key_len = sb.read_uint16()
-    if (n = key_len/8) != (m = tweetnacl.sign.seedLength)
+    if (n = key_len/8) != (m = kbnacl.sign.seedLength)
       throw new Error "Expected #{m} bytes for EDDSA priv key, got #{n}."

-    x = sb.read_buffer tweetnacl.sign.seedLength
-    { publicKey, secretKey } = tweetnacl.sign.keyPair.fromSeed(x)
+    x = sb.read_buffer kbnacl.sign.seedLength
+    { publicKey, secretKey } = kbnacl.alloc({}).genFromSeed seed: x

     if pub.key.toString('hex') != new Buffer(publicKey).toString('hex')
       # TODO: Better buffer/array comparasion
@@ -132,11 +131,15 @@
   #-------------------

   sign : (h, cb) ->
-    ret = tweetnacl.sign(h, @x)
-    # crypto_sign returns signature + the message, we want just the
+    nacl = kbnacl.alloc({ secretKey: @x })
+    ret = nacl.sign { payload: h }
+    # nacl.sign returns signature + the message, we want just the
     # signature. gpg keeps the signature as two numbers, r and s, lets
-    # keep it that way instead of one 64-byte buffer.
-    len = tweetnacl.sign.signatureLength/2
+    # keep it that way instead of one 64-byte buffer. We could use
+    # {detached} argument to sign here, but it would still return one
+    # buffer as a the signature instead of two, so we might as well
+    # split/detach ourselves.
+    len = kbnacl.sign.signatureLength/2
     cb [new Buffer(ret[0...len]), new Buffer(ret[len...len*2])]

 #=================================================================


slightly more compilated but more useful


Fill shapes.

Fill shapes.

--- src/element.coffee
+++ src/element.coffee
@@ -130,7 +130,7 @@
     render : (context) ->
         context.beginPath()
         context.arc(@x, @y, @radius, 0, Math.PI *2)
-        context.stroke()
+        context.fill()

     getAxisAlignedBoundingBox : () ->
         # FIXME: need non-axis aligned collisions for this


Whitspace clean-up.

Whitspace clean-up.

--- src/element.coffee
+++ src/element.coffee
@@ -161,5 +161,3 @@
     getAxisAlignedBoundingBox : () ->
         return this.getCorners()

-
-


Refactor post and page processing to share code and pave way for generator plugins

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -8,8 +8,9 @@
 node_static = require 'node-static'
 watch = require 'watch'
 http = require 'http'
-
-config = require './config'
+async = require 'async'
+
+configReader = require './config'

 CONFIG_FILENAME = '_config.yml'

@@ -80,252 +81,188 @@
           destination = remainderArgs[1]

         # Load configuration
-        options = config.getConfig(path.join source, CONFIG_FILENAME)
+        config = configReader.getConfig(path.join source, CONFIG_FILENAME)
         # Override config with passed variables
         if destination
-          options.destination = destination
+          config.destination = destination
         if args.server
-          options.server = true
+          config.server = true
         if args.server and args.server isnt true
-          options.server_port = args.server
+          config.server_port = args.server
         # Server activates auto
-        if args.auto or options.server
-          options.auto = true
+        if args.auto or config.server
+          config.auto = true
         # TODO: args.url

-        begin options
+        begin config

 # Workhorse function
-begin = (options) ->
+begin = (config) ->
   # Copy default filters
-  options.filters = {}
-  options.filters[key] = tinyliquid.filters[key] for key of tinyliquid.filters
-
-  options.converters = []
+  config.filters = {}
+  config.filters[key] = tinyliquid.filters[key] for key of tinyliquid.filters
+
+  config.converters = []
+  config.generators = []

   # Load bundled plugins
-  loadPlugins options, path.join __dirname, 'plugins'
-  checkDirectories options
+  loadPlugins config, path.join __dirname, 'plugins'
+  checkDirectories config
   # Load directory plugins
-  loadPlugins options
+  loadPlugins config

   # Sort converters based on priority
-  options.converters.sort (a, b) ->
+  config.converters.sort (a, b) ->
     b.priority - a.priority

-  generate options
-
-  if options.auto
+  generate config
+
+  if config.auto
     console.log "Auto-regenerating enabled".green +
-      " #{options.source} -> #{options.destination}".green
+      " #{config.source} -> #{config.destination}".green
     # Avoid infinite refreshing from watching the output directory
-    realDestination = path.resolve options.destination
+    realDestination = path.resolve config.destination
     fileFilter = (f) ->
       path.resolve(f) is realDestination
-    watch.watchTree options.source, { filter: fileFilter }, (f, curr, prev) ->
+    watch.watchTree config.source, { filter: fileFilter }, (f, curr, prev) ->
       if typeof f is 'object' and curr is null and prev is null
         # Finished walking tree, ignore
         return
       else if prev is null
         # New file
-        generateDebounced options, ->
+        generateDebounced config, ->
           console.log 'Updated due to new file'
       else if curr.nlink is 0
         # Removed file
-        generateDebounced options, ->
+        generateDebounced config, ->
           console.log 'Updated due to removed file'
       else
         # File was changed
-        generateDebounced options, ->
+        generateDebounced config, ->
           console.log 'Updated due to changed file'

     # TODO: Run server
-    if options.server
-      fileServer = new(node_static.Server)(options.destination)
+    if config.server
+      fileServer = new(node_static.Server)(config.destination)
       server = http.createServer (request, response) ->
         request.addListener 'end', ->
           fileServer.serve request, response
-      server.listen options.server_port
-      console.log "Running server at http://localhost:#{options.server_port}"
+      server.listen config.server_port
+      console.log "Running server at http://localhost:#{config.server_port}"

 lastGenerated = 0
 generateTimeoutId = 0
 wait = (timeout, f) ->
   setTimeout f, timeout
-generateDebounced = (options, callback) ->
+generateDebounced = (config, callback) ->
   now = Date.now()
   clearTimeout generateTimeoutId
   generateTimeoutId = wait 100, ->
-    generate options, ->
+    generate config, ->
       lastGenerated = Date.now()
       callback()
   return

-generate = (options, callback) ->
-  { layouts, includes } = getLayoutsAndIncludes options
-
-  console.log "Building site: #{options.source} -> #{options.destination}"
-
-  posts = getPosts options
-  # Sort on date
-  posts.sort (a, b) ->
-    b.date - a.date
-  # Create data
-  siteData = {}
-  # Add in variables from config
-  siteData[key] = options[key] for key of options
-  # Post collection
-  siteData.posts = posts
-  siteData.time = Date.now()
+generate = (config, callback) ->
+  { layouts, includes } = getLayoutsAndIncludes config
+
+  console.log "Building site: #{config.source} -> #{config.destination}"
+
+  posts = getPosts config
+  {pages, staticFiles} = getPagesAndStaticFiles config
+
+  # Create site data structure
+  siteData =
+    config: config
+    time: Date.now()
+    posts: posts
+    pages: pages
+
+  # Setup tags & categories for posts
   for type in ['tags', 'categories']
     siteData[type] = {}
     for post in posts
-      continue unless options.future or post.published
+      continue unless config.future or post.published
       if post[type]
         for val in post[type]
           siteData[type][val] or= { name: val, posts: [] }
           siteData[type][val].posts.push post

+  # TODO: Run generators
+
   liquidOptions =
     files: includes
     original: true

-  # Set next / prev on posts
-  prev = null
-  for post in posts
-    # List is in reverse-chronological order, so the last post we processed is
-    # actually the next post
-    if prev
-      post.next = prev
-      prev.prev = post
-    prev = post
-
-  # Write out posts
-  for post, i in posts
+  # Write out pages and posts
+  for page in posts.concat pages
     # Respect published flag
-    continue unless options.future or post.published
-
-    # Template
-    content = tinyliquid.compile(post.raw_content, liquidOptions) {
+    continue unless config.future or page.published
+
+    # Content can contain liquid directives, process now
+    content = tinyliquid.compile(page.raw_content, liquidOptions) {
       site: siteData
-      page: post
-    }, options.filters
+      page: page
+    }, config.filters

     # Run conversion
-    { ext, content } = convertContent post.ext, content, options.converters
-    post.content = content
-
-    if post.layout
-      template = layouts[post.layout]
+    {ext, content} = convertContent page.ext, content, config.converters
+    page.content = content
+
+    # Apply layout, if it exists
+    if page.layout and page.layout of layouts
+      template = layouts[page.layout]
       rendered = template {
-        content: post.content
-        page: post
+        content: content
         site: siteData
-      }, options.filters
+        page: page
+      }, config.filters
     else
-      rendered = post.content
+      rendered = content

     if ext is '.html'
-      outputPath = path.join options.destination, post.url, 'index.html'
+      outputPath = path.join config.destination, page.url, 'index.html'
     else
-      post.url += ext
-      outputPath = path.join options.destination, post.url
-
+      page.url += ext
+      outputPath = path.join config.destination, page.url
+
+    # Write file
     fs.mkdirsSync path.dirname outputPath
     fs.writeFileSync outputPath, rendered

-  isHidden = (filepath) ->
-    basename = path.basename filepath
-    if basename in options.exclude
-      true
-    else if basename in options.include
-      false
-    else
-      filepath isnt options.source and
-        (basename[0] is '_' or basename[0] is '.')
-
-  # Walk through other directories in the root
-  files = [options.source]
-  while filepath = files.pop()
-    # Folder
-    if fs.statSync(filepath).isDirectory()
-      # Skip special folders
-      continue if isHidden filepath
-
-      # Create directory in destination
-      fs.mkdirsSync path.join options.destination, filepath
-
-      for filename in fs.readdirSync filepath
-        # Skip hidden files
-        childPath = path.join filepath, filename
-        continue if isHidden childPath
-        files.push childPath
-    else
-      { data, content, ext } = getDataAndContent filepath
-
-      if data
-        # Process
-        page = {}
-        page[key] = data[key] for key of data
-
-        basename = path.basename filepath, ext
-        if basename is 'index' and (ext is '.md' or ext is '.html')
-          basename = ''
-        page.url = "/#{path.join path.dirname(filepath), basename}"
-
-        # Content can contain liquid directives
-        content = tinyliquid.compile(content, liquidOptions) {
-          site: siteData
-          page: page
-        }, options.filters
-
-        # Run conversion
-        { ext, content } = convertContent ext, content, options.converters
-        page.content = content
-
-        if page.layout and page.layout of layouts
-          template = layouts[page.layout]
-          rendered = template {
-            content: content
-            site: siteData
-            page: data
-          }, options.filters
-        else
-          rendered = content
-
-        if ext is '.html'
-          outputPath = path.join options.destination, page.url, 'index.html'
-        else
-          page.url += ext
-          outputPath = path.join options.destination, page.url
-
-        fs.mkdirsSync path.dirname outputPath
-        fs.writeFileSync outputPath, rendered
-      else
-        # Straight copy
-        fs.copy filepath, path.join options.destination, filepath
+  # Copy static files without overwhelming the file system
+  async.forEachLimit(
+    staticFiles
+    5
+    (filepath, callback) ->
+      # Make sure directory exists before copying
+      outPath = path.join(config.destination, filepath)
+      fs.mkdirsSync path.dirname(outPath)
+      fs.copy filepath, outPath, callback
+    (err) -> if err then throw err
+  )

   console.log "Successfully generated site: ".green +
-    "#{path.resolve options.source} -> #{options.destination}".green
+    "#{path.resolve config.source} -> #{config.destination}".green
   callback() if callback

 # Make sure the directories needed are there
-checkDirectories = (options) ->
-  unless fs.existsSync options.source
-    console.error "Source directory does not exist: #{options.source}".red
+checkDirectories = (config) ->
+  unless fs.existsSync config.source
+    console.error "Source directory does not exist: #{config.source}".red
     process.exit -1

-  if fs.existsSync options.destination
-    unless fs.lstatSync(options.destination).isDirectory()
-      console.error "Destination is not a directory: #{options.destination}".red
+  if fs.existsSync config.destination
+    unless fs.lstatSync(config.destination).isDirectory()
+      console.error "Destination is not a directory: #{config.destination}".red
       process.exit -1
   else
-    fs.mkdirSync options.destination
+    fs.mkdirSync config.destination

 # Load plugins
-loadPlugins = (options, pluginDir) ->
+loadPlugins = (config, pluginDir) ->
   unless pluginDir
-    pluginDir = path.resolve path.join options.source, '_plugins'
+    pluginDir = path.resolve path.join config.source, '_plugins'

   return unless fs.existsSync pluginDir

@@ -340,17 +277,21 @@
       # Load file
       plugin = require filepath
       if plugin.filters
-        options.filters[key] = plugin.filters[key] for key of plugin.filters
+        for key, filter of plugin.filters
+          config.filters[key] = filter
       if plugin.converters
-        for key of plugin.converters
-          options.converters.push(plugin.converters[key])
+        for key, converter of plugin.converters
+          config.converters.push converter
+      if plugin.generators
+        for key, generator of plugin.generators
+          config.generators.push generator

   return

 # Compile all layouts and return
-getLayoutsAndIncludes = (options) ->
-  layoutDir = path.join options.source, options.layout
-  includesDir = path.join options.source, '_includes'
+getLayoutsAndIncludes = (config) ->
+  layoutDir = path.join config.source, config.layout
+  includesDir = path.join config.source, '_includes'

   includes = {}
   if fs.existsSync includesDir
@@ -392,21 +333,21 @@

 # Get all posts
 postMask = /^(\d{4})-(\d{2})-(\d{2})-(.+)\.(md|html)$/
-getPosts = (options) ->
+getPosts = (config) ->
   posts = []
-  postDir = path.join options.source, '_posts'
+  postDir = path.join config.source, '_posts'
   permalinks = {}
   for filename in fs.readdirSync postDir
     if match = filename.match postMask
       try
-        { data, content, ext } = getDataAndContent path.join postDir, filename
+        {data, content, ext} = getDataAndContent path.join postDir, filename
       catch err
         console.error "Error while trying to read #{filename}:".red
         console.error err.toString()
         console.error "Skipping post #{filename}".yellow
         continue

-      post = {}
+      post = { raw_content: content, ext }
       post[key] = data[key] for key of data

       post.date = new Date match[1], match[2] - 1, match[3]
@@ -416,7 +357,6 @@
       if post.url of permalinks
         console.error "Repeated permalink #{post.url}".red
       permalinks[post.url] = true
-      post.raw_content = content
       post.ext = ext
       if post.tags and typeof post.tags is 'string'
         post.tags = post.tags.split ' '
@@ -430,7 +370,57 @@
     else
       # Doesn't match post mask, ignore

+  # Sort on date
+  posts.sort (a, b) ->
+    b.date - a.date
+
+  # Set next / prev on posts
+  prev = null
+  for post in posts
+    # List is in reverse-chronological order, so the previous post in the loop
+    # is actually the next post
+    if prev
+      post.next = prev
+      prev.prev = post
+    prev = post
+
   posts
+
+getPagesAndStaticFiles = (config) ->
+  pages = []
+  staticFiles = []
+
+  # Walk through all directories looking for files
+  files = [config.source]
+  while filepath = files.pop()
+    # Folder?
+    if fs.statSync(filepath).isDirectory()
+      # Skip special folders
+      continue if isHidden filepath, config
+
+      for filename in fs.readdirSync filepath
+        # Skip hidden files
+        childPath = path.join filepath, filename
+        continue if isHidden childPath, config
+        files.push childPath
+    else
+      {data, content, ext} = getDataAndContent filepath
+
+      if data
+        page = { raw_content: content, ext }
+        page[key] = data[key] for key of data
+
+        basename = path.basename filepath, ext
+        if basename is 'index' and (ext is '.md' or ext is '.html')
+          basename = ''
+        page.url = "/#{path.join path.dirname(filepath), basename}"
+
+        # Add to collection
+        pages.push page
+      else
+        staticFiles.push filepath
+
+  { pages, staticFiles }

 # Get the frontmatter plus content of a file
 getDataAndContent = (filepath) ->
@@ -460,6 +450,17 @@

   # None found, leave unmodified
   return { ext, content }
+
+# Whether the file should be ignored by the system
+isHidden = (filepath, config) ->
+  basename = path.basename filepath
+  if basename in config.exclude
+    true
+  else if basename in config.include
+    false
+  else
+    filepath isnt config.source and
+      (basename[0] is '_' or basename[0] is '.')

 # Creates all the default directories
 createDirectoryStructure = (dir) ->


[client][app][Groups][views] used "is" instead of ==

--- client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
+++ client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
@@ -202,7 +202,7 @@

       currentGroup = KD.getSingleton('groupsController').getCurrentGroup()
       currentGroupSlug = currentGroup.getAt 'slug'
-      if group.slug == currentGroupSlug
+      if group.slug is currentGroupSlug
         document.location.reload()
       callback()


Renamed KO model

Renamed KO model

--- public_src/js/jsmoo.coffee
+++ public_src/js/jsmoo.coffee
@@ -1,4 +1,4 @@
-class Moo
+class MooViewModel

   lines: ko.observableArray []
   maxLines: ko.observable 1000
@@ -78,4 +78,4 @@
           @command ""

 $ ->
-  ko.applyBindings new Moo+  ko.applyBindings new MooViewModel


expression literal

expression literal

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -327,6 +327,7 @@
   Field: [
     o 'STAR',                                             -> new Star()
     o 'Expression',                                       -> new Field($1)
+    o 'Expression Literal',                               -> new Field($1, $2)
     o 'Expression AS Literal',                            -> new Field($1, $3)
   ]

@@ -336,7 +337,8 @@
   ['left', 'MATH_MULTI']
   ['left', 'MATH']
   ['left', 'OPERATOR']
-  ['left', 'CONDITIONAL']
+  ['left', 'AND']
+  ['left', 'OR']
 ]

 for name, alternatives of grammar


browser action title

browser action title

--- src/chrome/manifest.cson
+++ src/chrome/manifest.cson
@@ -18,6 +18,7 @@
     19: "{{../shared/img/Whose-News-Icon19.png}}"
     38: "{{../shared/img/Whose-News-Icon38.png}}"
   default_popup: "{{../shared/views/popup}}"
+  default_title: "Whose News: No news detected"

 permissions: [
   "activeTab"


Add meta.type-signature.haskell to in-line sigs

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -364,12 +364,12 @@
       captures:
         1: patterns: [include: 'source.haskell']
         2: name: 'keyword.other.double-colon.haskell'
-        3: patterns: [include: '#type_signature']
+        3: {name: 'meta.type-signature.haskell', patterns: [include: '#type_signature']}
     ,
       match: '(::|∷)(.*)'
       captures:
         1: name: 'keyword.other.double-colon.haskell'
-        2: patterns: [include: '#type_signature']
+        2: {name: 'meta.type-signature.haskell', patterns: [include: '#type_signature']}
     ,
       match: /\b(Just|Left|Right|Nothing|True|False|LT|EQ|GT)(?!')\b/
       name: 'support.tag.haskell'


Better C preprocessor support (referencing source.c)

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -276,9 +276,11 @@
       match: /\b([0-9]+|0([xX][0-9a-fA-F]+|[oO][0-7]+))\b/
     ,
       name: 'meta.preprocessor.c'
-      match: /{maybeBirdTrack}\s*(#)\s*\w+/
-      captures:
-        1: name: 'punctuation.definition.preprocessor.c'
+      begin: /{maybeBirdTrack}\s*(?=#)/
+      end: '(?<!\\\\)(?=\\n)'
+      patterns: [
+        include: 'source.c'
+      ]
       ###
       In addition to Haskell's "native" syntax, GHC permits the C
       preprocessor to be run on a source file.


:art: Tpyo

:art: Tpyo

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -189,7 +189,7 @@
   event

 # bindingKeystrokes and userKeystrokes are arrays of keystrokes
-# e.g. ['crtl-y', 'ctrl-x', '^x']
+# e.g. ['ctrl-y', 'ctrl-x', '^x']
 exports.keystrokesMatch = (bindingKeystrokes, userKeystrokes) ->
   userKeystrokeIndex = -1
   userKeystrokesHasKeydownEvent = false


Use Patch.hunk

Use Patch.hunk

--- src/history.coffee
+++ src/history.coffee
@@ -95,7 +95,7 @@
     throw new Error("Didn't find matching group-start entry")

   pushChange: (change) ->
-    @undoStack.push(Patch.withSingleChange(change))
+    @undoStack.push(Patch.hunk(change))
     @clearRedoStack()

     if @undoStack.length - @maxUndoEntries > 0


Remove unnecessary function

Remove unnecessary function

--- src/initial.coffee
+++ src/initial.coffee
@@ -12,7 +12,6 @@
 LIMITS = KindredAPI.LIMITS
 CACHE_TYPES = KindredAPI.CACHE_TYPES
 lolClient = null;
-formatSummonerName = (name) -> return name.replace(/\s/g, '').toLowerCase()
 # Start of logic for initial start
 mainWindow = new BrowserWindow {
   width: 500


Tweak progress message for global installs

--- src/install.coffee
+++ src/install.coffee
@@ -221,7 +221,10 @@
       callback()
       return

-    process.stdout.write "Installing #{packageName}@#{packageVersion} "
+    unless installGlobally
+      label = packageName
+      label += "@#{packageVersion}" if packageVersion
+      process.stdout.write "Installing #{label} "

     auth.getToken (error, token) =>
       if error?
@@ -253,10 +256,11 @@
               @installModule(options, pack, packagePath, callback)

             async.waterfall commands, (error) ->
-              if error?
-                process.stdout.write '\u2717\n'.red
-              else
-                process.stdout.write '\u2713\n'.green
+              unless installGlobally
+                if error?
+                  process.stdout.write '\u2717\n'.red
+                else
+                  process.stdout.write '\u2713\n'.green
               callback(error)

   # Install all the package dependencies found in the package.json file.


Add `tiles` array and `newTile` function

--- src/js/tael.coffee
+++ src/js/tael.coffee
@@ -1,6 +1,35 @@
 # tael.js
 # Copyright (c) 2015 Carter Hinsley
 # MIT License
+
+tiles = [{
+    type: 'container'
+}]
+
+newTile = (parent) ->
+    # Add a new tile to the `tiles` array.
+    pushTile = ->
+        # Push a new tile to the `tiles` array and return its index.
+        (
+            tiles.push
+                type: 'leaf'
+        ) - 1
+    switch parent.type
+        when 'container'
+            parent.child = do pushTile
+        when 'leaf'
+            parent =
+                type: 'branch'
+                children:
+                    left: do pushTile
+                    right: do pushTile
+                layout:
+                    split: 'horizontal'
+                    divider_location: 1
+        when 'branch'
+            throw "Branch tiles cannot spawn new children post-creation."
+
+newTile(tiles[0])

 module.exports = ->
     ($ document).ready ->


Detect user agent as first step then remove default stylesheet and load in android stylesheet if needed

* Don't use iscroll

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -18,6 +18,7 @@
   speed: 0.3
   mainMenu: '#main-menu'
   menuCloseButton: '#close-menu-btn'
+  stylesheetPath: '../lib/'

   # Public: Instantiate Flight and set any options.
   #
@@ -25,6 +26,10 @@
   #
   # Returns nothing.
   constructor: (options = {}) ->
+    #TODO: don't allow slide up transitions if old android
+    @detectUserAgent()
+    @setupStyleSheets()
+
     if options.transitionAnimation?
       @transitionAnimation = options.transitionAnimation

@@ -32,8 +37,6 @@

     @mainMenu = options.mainMenu if options.mainMenu?
     @mainMenu = document.querySelector @mainMenu if typeof @mainMenu is "string"
-
-    @detectUserAgent()

     @transitionAnimation = false if @os.android and @os.version <= '2.1'

@@ -89,9 +92,6 @@
       @targetPage = targetPage

     return if @targetPage is @currentPage
-
-    if @isAndroid() and @os.version < '4'
-      @initIscroll @targetPage

     unless @currentPage
       @targetPage.style.display = "-webkit-box"
@@ -280,6 +280,18 @@
     os.desktop = not (os.ios or os.android or os.blackberry or os.opera or os.fennec)
     @os = os

+  setupStyleSheets: =>
+    if @isAndroid() and @os.version < '4'
+      head = document.getElementsByTagName('head')[0]
+      androidCSS = document.createElement("link")
+      androidCSS.setAttribute("rel", "stylesheet")
+      androidCSS.setAttribute("type", "text/css")
+      androidCSS.setAttribute("href", "#{@stylesheetPath}flight.android.css")
+      head.appendChild(androidCSS)
+
+      styleSheets = document.styleSheets
+      styleSheet.disabled = true for styleSheet in styleSheets when styleSheet.href.indexOf("flight.css") isnt -1
+
   # Private: Hide the URL bar in mobile browsers.
   #
   # Returns nothing.


Handle max pages in frame data output

Handle max pages in frame data output

--- src/ext/components/frame-data-output.coffee
+++ src/ext/components/frame-data-output.coffee
@@ -3,7 +3,7 @@
   _data = signal null
   _columnNameSearchTerm = signal null
   _currentPage = signal 0
-  _maxPages = signal 10000 #TODO FIXME
+  _maxPages = signal Math.ceil _frame.total_column_count / MaxItemsPerPage
   _canGoToPreviousPage = lift _currentPage, (index) -> index > 0
   _canGoToNextPage = lift _maxPages, _currentPage, (maxPages, index) -> index < maxPages - 1

@@ -26,8 +26,9 @@
     searchTerm = _columnNameSearchTerm()
     if searchTerm isnt _lastUsedSearchTerm
       pageIndex = 0
-
-    _.requestFrameDataE _frame.frame_id.name, searchTerm, pageIndex * MaxItemsPerPage, MaxItemsPerPage, (error, frame) ->
+    startIndex = pageIndex * MaxItemsPerPage
+    itemCount = if startIndex + MaxItemsPerPage < _frame.total_column_count then MaxItemsPerPage else _frame.total_column_count - startIndex
+    _.requestFrameDataE _frame.frame_id.name, searchTerm, startIndex, itemCount, (error, frame) ->
       if error
         #TODO
       else


Don't cache things in window

Don't cache things in window

--- app/assets/javascripts/darkswarm/controllers/checkout/shipping_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/checkout/shipping_controller.js.coffee
@@ -3,4 +3,3 @@
   $scope.name = "shipping"
   $scope.nextPanel = "payment"

-  window.tmp = $scope


Unused variables

Unused variables

--- app/assets/javascripts/rglossa/speech/components/centre/results/wfplayer.jsx.coffee
+++ app/assets/javascripts/rglossa/speech/components/centre/results/wfplayer.jsx.coffee
@@ -10,8 +10,6 @@

     corpus_id = mediaObj.corpus_id
     line_key = mediaObj.mov.line_key
-    mov = mediaObj.mov.movie_loc
-    path = mediaObj.mov.path
     $("#movietitle").text(mediaObj.title)
     start = mediaObj.divs.annotation[@props.startAt || parseInt(mediaObj.start_at)].from
     stop  = mediaObj.divs.annotation[@props.endAt || parseInt(mediaObj.end_at)].to


Fix sending from another account

Fix sending from another account

--- app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
@@ -54,4 +54,4 @@
     else
       cardBlock(transaction)

-  _getAccount: -> @_account ||= Account.findById(+@params.account_id or 0)
+  _getAccount: -> @_account ||= @params.account


ComputeProvider: validateStackTemplates multiple version of the validateStackTemplate

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -307,6 +307,21 @@
       return callback new KodingError results.last[1]  unless passed

+  # Takes an array of stack template ids and returns the final result ^^
+  @validateTemplates = (client, stackTemplates, group, callback) ->
+
+    queue = []
+
+    stackTemplates.forEach (stackTemplateId) -> queue.push ->
+      ComputeProvider.validateTemplate client, stackTemplateId, group, (err) ->
+        return callback err  if err
+        queue.next()
+
+    queue.push -> callback null
+
+    daisy queue
+
+
   # Auto create stack operations ###

   @createGroupStack$ = permit 'create machines',


mouse events routed

mouse events routed

--- src/javascript/modules/block_maze/index.coffee
+++ src/javascript/modules/block_maze/index.coffee
@@ -45,8 +45,6 @@
     )
     C.buildCompForType(T.Controller, inputName: 'player1')
   ])
-
-  console.log e

   estore.createEntity([
     C.buildCompForType(T.Name, name: 'Corner1')
@@ -110,6 +108,11 @@
     e = action.value
     model.input.controllerEvents.push(e)

+  if action instanceof Mouse
+    model.NO_RENDER=true
+    {type,x,y,event} = action.value
+    # console.log "Action.Mouse type=#{type}", x,y
+
   if action instanceof Time
     t = action.value
     if model.lastTime?
@@ -128,34 +131,28 @@
   else
     [model,null]

-# AsciiView = require './elements/ascii_view'
 MazeView = require './elements/maze_view'

-sendMouseAction: (address, type,e) ->
-  e.stopPropagation()
-  e.preventDefault()
-  address.send new Mouse(type: type, event: e)
-
-getMouseHandlers: (address) ->
-  handler = (type) =>
-    (e) =>
-      sendMouseAction(address, type, e)
-  {
-    onMouseEnter: handler('enter')
-    onMouseLeave: handler('leave')
-    onMouseMove: handler('move')
-    onMouseDown: handler('down')
-    onMouseUp: handler('up')
-    onWheel: handler('wheel')
-  }
+handleMouse = (type,address) ->
+  (e) ->
+    e.stopPropagation()
+    e.preventDefault()
+    address.send new Mouse(
+      type: type
+      x: e.nativeEvent.offsetX
+      y: e.nativeEvent.offsetY
+      event: e
+    )

 exports.view = (model,address) ->

Maze Thinger

-    {#<AsciiView estore={model.estore} /> #}
-
-

+

       <MazeView estore={model.estore} />

     <KeyboardInput


Improve compiler errors on built ins and added file info to error from other file

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -808,7 +808,7 @@
 };

 var and_ = function (x, xs) {
-  if (xs == null) {
+  if (!xs) {
     throw new Error('Second argument to & must be a sequence');
   }
   if (typeof xs == 'string' || xs instanceof String) {
@@ -824,6 +824,9 @@
 };

 var $sequenceSize = function (xs) {
+  if (!xs) {
+    throw new Error('Pattern matching on size of undefined');
+  }
   if (xs.length != null) {
     return xs.length;
   }
@@ -831,6 +834,9 @@
 };

 var $sequenceAt = function (i, xs) {
+  if (!xs) {
+    throw new Error('Pattern matching required sequence got undefined');
+  }
   if (xs.length) {
     return xs[i];
   }
@@ -849,6 +855,9 @@

 // temporary, will be replaced by typed 0-argument function
 var $empty = function (xs) {
+  if (!xs) {
+    throw new Error('Empty needs a sequence');
+  }
   if (typeof xs == 'string' || xs instanceof String) {
     return "";
   }


Added class constraints, tests running again

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -90,7 +90,7 @@
   ast = astize tokenize string
   compiled = fn (ctx = new Context), ast
   syntax: collapse toHtml ast
-  types: values mapMap (__ highlightType, _type), subtractMaps ctx._scope(), builtInContext new Context
+  types: values mapMap (__ highlightType, _type), subtractMaps ctx._scope(), builtInContext()
   translation: '\n' + compiled

 mapCompile = (fn, string) ->
@@ -102,7 +102,7 @@
   expressions = []
   visitExpressions ast, (expression) ->
     expressions.push "#{collapse toHtml expression} :: #{highlightType expression.tea}" if expression.tea
-  types: values mapMap (__ highlightType, _type), subtractMaps ctx._scope(), builtInContext new Context
+  types: values mapMap (__ highlightType, _type), subtractMaps ctx._scope(), builtInContext()
   subs: values mapMap highlightType, ctx.substitution
   ast: expressions
   deferred: ctx.deferredBindings()
@@ -113,7 +113,7 @@
   expressions = []
   visitExpressions ast, (expression) ->
     expressions.push [(collapse toHtml expression), expression.tea] if expression.tea
-  types: values mapMap _type, subtractMaps ctx._scope(), builtInContext new Context
+  types: values mapMap _type, subtractMaps ctx._scope(), builtInContext()
   subs: values ctx.substitution
   ast: expressions
   deferred: ctx.deferredBindings()
@@ -143,7 +143,7 @@
     @statement = []
     @cacheScopes = [[]]
     @_assignTos = []
-    topScope = @_augmentScope builtInContext this # dangerous passing itself in constructor
+    topScope = @_augmentScope builtInContext()
     topScope.topLevel = yes
     @scopes = [topScope]

@@ -241,6 +241,8 @@
   _augmentScope: (scope) ->
     scope.deferred = []
     scope.deferredBindings = []
+    scope.boundTypeVariables = newSet()
+    scope.classes = newMap()
     scope

   newLateScope: ->
@@ -255,6 +257,30 @@

   isInTopScope: ->
     @_scope().topLevel
+
+  bindTypeVariables: (vars) ->
+    addAllToSet @_scope().boundTypeVariables, vars
+
+  allBoundTypeVariables: ->
+    concatSets (for scope in @scopes
+      scope.boundTypeVariables)
+
+  isClassDefined: (name) ->
+    !!@classNamed name
+
+  addClass: (name, superClasses, declarations) ->
+    addToMap @_scope().classes, name,
+      supers: superClasses
+      instances: []
+      declarations: declarations
+
+  classNamed: (name) ->
+    for scope in (reverse @scopes)
+      if classDeclaration = lookupInMap scope.classes, name
+        return classDeclaration
+
+  addInstance: (name, instance) ->
+    (@classNamed instance.type.type).instances.push instance

   isDeclared: (name) ->
     !!(@_declaration name)
@@ -269,6 +295,9 @@
     (lookupInMap @scopes[i], name) or
       i > 0 and (@_declarationInScope i - 1, name) or
       undefined # throw "Could not find declaration for #{name}"
+
+  isCurrentlyDeclared: (name) ->
+    !!(lookupInMap @_scope(), name)

   assignType: (name, type) ->
     # log "TYPE OF #{name}", printType type
@@ -278,6 +307,9 @@
       declaration.type = type
     else
       throw new Error "assignType: #{name} is not declared"
+
+  currentDeclarations: ->
+    cloneMap @_scope()

   addToDeferredNames: (binding) ->
     @_definition().deferredBindings.push binding
@@ -496,12 +528,11 @@
     ctx.resetAssignTo()
     elemCompiled)

+  # Typing operator like inside a known call
   for arg in args when isExtra arg
     arg.tea = ctx.freshTypeVariable star

-  # Typing operator like inside a known call
   precsForData = operatorCompile ctx, call
-  # log 'op in pattern', precsForData

   # Gets the general type as if the extra arguments were supplied
   callTyping ctx, call
@@ -572,8 +603,8 @@

 callInferSingle = (ctx, operatorTea, argTea) ->
   returnType = ctx.freshTypeVariable star
-  unify ctx, operatorTea, (typeFn argTea, returnType)
-  returnType
+  unify ctx, operatorTea.type, (typeFn argTea.type, returnType)
+  new Constrained (join operatorTea.constraints, argTea.constraints), returnType

 termsCompile = (ctx, list) ->
   termCompile ctx, term for term in list
@@ -608,7 +639,8 @@
   #   map [0: "hello" 1:] {"world", "le mond", "svete"}
   # TODO: should we support bare records?
   #   [a: 2 b: 3]
-  form.tea = applyKindFn (tupleType arity), (tea for {tea} in elems)...
+  form.tea = new Constrained (concatMap _constraints, elems),
+    applyKindFn (tupleType arity), (tea for {tea} in elems)...

   if ctx.assignTo()
     combinePatterns compiledElems
@@ -648,12 +680,13 @@
       lhsCompiled)
     elemType = ctx.freshTypeVariable star
     # TODO use (Seq c e) instead of (Array e)
-    form.tea = new TypeApp arrayType, elemType
+    form.tea = new Constrained (concatMap _constraints, elems),
+      new TypeApp arrayType, elemType

     for elem in elems
-      unify ctx, elem.tea,
+      unify ctx, elem.tea.type,
         if isSplat elem
-          form.tea
+          form.tea.type
         else
           elemType

@@ -669,11 +702,15 @@
     elemType = ctx.freshTypeVariable star
     compiledElems = termsCompile ctx, elems
     for elem in elems
-      unify ctx, elemType, elem.tea
+      unify ctx, elemType, elem.tea.type

     form.label = 'operator'
-    form.tea = new TypeApp arrayType, elemType
+    form.tea = new Constrained (concatMap _constraints, elems),
+      new TypeApp arrayType, elemType
     assignCompile ctx, form, "[#{listOf compiledElems}]"
+
+_constraints = (expression) ->
+  expression.tea.constraints

 isSplat = (expression) ->
   (isAtom expression) and (_symbol expression)[...2] is '..'
@@ -690,8 +727,8 @@
 #     (call_ (token_ 'cons-array'), [x, (arrayToConses xs)])

-typeCompile = (ctx, expression) ->
-  throw new Error "invalid typeCompile args" unless ctx instanceof Context and expression
+typeCompile = (expression) ->
+  throw new Error "invalid typeCompile args" unless expression
   (if isAtom expression
     typeConstantCompile
   else if isTuple expression
@@ -700,19 +737,19 @@
     typeConstructorCompile
   else
     malformed expression, 'not a valid type'
-  )? ctx, expression
-
-typesCompile = (ctx, expressions) ->
-  map ((e) -> typeCompile ctx, e), expressions
-
-typeConstructorCompile = (ctx, call) ->
+  )? expression
+
+typesCompile = (expressions) ->
+  map typeCompile, expressions
+
+typeConstructorCompile = (call) ->
   op = _operator call
   args = _arguments call

   if isAtom op
     op.label = 'operator'
     name = op.symbol
-    compiledArgs = typesCompile ctx, args
+    compiledArgs = typesCompile args
     if name is 'Fn'
       typeFn compiledArgs...
     else
@@ -721,17 +758,37 @@
   else
     malformed op, 'Should use a type constructor here'

-typeTupleCompile = (ctx, form) ->
+typeConstraintCompile = (expression) ->
+  op = _operator expression
+  args = _arguments expression
+  if isCall expression
+    if isAtom op
+      op.label = 'operator'
+      new ClassContraint op.symbol, typeCompile args[0] # TODO: support multiparameter type classes
+    else
+      malformed 'Class name required in a constraint', expression
+  else
+    malformed 'Class constraint expected', expression
+
+typeConstraintsCompile = (expressions) ->
+  map typeConstraintCompile, expressions
+
+typeTupleCompile = (form) ->
   form.label = 'operator'
   elemTypes = _terms form
-  applyKindFn (tupleType elemTypes.length), (typesCompile ctx, elemTypes)...
-
-typeConstantCompile = (ctx, atom) ->
+  applyKindFn (tupleType elemTypes.length), (typesCompile elemTypes)...
+
+typeConstantCompile = (atom) ->
   name = atom.symbol
+  atom.label = 'typename'
   if /^[A-Z]/.test name
     typeConstant name
   else
     new TypeVariable name, star
+
+typeConstantsCompile = (expressions) ->
+  map typeConstantCompile, expressions
+

 # Inside definition, we call assignCompile with its RHS
 #   whether to call it and with what expression is left to the RHS expression
@@ -779,9 +836,10 @@
     #log "exiting pattern early", pattern, "for", ctx.shouldDefer()
     return {}

-  # Check the types
+  # Properly bind types according to the pattern
   if pattern.tea
-    unify ctx, matched.tea, pattern.tea
+    # log pattern, matched, matched.tea, pattern.tea
+    unify ctx, matched.tea.type, pattern.tea.type

   # log "pattern compiel", definedNames, pattern
   for {name, type} in definedNames
@@ -795,12 +853,12 @@
       ctx.declare name, type: new TempType type
     else
       # TODO: this is because functions might declare arity before being declared
-      if not ctx.isDeclared name
+      if not ctx.isCurrentlyDeclared name
         ctx.declare name
       # For explicitly typed bindings, we need to check that the inferred type
       #   corresponds to the annotated
       if ctx.isTyped name
-        unify ctx, currentType, freshInstance ctx, ctx.type name
+        unify ctx, currentType.type, (freshInstance ctx, ctx.type name).type
       else
         ctx.assignType name,
           if ctx.isAtDefinition()
@@ -919,6 +977,7 @@
         [type, body, wheres...] = defs
       else
         [body, wheres...] = defs
+      paramNames = _names params

       # Arity - before deferring instead? put to assignCompile, because this makes the naming of functions special
       if ctx.isAtSimpleDefinition()
@@ -926,21 +985,22 @@
         ctx.declareArity ctx.definitionName(), paramNames
         # Explicit typing
         if type
-          ctx.assignType ctx.definitionName(), quantifyAll typeCompile ctx, type
-
-      paramNames = map _symbol, params
-      # pattern
-      paramTypes = map (-> toForAll ctx.freshTypeVariable star), params
+          explicitType = quantifyUnbound ctx, typeCompile type
+          ctx.assignType ctx.definitionName(), explicitType
+
+      paramTypeVars = map (-> ctx.freshTypeVariable star), params
+      paramTypes = map (__ toForAll, toConstrained), paramTypeVars
       ctx.newLateScope()
       # log "adding types", (map _symbol, params), paramTypes
-      ctx.declareTypes (map _symbol, params), paramTypes
+      ctx.declareTypes paramNames, paramTypes

       #log "compiling wheres", pairs wheres
       compiledWheres = definitionList ctx, pairs wheres

       # log "types added"
       #log "compiling", body
-      compiledBody = termCompile ctx, body
+      if body
+        compiledBody = termCompile ctx, body
       # log "compiled", body.tea
       ctx.closeScope()

@@ -961,8 +1021,12 @@
           if body and not body.tea
             #log body
             throw new Error "Body not typed"
-          call.tea = typeFn (map _type, paramTypes)..., body.tea
-
+          call.tea =
+            if body
+              new Constrained body.tea.constraints,
+                typeFn paramTypeVars..., body.tea.type
+            else
+              freshInstance ctx, explicitType
           """λ#{paramNames.length}(function (#{listOf paramNames}) {
             #{compiledWheres}
             return #{compiledBody};
@@ -975,12 +1039,9 @@
   #         type
   #     constant-name
   data: (ctx, call) ->
+    hasName = requireName ctx, 'Name required to declare new algebraic data'
     defs = pairsLeft isAtom, _arguments call
     # Syntax
-    if ctx.isAtDefinition()
-      syntaxNewName 'Name required to declare new algebraic data', ctx.definitionPattern()
-    else
-      malformed call, 'Name required to declare new algebraic data'
     [names, typeArgLists] = unzip defs
     map (syntaxNewName 'Type constructor name required'), names
     for typeArgs in typeArgLists
@@ -989,9 +1050,10 @@
           syntaxType type
       else
         typeArgs.label = 'malformed'
+    if not hasName
+      return 'malformed'
+
     dataName = ctx.definitionName()
-    if not dataName
-      return 'malformed'

     # Types, Arity
     for [constr, params] in defs
@@ -1002,7 +1064,7 @@
       # TODO support polymorphic data
       #log "Adding constructor #{constr.symbol}"
       ctx.declare constr.symbol,
-        type: toForAll constrType
+        type: toForAll toConstrained constrType
         arity: ((_labeled _terms params).map(_fst).map(_labelName) if params)
       constr.tea = constrType
     # We don't add binding to kind constructors, but maybe we need to
@@ -1014,9 +1076,6 @@
       paramNames = (_labeled _terms params or []).map(_fst).map(_labelName)
         .map(validIdentifier)
       paramList = paramNames.join(', ')
-      paramAssigns = blockOfLines paramNames.map (name) ->
-        "  this.#{name} = #{name};"
-      constrFn = """function #{identifier}(#{paramList}) {#{paramAssigns}};"""
       constrValue = if params
         """
         #{identifier}.value = λ#{paramNames.length}(function(#{paramList}){
@@ -1024,10 +1083,11 @@
         });"""
       else
         "#{identifier}.value = new #{identifier}();"
-      listOfLines [constrFn, constrValue])
+      listOfLines [(translateDict identifier, paramNames), constrValue])

   record: (ctx, call) ->
     args = _arguments call
+    hasName = requireName ctx, 'Name required to declare new record'
     for [name, type] in _labeled args
       if not name
         malformed type, 'Label is required'
@@ -1038,8 +1098,93 @@
     if args.length is 0
       malformed call, 'Missing arguments'
     # TS: (data #{ctx.definitionName()} [#{_arguments form}])
+    if not hasName
+      return 'malformed'
     replicate call,
       (call_ (token_ 'data'), [(token_ ctx.definitionName()), (tuple_ args)])
+
+  # # Type an expression
+  # ':': (ctx, call) ->
+  #   [expression, type] = _arguments call
+  #   if not type
+  #     return malformed 'Missing type', call
+  #   ctx.setIsType true
+  #   ctx.setIsType false
+
+  # Adds a class to the scope or defers if superclass doesn't exist
+  class: (ctx, call) ->
+    hasName = requireName ctx, 'Name required to declare a new class'
+    [paramList, defs...] = _arguments call
+    params = paramTuple ctx, paramList
+    paramNames = _names params
+    [docs, defs] = partition isComment, defs
+    if defs.length % 2 == 0
+      wheres = defs
+    else
+      [constraintSeq, wheres...] = defs
+
+    superClasses = map (({className}) -> className), constraintsFromSeq ctx, constraintSeq
+    # TODO: defer if not all declared to prevent cycles in classes
+    #   allDeclared = (ctx.isClass c for c in superClasses)
+
+    ctx.newScope()
+    ctx.bindTypeVariables paramNames
+    definitionList ctx, pairs wheres
+    declarations = ctx.currentDeclarations()
+    ctx.closeScope()
+
+    if hasName
+      name = ctx.definitionName()
+      if ctx.isClassDefined name
+        malformed 'class already defined', ctx.definitionPattern()
+      else
+        ctx.addClass name, superClasses, declarations
+        translateDict name, keysOfMap declarations
+    else
+      'malformed'
+
+  instance: (ctx, call) ->
+    hasName = requireName ctx, 'Name required to declare a new instance'
+
+    [instanceConstraint, defs...] = _arguments call
+    if not isCall call
+      return malformed 'Instance requires a single class constraint', call
+    else
+      instanceType = typeConstraintCompile instanceConstraint
+    if defs.length % 2 == 0
+      wheres = defs
+    else
+      [constraintSeq, wheres...] = defs
+
+    constraints = constraintsFromSeq ctx, constraintSeq
+
+    ctx.newScope()
+    ctx.bindTypeVariables keysOfMap findFree instanceType.type
+    compiledWheres = definitionList ctx, pairs wheres
+    ## declarations = ctx.currentDeclarations()
+    ctx.closeScope()
+
+    # TODO: this is hard,
+    # there are some special cases like:
+    # class Show a where
+    #   show :: a -> String
+    #   ho :: a -> String
+    methods = []##methodsFromDefinitions compiledWheres
+
+    className = instanceType.className
+    # TODO: defer for class declaration if not defined
+    ## if not ctx.isClassDefined className    ...
+
+    if hasName
+      name = ctx.definitionName()
+      instance = (new Constrained constraints, instanceType)
+      ## if overlaps ctx, instance
+      ##   malformed 'instance overlaps with another', instance
+      ## else
+      ctx.addInstance name, instance
+      """var #{name} = new #{className}(#{listOf methods});"""
+    else
+      'malformed'

   # TODO:
   # For now support the simplest function macros, just compiling down to source
@@ -1072,7 +1217,7 @@
     subjectCompiled = termCompile ctx, subject

     # To make sure all results have the same type
-    call.tea = resultType = ctx.freshTypeVariable star
+    call.tea = toConstrained (resultType = ctx.freshTypeVariable star)

     ctx.setGroupTranslation()
     compiledCases = conditional (for [pattern, result] in pairs cases
@@ -1090,7 +1235,7 @@
         continue

       # log "unifying in match", result, resultType, result.tea
-      unify ctx, resultType, result.tea
+      unify ctx, resultType, result.tea.type
       varNames.push (findDeclarables precs)...

       matchBranchTranslate precs, assigns, compiledResult
@@ -1149,6 +1294,83 @@
     map (syntaxNewName 'Parameter name expected'), params
   params

+quantifyUnbound = (ctx, type) ->
+  vars = subtractSets (arrayToSet findFree type), ctx.allBoundTypeVariables()
+  quantify vars, type
+
+# Takes a set of fixed type variables, a set of type variables which
+# should be quantified and a list of constraints
+# returns deferred and retained constraints
+deferConstraints = (ctx, fixedVars, quantifiedVars, constraints) ->
+  reducedConstraints = reduceConstraints ctx, constraints
+  isFixed = (constraint) ->
+    isSubset fixedVars, (findFree constraint)
+  [deferred, retained] = partition isFixed, reducedConstraints
+  # TODO: handle ambiguity when reducedConstraints include variables not in
+  # fixedVars or quantifiedVars
+  [deferred, retained]
+
+reduceConstraints = (ctx, constraints) ->
+  normalized = normalizeConstraints ctx, constraints
+  if normalized
+    simplifyConstraints ctx, normalized
+  else
+    null
+
+normalizeConstraints = (ctx, constraints) ->
+  normalized = concat (for constraint in constraints
+    normalizeConstraint ctx, constraint)
+  if all normalized
+    normalized
+  else
+    null
+
+normalizeConstraint = (ctx, constraint) ->
+  if isNormalizedConstraint constraint
+    [constraint]
+  else
+    instanceContraints = constraintsFromInstance ctx, constraint
+    if instanceContraints
+      normalizeConstraints ctx, instanceContraints
+    else
+      null
+
+simplifyConstraints = (ctx, constraints) ->
+  requiredConstraints = []
+  for constraint, i in constraints
+    if not entail ctx, (join requiredConstraints, constraints[i + 1..]), constraint
+      requiredConstraints.push constraint
+  requiredConstraints
+
+
+# Whether constraints entail constraint
+entail = (ctx, constraints, constraint) ->
+  for c in constraints
+    for superClassContraint in constraintsFromSuperClasses ctx, c
+      if typeEq superClassContraint, constraint
+        return yes
+  instanceContraints = constraintsFromInstance ctx, constraint
+  if instanceContraints
+    allMap ((c) -> entail ctx, constraints, c), instanceContraints
+  else
+    no
+
+constraintsFromSuperClasses = (ctx, constraint) ->
+  {className, type} = constraint
+  join [constraint], concat (for s in (ctx.classNamed className).supers
+    bySuper ctx, new ClassContraint s, type)
+
+constraintsFromInstance = (ctx, constraint) ->
+  {className, type} = constraint
+  for instance in (ctx.classNamed className).instances
+    substitution = match instance.type, constraint
+    if not lookupInMap substitution, "could not unify"
+      return map ((c) -> substitute substitution, c), instance.constraints
+  null
+
+_names = (list) ->
+  map _symbol, list
+
 # From precs, find caches and the LHS are declarable variables
 findDeclarables = (precs) ->
   map (__ _fst, _cache), (filter _cache, precs)
@@ -1186,6 +1408,33 @@
         unless isWellformed term
           return no
     yes
+
+translateDict = (dictName, fieldNames) ->
+  paramAssigns = blockOfLines fieldNames.map (name) ->
+    "  this.#{name} = #{name};"
+  constrFn = """function #{dictName}(#{listOf fieldNames}) {#{paramAssigns}};"""
+  accessors = listOfLines fieldNames.map (name) ->
+    """#{dictName}.#{name} = function #{name}(dict) {
+        return dict.#{name};
+      };"""
+  listOfLines [constrFn, accessors]
+
+requireName = (ctx, message) ->
+  if ctx.isAtDefinition()
+    syntaxNewName message, ctx.definitionPattern()
+  else
+    malformed call, message
+    false
+
+constraintsFromSeq = (ctx, seq) ->
+  if seq
+    constraints = _terms seq
+    if (not isSeq seq)
+      malformed 'Constraints must be specified in a list', seq
+    else
+      constrainTypes = filter ((t) -> t instanceof ClassContraint),
+        typeConstraintsCompile ctx, constraints
+  constrainTypes or []

 atomCompile = (ctx, atom) ->
   {symbol, label} = atom
@@ -1197,11 +1446,11 @@
       when 'regex'
         regexCompile ctx, symbol
       when 'char'
-        type: typeConstant 'Char'
+        type: toConstrained typeConstant 'Char'
         translation: symbol
         pattern: literalPattern ctx, symbol
       when 'string'
-        type: typeConstant 'String'
+        type: toConstrained typeConstant 'String'
         translation: symbol
         pattern: literalPattern ctx, symbol
       else
@@ -1229,7 +1478,7 @@
         pattern: []
     else
       atom.label = 'name'
-      type = ctx.freshTypeVariable star
+      type = toConstrained ctx.freshTypeVariable star
       ctx.addToDefinedNames {name: symbol, type: type}
       type: type
       pattern:
@@ -1245,7 +1494,7 @@
     # Inside function only defer compilation if we don't know arity
     else if ctx.isInsideLateScope() and (ctx.isDeclared symbol) or contextType instanceof TempType
       # Typing deferred, use an impricise type var
-      type = ctx.freshTypeVariable star
+      type = toConstrained ctx.freshTypeVariable star
       ctx.addToDeferredNames {name: symbol, type: type}
       {
         type: type
@@ -1276,12 +1525,12 @@

 numericalCompile = (ctx, symbol) ->
   translation = if symbol[0] is '~' then "(-#{symbol})" else symbol
-  type: typeConstant 'Num'
+  type: toConstrained typeConstant 'Num'
   translation: translation
   pattern: literalPattern ctx, translation

 regexCompile = (ctx, symbol) ->
-  type: typeConstant 'Regex'
+  type: toConstrained typeConstant 'Regex'
   translation: symbol
   pattern:
     if ctx.assignTo()
@@ -1290,11 +1539,6 @@
 literalPattern = (ctx, translation) ->
   if ctx.assignTo()
     precs: [cond_ "#{ctx.assignTo()}" + " === #{translation}"]
-
-regexMapping = (symbol, regexes...) ->
-  for [label, regex] in regexes
-    if regex.test symbol
-      return label

 # type expressions syntax
 # or
@@ -1330,6 +1574,7 @@
   curried = (atom) ->
     if isName atom
       atom.label = label
+      true
     else
       malformed atom, message
   if atom then curried atom else curried
@@ -1442,6 +1687,10 @@
 pairs = (list) ->
   for el, i in list by 2
     [el, list[i + 1]]
+
+tuplize = (n, list) ->
+  for e, i in list by n
+    list[i...i + n]

 unzip = (pairs) ->
   [
@@ -1487,47 +1736,6 @@
     word.label = label
     return word
   word
-
-# TODO: support classes and instances
-# classDefinition = (node) ->
-#   words = inside node
-#   [keyword, paramList, defs...] = words
-#   params = if Array.isArray(paramList) then paramList else undefined
-#   defs ?= []
-#   if defs.length > 0
-#     [first] = defs
-#     if Array.isArray first
-#       context = first
-#       defs = defs[1..]
-#   wheres = whereList defs
-#   {params, context, wheres}
-
-# instanceDefinition = (node) ->
-#   words = inside node
-#   [keyword, klass, defs...] = words
-#   wheres = whereList defs
-#   {klass, wheres}
-
-# labelClasses = (ast) ->
-#   macro 'class', ast, (node, args) ->
-#     node.type = 'class'
-#     {params, context, wheres} = classDefinition node
-#     labelContext context
-#     labelWhere wheres
-#     labelParams node, params if params?
-#     node
-
-# labelContext = (node) ->
-#   for parent in inside node
-#     parent.label = 'operator'
-
-# labelInstances = (ast) ->
-#   macro 'instance', ast, (node, args) ->
-#     node.type = 'instance'
-#     {klass, wheres} = instanceDefinition node
-#     klass.label = 'operator'
-#     labelWhere wheres
-#     node

 # TODO: support require
 # Ideally shouldnt have to, just doing it to get around the def checking
@@ -1971,34 +2179,34 @@

 # Default type context with builtins

-binaryMathOpType = ['Num', 'Num', 'Num']
-comparatorOpType = ['a', 'a', 'Bool']
-
-builtInContext = (ctx) ->
+binaryMathOpType = '(Fn Num Num Num)'
+comparatorOpType = '(Fn a a Bool)'
+
+builtInContext = ->
   concatMaps (mapMap desiplifyTypeAndArity, newMapWith 'True', 'Bool',
     'False', 'Bool'
-    '&', ['a', 'b', 'b'] # TODO: replace with actual type
-    'show-list', ['a', 'b'] # TODO: replace with actual type
-    'from-nullable', ['a', 'b'] # TODO: replace with actual type JS -> Maybe a
+    '&', '(Fn a b b)' # TODO: replace with actual type
+    'show-list', '(Fn a b)' # TODO: replace with actual type
+    'from-nullable', '(Fn a b)' # TODO: replace with actual type JS -> Maybe a

     # TODO match

-    'if', ['Bool', 'a', 'a', 'a']
+    'if', '(Fn Bool a a a)'
     # TODO JS interop

-    'sqrt', ['Num', 'Num']
-    'not', ['Bool', 'Bool']
+    'sqrt', '(Fn Num Num)'
+    'not', '(Fn Bool Bool)'

     '^', binaryMathOpType

-    '~', ['Num', 'Num']
+    '~', '(Fn Num Num)'

     '+', binaryMathOpType
     '*', binaryMathOpType
     '=', comparatorOpType
     '!=', comparatorOpType
-    'and', ['Bool', 'Bool', 'Bool']
-    'or', ['Bool', 'Bool', 'Bool']
+    'and', '(Fn Bool Bool Bool)'
+    'or', '(Fn Bool Bool Bool)'

     '-', binaryMathOpType
     '/', binaryMathOpType
@@ -2007,17 +2215,18 @@
     '>', comparatorOpType
     '<=', comparatorOpType
     '>=', comparatorOpType),
-    newMapWith 'empty-array', (type: (new TypeApp arrayType, (ctx.freshTypeVariable star))),
-      'cons-array', (
-        type:
-          (typeFn (elemType = ctx.freshTypeVariable star),
-            (new TypeApp arrayType, elemType),
-            (new TypeApp arrayType, elemType))
-        arity: ['what', 'onto'])
+    newMapWith 'empty-array', (type: (parseUnConstrainedType '(Fn (Array a))'), arity: [])
+      'cons-array', (type: (parseUnConstrainedType '(Fn a (Array a) (Array a))'), arity: ['what', 'onto'])

 desiplifyTypeAndArity = (simple) ->
-  type: quantifyAll desiplifyType simple
-  arity: ("a#{i}" for _, i in simple[0...simple.length - 1])
+  type = parseUnConstrainedType simple
+  args = collectArgs type.type.type
+  arity = if Array.isArray args then args.length else 0
+  type: type
+  arity: ("a#{i}" for i in  [0...arity - 2])
+
+parseUnConstrainedType = (string) ->
+  quantifyAll toConstrained typeCompile astize tokenize string

 desiplifyType = (simple) ->
   if Array.isArray simple
@@ -2060,6 +2269,7 @@
     removeFromSet set, v
   set

+keysOfMap =
 setToArray = (set) ->
   key for key of set.values

@@ -2146,6 +2356,9 @@
 doIntersect = (setA, setB) ->
   (subtractSets setA, setB).size isnt setA.size

+isSubset = (superSet, subSet) ->
+  (subtractSets subSet, superSet).size is 0
+
 intersectRight = (mapA, mapB) ->
   intersection = newMap()
   for k, v of mapB.values when k of mapA.values
@@ -2159,9 +2372,10 @@
 unify = (ctx, t1, t2) ->
   throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
   sub = ctx.substitution
-  ctx.extendSubstitution findSubToMatch (substitute sub, t1), (substitute sub, t2)
-
-findSubToMatch = (t1, t2) ->
+  ctx.extendSubstitution mostGeneralUnifier (substitute sub, t1), (substitute sub, t2)
+
+# Returns a substitution
+mostGeneralUnifier = (t1, t2) ->
   if t1 instanceof TypeVariable
     bindVariable t1, t2
   else if t2 instanceof TypeVariable
@@ -2170,8 +2384,8 @@
     t1.name is t2.name
       emptySubstitution()
   else if t1 instanceof TypeApp and t2 instanceof TypeApp
-    s1 = findSubToMatch t1.op, t2.op
-    s2 = findSubToMatch (substitute s1, t1.arg), (substitute s1, t2.arg)
+    s1 = mostGeneralUnifier t1.op, t2.op
+    s2 = mostGeneralUnifier (substitute s1, t1.arg), (substitute s1, t2.arg)
     joinSubs s1, s2
   else
     newMapWith "could not unify", [(printType t1), (printType t2)]
@@ -2186,14 +2400,39 @@
   else
     newMapWith variable.name, type

+# Returns a substitution
+match = (ctx, t1, t2) ->
+  if t1 instanceof TypeVariable and kindsEq (kind t1), (kind t2)
+    newMapWith t1.name, t2
+  else if t1 instanceof TypeConstr and t2 instanceof TypeConstr and
+    t1.name is t2.name
+      emptySubstitution()
+  else if t1 instanceof TypeApp and t2 instanceof TypeApp
+    s1 = match t1.op, t2.op
+    s2 = match t1.arg, t2.arg
+    s3 = mergeSubs s1, s2
+    s3 or
+      newMapWith "could not unify", [(printType t1), (printType t2)]
+  else
+    newMapWith "could not unify", [(printType t1), (printType t2)]
+
 joinSubs = (s1,s2) ->
-  concatSets s1, mapMap ((type) -> substitute s1, type), s2
+  concatMaps s1, mapMap ((type) -> substitute s1, type), s2
+
+mergeSubs = (s1, s2) ->
+  agree = (varName) ->
+    variable = new TypeVariable varName, star
+    typeEq (substitute s1, variable), (substitute s2, variable)
+  if allMap agree, keysOfMap intersectRight s1, s2
+    concatMaps s1, s2
+  else
+    null

 emptySubstitution = ->
   newMap()

 # Unlike in Jones, we simply use substitue for both variables and quantifieds
-# variables are strings, wheres quantifieds are ints
+# - variables are strings, wheres quantifieds are ints
 substitute = (substitution, type) ->
   if type instanceof TypeVariable and substitution.values
     (lookupInMap substitution, type.name) or type
@@ -2204,14 +2443,24 @@
       (substitute substitution, type.arg)
   else if type instanceof ForAll
     new ForAll type.kinds, (substitute substitution, type.type)
+  else if type instanceof Constrained
+    new Constrained (substituteList substitution, type.constraints),
+      (substitute substitution, type.type)
+  else if type instanceof ClassContraint
+    new ClassContraint type.className, substitute substitution, type.type
   else
     type
+
+substituteList = (substitution, list) ->
+  map ((t) -> substitute substitution t), list

 findFree = (type) ->
   if type instanceof TypeVariable
     newMapWith type.name, type.kind
   else if type instanceof TypeApp
     concatMaps (findFree type.op), (findFree type.arg)
+  else if type instanceof Constrained
+    concatMaps (findFree (map findFree, type.constraints)), (findFree type.type)
   else
     newMap()

@@ -2226,6 +2475,36 @@
 freshName = (nameIndex) ->
   suffix = if nameIndex > 25 then freshName (Math.floor nameIndex / 25) - 1 else ''
   (String.fromCharCode 97 + nameIndex % 25) + suffix
+
+# Normalized constraint has a type which has type variable at its head
+#   that is either ordinary type variable or type variable standing for a constructor
+#   with arbitrary type arguments
+isNormalizedConstraint = (constraint) ->
+  {type} = constraint
+  if type instanceof TypeVariable
+    yes
+  else if type instanceof TypeConstr
+    no
+  else if type instanceof TypeApp
+    isNormalizedConstraint type.op
+
+typeEq = (a, b) ->
+  if a instanceof TypeVariable and b instanceof TypeVariable or
+      a instanceof TypeConstr and b instanceof TypeConstr
+    a.name is b.name
+  else if a instanceof QuantifiedVar and b instanceof QuantifiedVar
+    a.var is b.var
+  else if a instanceof TypeApp and b instanceof TypeApp
+    (typeEq a.op, b.op) and (typeEq a.arg, b.arg)
+  else if a instanceof ForAll and b instanceof ForAll
+    typeEq a.type, b.type
+  else if a instanceof Constrained and b instanceof Constrained
+    (all zipWith typeEq, a.constraints, b.constraints) and
+      (typeEq a.type, b.type)
+  else if a instanceof ClassContraint and b instanceof ClassContraint
+    a.className is b.className and (typeEq a.type, b.type)
+  else
+    no

 # Kind (data
 #   Star
@@ -2299,8 +2578,16 @@
 class TempType
   constructor: (@type) ->

+class Constrained
+  constructor: (@constraints, @type) ->
+class ClassContraint
+  constructor: (@className, @type) ->
+
 toForAll = (type) ->
   new ForAll [], type
+
+toConstrained = (type) ->
+  new Constrained [], type

 quantifyAll = (type) ->
   quantify (findFree type), type
@@ -2327,6 +2614,10 @@
     flattenType collectArgs type
   else if type instanceof ForAll
     "(∀ #{printType type.type})"
+  else if type instanceof ClassContraint
+    "(#{type.className} #{printType type.type})"
+  else if type instanceof Constrained
+    "(: #{printType type.type} {#{(map printType, type.constraints).join ' '}})"
   else if type instanceof TempType
     "(. #{printType type.type})"
   else if Array.isArray type
@@ -2350,7 +2641,6 @@
     printType type

 flattenType = (types) ->
-  log types
   if types[0].match /^\[\d+\]$/
     "[#{types[1..].join ' '}]"
   else
@@ -2553,6 +2843,9 @@
 concat = (lists) ->
   [].concat lists...

+reverse = (list) ->
+  (map id, list).reverse()
+
 id = (x) -> x

 map = (fn, list) ->
@@ -2563,6 +2856,9 @@

 all = (list) ->
   (filter _is, list).length is list.length
+
+any = (list) ->
+  (filter _is, list).length > 0

 filter = (fn, list) ->
   list.filter fn
@@ -2589,7 +2885,7 @@
   try
     log (collapse toHtml compiled.ast)
     if result isnt (eval compiled.compiled)
-      log result
+      log "Wrong result", result
   catch e
     logError "Error in test #{teaSource}", e

@@ -2601,7 +2897,7 @@
   'more defs'
   """a 2
     b 3"""
-  "a", 1
+  "a", 2

   'constant data'
   """Color (data Red Blue)
@@ -2688,7 +2984,7 @@
 ]

 testNamed = (givenName) ->
-  for [name, source, expression, result] in pairs tests when name is givenName
+  for [name, source, expression, result] in tuplize 4, tests when name is givenName
     return source
   throw new Error "Test #{givenName} not found!"

@@ -2697,49 +2993,14 @@
     .replace(/\n?((\w+)[^>\n]+>[^>\n]+>[^>\n]+:(\d+:\d+)|.*)(?=\n)/g, '\n$2 $3')
     .replace(/\n (?=\n)/g, '')

+debug = (fun) ->
+  try
+    fun()
+  catch e
+    logError "debug", e
+
 runTests = (tests) ->
-  for [name, source, expression, result] in pairs tests
-    test source + expression, result
+  for [name, source, expression, result] in tuplize 4, tests
+    test source + " " + expression, result
   "Finished"
-# end of tests
-
-exports.compileTopLevel = compileTopLevel
-exports.compileTopLevelAndExpression = compileTopLevelAndExpression
-exports.astizeList = astizeList
-exports.astizeExpression = astizeExpression
-exports.astizeExpressionWithWrapper = astizeExpressionWithWrapper
-exports.syntaxedExpHtml = syntaxedExpHtml
-
-# exports.compileModule = (source) ->
-#   """
-#   #{library}
-#   var exports = {};
-#   #{compileDefinitionsInModule source}
-#   exports"""
-
-exports.library = library
-
-exports.isForm = isForm
-exports.isAtom = isAtom
-
-
-exports.join = join
-exports.concatMap = concatMap
-exports.concat = concat
-exports.id = id
-exports.map = map
-exports.allMap = allMap
-exports.all = all
-exports.filter = filter
-exports.partition = partition
-exports._notEmpty = _notEmpty
-exports._is = _is
-exports.__ = __
-
-exports._operator = _operator
-exports._arguments = _arguments
-exports._terms = _terms
-exports._snd = _snd
-exports._fst = _fst
-exports._labelName = _labelName
-exports._symbol = _symbol+# end of tests


Try parsing builtin ISO 8601 timestamps

Try parsing builtin ISO 8601 timestamps

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -175,10 +175,13 @@

         # create the objects for LogLine models
         res = lines.map (data) =>
-          tryTimestamp = moment data, 'HH:mm:ss.SSS'
+          # Try builtin ISO 8601 timetamp strings
+          tryTimestamp = moment data
+          # Try custom format
+          tryTimestampCustom = moment data, 'HH:mm:ss.SSS'
           # TODO: We need to give "orphaned" lines at the beginning of a file a timestamp if we have any
-          if tryTimestamp.isValid()
-            timestamp = tryTimestamp
+          if tryTimestamp.isValid() or tryTimestampCustom.isValid()
+            timestamp = if tryTimestamp.isValid() then tryTimestamp else tryTimestampCustom
             @lastTimestamp = timestamp
             @timestampIndex = 0
           else


remove child directive controllers

remove child directive controllers

--- app/assets/javascripts/angular/directives/GradeSchemeDirectives.js.coffee
+++ app/assets/javascripts/angular/directives/GradeSchemeDirectives.js.coffee
@@ -23,18 +23,12 @@
 @gradecraft.directive 'lowRange', ()->
   require: ['^gradeSchemeRanges', '^ngModel']
   restrict: 'C'
-  controller: () ->
-    this.lowFoo = () ->
-      console.log("here is low foo")
   link: (scope, elm, attrs, ctrls) ->
     ctrls[1].$validators.range = ctrls[0].low_range

 @gradecraft.directive 'highRange', ()->
   require: ['^gradeSchemeRanges', '^ngModel']
   restrict: 'C'
-  controller: () ->
-    this.highFoo = () ->
-      console.log("here is low foo")
   link: (scope, elm, attrs, ctrls) ->
    ctrls[1].$validators.range = ctrls[0].high_range
     # create an on change binding


Limit max-width to images for now to prevent resize handles in IE caused by elements having "layout"

--- src/trix/elements/trix_document_element.coffee
+++ src/trix/elements/trix_document_element.coffee
@@ -13,7 +13,7 @@
       cursor: text;
     }

-    %t * {
+    %t img {
       max-width: 100%;
     }
   """


support separate args

support separate args

--- src/core/Object2D.coffee
+++ src/core/Object2D.coffee
@@ -37,7 +37,8 @@
 			else
 				console.warn "Bu.animations[\"#{ anim }\"] doesn't exists."
 		else if anim instanceof Array
-			@animate a, args for a in anim
+			args = [args] unless args instanceof Array
+			@animate anim[i], args for own i of anim
 		else
 			anim.apply @, args


Don't mix initialize method into task instances.

--- src/deferred_task.coffee
+++ src/deferred_task.coffee
@@ -18,7 +18,7 @@

   task = {}
   _.assign(task,
-           _.omit(options, 'action', 'task'),
+           _.omit(options, 'initialize', 'action', 'task'),
            bindToContextIfFunction(task))

   task.dispatch = (action) ->


More fold APIs

More fold APIs

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -100,18 +100,27 @@
   foldsIntersectingBufferRange: (bufferRange) ->
     @foldsMarkerLayer.findMarkers(intersectsRange: bufferRange).map ({id}) -> id

-  outermostFoldsInBufferRowRange: (startRow, endRow) ->
+  foldsStartingAtBufferRow: (bufferRow) ->
+    @foldsMarkerLayer.findMarkers(startRow: bufferRow).map ({id}) -> id
+
+  outermostFoldsInBufferRowRange: (startBufferRow, endBufferRow) ->
     folds = []
     lastFoldEndRow = -1

-    for marker in @foldsMarkerLayer.findMarkers(intersectsRowRange: [startRow, endRow])
+    for marker in @foldsMarkerLayer.findMarkers(intersectsRowRange: [startBufferRow, endBufferRow])
       range = marker.getRange()
       if range.start.row > lastFoldEndRow
         lastFoldEndRow = range.end.row
-        if startRow <= range.start.row <= range.end.row < endRow
+        if startBufferRow <= range.start.row <= range.end.row < endBufferRow
           folds.push(marker.id)

     folds
+
+  largestFoldStartingAtBufferRow: (row) ->
+    @foldsStartingAtBufferRow(row)[0]
+
+  largestFoldContainingBufferRow: (row) ->
+    @foldsIntersectingBufferRange([[row, 0], [row, Infinity]])[0]

   destroyFold: (foldId) ->
     if foldMarker = @foldsMarkerLayer.getMarker(foldId)


finish switch(), implicit return

finish switch(), implicit return

--- SingularityUI/app/handlebarsHelpers.coffee
+++ SingularityUI/app/handlebarsHelpers.coffee
@@ -107,7 +107,15 @@
 Handlebars.registerHelper 'substituteTaskId', (value, taskId) ->
     value.replace('$TASK_ID', taskId)

-Handlebars.registerHelper 'labelClassForTaskState', (value, taskId) ->
-    value.replace('$TASK_ID', taskId)
-
-Handlebars.logger.level = 0;
+Handlebars.registerHelper 'getLabelClass', (state) ->
+    switch state
+        when 'TASK_STARTING', 'TASK_CLEANING'
+            'warning'
+        when 'TASK_STAGING', 'TASK_LAUNCHED', 'TASK_RUNNING'
+            'info'
+        when 'TASK_FINISHED'
+            'success'
+        when 'TASK_KILLED', 'TASK_LOST', 'TASK_FAILED', 'TASK_LOST_WHILE_DOWN'
+            'danger'
+        else
+            'default'


Fix log route

Fix log route

--- admin/static/coffee/servers/machine.coffee
+++ admin/static/coffee/servers/machine.coffee
@@ -54,7 +54,7 @@
             )
             @data = new MachineView.Data model: @model
             @logs = new LogView.Container
-                route: "/ajax/log/"+@model.get('id')+"_?"
+                route: "/ajax/log/"+@model.get('id')+"?"
                 type: 'machine'

             machines.on 'remove', @check_if_still_exists


use better function name in JS: s/showStep/setLinkField/

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -76,6 +76,8 @@
     ajax =
       url: @form.attr("action")
       dataType: "json"
+      # beforeSubmit: (data) =>
+      #   console.log "Sending these data to the server: #{JSON.stringify data}"
       success: (serverData) =>
         this.handleServerData serverData
         # console.log "Successful form POST; got #{JSON.stringify(serverData)}"
@@ -112,10 +114,11 @@
     @form.formwizard("option", remoteAjax: this.ajaxEvents())

   goToStepId: (stepId) ->
-    this.showStep(stepId)
+    this.setLinkField(stepId)
     @form.formwizard("next")

   # TODO: Try linking to the same step rather than cloning it.
+  # I'm becoming more and more convinced that that won't work, however. And this isn't as bad as it used to be.
   repeatStep: (step) ->
     if $("fieldset##{step.name}").length is 0
       $sourceStep = this.findStep(step.repeatOf.fieldset)
@@ -124,9 +127,9 @@
       $clonedStep.attr("id", step.name)
       $sourceStep.after($clonedStep)
       this.updateSteps()
-    this.showStep step.name
-
-  showStep: (stepId) ->
+    this.setLinkField step.name
+
+  setLinkField: (stepId) ->
     inputId = "_ht_link_to_#{stepId}"
     this.setHiddenInput "_ht_step_link", stepId, inputId, @linkClass, this.currentStep()

@@ -288,7 +291,7 @@
         this.repeatStep(currentStep)
       else
         # console.log "Showing step #{currentStep.fieldset}"
-        this.showStep(currentStep.fieldset)
+        this.setLinkField(currentStep.fieldset) unless this.LinkFieldSet()

   bindEvents: ->
     @form.bind "step_shown", (event, data) =>


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -150,21 +150,20 @@
     @ping = _.throttle(@pingImpl, @PING_THROTTLE_TIME)
     @ping(position, zoomSteps, direction)

-  pingImpl : (position, zoomSteps, direction) ->
-
-    unless _.isEqual(position, @lastPosition) and _.isEqual(direction, @lastDirection) and _.isEqual(zoomSteps, @lastZoomSteps)
+  pingImpl : (position, zoomSteps) ->
+
+    unless _.isEqual(position, @lastPosition) and _.isEqual(zoomSteps, @lastZoomSteps)

       lastPosition = @lastPosition

       unless lastPosition
         lastPosition = [0, 0, 0]

-      unless direction
-        direction = [1, 0, 0]
+      unless lastDirection
+        @lastDirection = [1, 0, 0]

       @lastPosition = position
       @lastZoomSteps = zoomSteps.slice(0)
-      @lastDirection = direction

       console.time "ping"

@@ -191,22 +190,21 @@
         position[2] - lastPosition[2]
       ]

-      direction = [
-        direction[0] * 0.8 + newDirection[0] * 0.2,
-        direction[1] * 0.8 + newDirection[1] * 0.2,
-        direction[2] * 0.8 + newDirection[2] * 0.2
+      @lastDirection = [
+        @lastDirection[0] * 0.8 + newDirection[0] * 0.2,
+        @lastDirection[1] * 0.8 + newDirection[1] * 0.2,
+        @lastDirection[2] * 0.8 + newDirection[2] * 0.2
       ]

-      console.log direction, newDirection, loadDirection
-
       directionMax = Math.abs(Math.max(direction[0], direction[1], direction[2]))

-      loadDirection = direction
+      loadDirection = @lastDirection
       unless directionMax == 0
         loadDirection[0] /= directionMax;
         loadDirection[1] /= directionMax;
         loadDirection[2] /= directionMax;

+      console.log @lastDirection, newDirection, loadDirection

       delta_x = delta_y = delta_z = 0
       direction_x = direction_y = direction_z = 0


stop short polling for clickers when page becomes invisible

--- app/assets/javascripts/show_clicker.coffee
+++ app/assets/javascripts/show_clicker.coffee
@@ -82,7 +82,10 @@
   channel = $('#clickerChannel')
   if channel.length > 0
     adjustVoteStatus(channel)
-    window.clickerChannelId = setInterval(webNotificationPoll, 4000)
+    channel.data('interval', -1)
+    if document.visibilityState == 'visible'
+      i =  setInterval(webNotificationPoll, 5000)
+      channel.data('interval', i)
     renderMathInElement document.getElementById('clickerChannel'),
       delimiters: [
         {
@@ -107,4 +110,21 @@
         }
       ]
       throwOnError: false
+
+    document.addEventListener 'visibilitychange', ->
+      if document.visibilityState == 'visible'
+        webNotificationPoll()
+        console.log 'Visible again!'
+        console.log 'Current interval:' + channel.data('interval')
+        if channel.data('interval') == -1
+          console.log 'Interval is empty'
+          i = setInterval(webNotificationPoll, 5000)
+          channel.data('interval', i)
+          console.log 'New interval:' + channel.data('interval')
+      else
+        console.log 'Shutting down'
+        console.log channel.data('interval')
+        clearInterval(channel.data('interval'))
+        channel.data('interval', -1)
+      return
   return


[backport] fix backlog sort with doomline

--- app/coffee/modules/backlog/sortable.coffee
+++ app/coffee/modules/backlog/sortable.coffee
@@ -45,6 +45,9 @@
     # helper-in-wrong-place-when-scrolled-down-page

     link = ($scope, $el, $attrs) ->
+        getUsIndex = (us) =>
+            return $(us).index(".backlog-table-body .row")
+
         bindOnce $scope, "project", (project) ->
             # If the user has not enough permissions we don't enable the sortable
             if not (project.my_permissions.indexOf("modify_us") > -1)
@@ -83,7 +86,7 @@
                     return

                 itemUs = ui.item.scope().us
-                itemIndex = ui.item.index()
+                itemIndex = getUsIndex(ui.item)

                 deleteElement(ui.item)

@@ -99,7 +102,7 @@
                     return $(item).index()

                 index = _.min _.map items, (item) ->
-                    return $(item).index()
+                    return getUsIndex(item)

                 us = _.map items, (item) ->
                     item = $(item)