DE17379: Fields column should resize dynamically based on content

--- kanban/ColumnSettingsFieldSpec.coffee
+++ kanban/ColumnSettingsFieldSpec.coffee
@@ -33,33 +33,18 @@
       expect(@field.getErrors().length).toBe 1
       expect(@field.getErrors()[0]).toBe 'At least one column must be shown.'

-  it 'should display saved pref data for field when form loaded', ->
-    @_assertFieldSetWithPredefinedData(
-      shouldShowColumnLevelFieldPicker: false
-      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'In-Progress', 'No', '∞', '--No Mapping--']
-    )
+  it 'should display 4 columns with the scheduleStateMapping picker as the last column', ->
+     @_createKanbanSettingsField(
+       renderTo: 'testDiv',
+       shouldShowColumnLevelFieldPicker: false
+     )
+     @_refreshField()

-  it 'should submit columns data for field when user saves form', ->
-    @_assertFieldSetWithPredefinedData(
-      shouldShowColumnLevelFieldPicker: false
-      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'In-Progress', 'No', '∞', '--No Mapping--']
-    )
+     @waitForCallback(@readyCallback).then =>
+       expect(@field._grid.columns.length).toBe 4
+       expect(@field._grid.columns[3].dataIndex).toBe 'scheduleStateMapping'

-    @waitForCallback(@readyCallback).then =>
-      data = @field.getSubmitData()
-      expect(data.foo).toBe @value
-
-  it 'should submit columns data with alwaysSelected cardFields if no fields set when user saves form', ->
-    @_assertFieldSetWithPredefinedData(
-      shouldShowColumnLevelFieldPicker: true
-      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'mycardfield', 'In-Progress', 'No', '∞', '--No Mapping--', 'FormattedID,Name,Owner']
-    )
-
-    @waitForCallback(@readyCallback).then =>
-      data = @field.getSubmitData()
-      expect(data.foo).toBe @value
-
-  it 'should create column field picker if shouldShowColumnLevelFieldPicker enabled', ->
+  it 'should display 5 columns with the cardFields field picker as the last column if shouldShowColumnLevelFieldPicker on', ->
     @_createKanbanSettingsField(
       renderTo: 'testDiv',
       shouldShowColumnLevelFieldPicker: true
@@ -70,26 +55,46 @@
       expect(@field._grid.columns.length).toBe 5
       expect(@field._grid.columns[4].dataIndex).toBe 'cardFields'

-  it 'should not create column field picker if shouldShowColumnLevelFieldPicker disabled', ->
-    @_createKanbanSettingsField(
-      renderTo: 'testDiv',
+  it 'should display saved columns pref data', ->
+    @_assertFieldSetWithPredefinedData(
       shouldShowColumnLevelFieldPicker: false
+      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'In-Progress', 'No', '∞', '--No Mapping--']
     )
-    @_refreshField()
+
+  it 'should display saved columns pref data with a cardFields column when shouldShowColumnLevelFieldPicker on', ->
+    @_assertFieldSetWithPredefinedData(
+      shouldShowColumnLevelFieldPicker: true
+      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'mycardfield', 'In-Progress', 'No', '∞', '--No Mapping--', 'FormattedID, Name, Owner']
+    )
+
+  it 'should display saved columns pref data with default cardFields values when no cardFields saved and shouldShowColumnLevelFieldPicker on', ->
+    @_assertFieldSetWithPredefinedData(
+      shouldShowColumnLevelFieldPicker: true
+      defaultCardFields: 'FooBar,MyField'
+      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'mycardfield', 'In-Progress', 'No', '∞', '--No Mapping--', 'FooBar, MyField']
+    )
+
+  it 'should submit columns pref data when user saves settings', ->
+    @_assertFieldSetWithPredefinedData(
+      shouldShowColumnLevelFieldPicker: false
+      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'In-Progress', 'No', '∞', '--No Mapping--']
+    )

     @waitForCallback(@readyCallback).then =>
-      expect(@field._grid.columns.length).toBe 4
-      expect(@field._grid.columns[3].dataIndex).toBe 'scheduleStateMapping'
+      data = @field.getSubmitData()
+      expect(data.foo).toBe @value

-  it 'should display saved pref data for field with cardFields', ->
-    defaultCardFields = 'FooBar,MyField'
+  it 'should submit columns pref data with cardFields when user saves settings and shouldShowColumnLevelFieldPicker on', ->
     @_assertFieldSetWithPredefinedData(
       shouldShowColumnLevelFieldPicker: true
-      defaultCardFields: defaultCardFields
-      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'mycardfield', 'In-Progress', 'No', '∞', '--No Mapping--', defaultCardFields]
+      expectedValues: ['Defined', 'Yes', '2', 'Defined', 'mycardfield', 'In-Progress', 'No', '∞', '--No Mapping--', 'FormattedID, Name, Owner']
     )

-  it 'should destroy grid when destroyed', ->
+    @waitForCallback(@readyCallback).then =>
+      data = @field.getSubmitData()
+      expect(data.foo).toBe @value
+
+  it 'should destroy columns grid when destroyed', ->
     @_createKanbanSettingsField(
       renderTo: 'testDiv'
     )


Add a rooms param to notify method to notify in multiple rooms

--- sensor.coffee
+++ sensor.coffee
@@ -14,8 +14,8 @@

   fire: () ->

-  notify: (text) ->
-    @robot.messageRoom @room, text
+  notify: (text, rooms = [@room]) ->
+    @robot.messageRoom(room, text) for room in [].concat rooms

   name: () ->
     @constructor.name


Making migrations apply in global order.

This is potentially a backwards incompatible change. In the case of issues you probably just have to reorder files with existing migrations in a way that they match the global order.

--- server.coffee
+++ server.coffee
@@ -3,11 +3,13 @@

 globals = @

+allMigrationDescriptors = []
+
 try
-  # Migration of migrations collection
+  # Migration of migrations collection.
   new DirectCollection('migrations').renameCollection 'peerdb.migrations'
 catch error
-  throw error unless /source namespace does not exist/.test "#{ error }"
+  throw error unless /source namespace does not exist/.test "#{error}"

 # Fields:
 #   serial
@@ -19,7 +21,8 @@
 #   timestamp
 #   migrated
 #   all
-# We use a lower case collection name to signal it is a system collection
+#
+# We use a lower case collection name to signal it is a system collection.
 globals.Document.Migrations = new Meteor.Collection 'peerdb.migrations'

 class globals.Document._Migration
@@ -71,7 +74,7 @@
     counts

 class globals.Document.AddGeneratedFieldsMigration extends globals.Document.MinorMigration
-  # Fields is an array
+  # Fields is an array.
   constructor: (fields) ->
     super()

@@ -103,7 +106,7 @@
     counts

 class globals.Document.ModifyGeneratedFieldsMigration extends globals.Document.PatchMigration
-  # Fields is an array
+  # Fields is an array.
   constructor: (fields) ->
     super()

@@ -128,7 +131,7 @@
     counts

 class globals.Document.RemoveGeneratedFieldsMigration extends globals.Document.MajorMigration
-  # Fields is an array
+  # Fields is an array.
   constructor: (fields) ->
     super()

@@ -160,7 +163,7 @@
     counts

 class globals.Document.AddOptionalFieldsMigration extends globals.Document.MinorMigration
-  # Fields is an array
+  # Fields is an array.
   constructor: (fields) ->
     super()

@@ -187,7 +190,7 @@
     counts

 class globals.Document.AddRequiredFieldsMigration extends globals.Document.MinorMigration
-  # Fields is an object
+  # Fields is an object.
   constructor: (fields) ->
     super()

@@ -233,7 +236,7 @@
     counts

 class globals.Document.RemoveFieldsMigration extends globals.Document.MajorMigration
-  # Fields is an object
+  # Fields is an object.
   constructor: (fields) ->
     super()

@@ -280,7 +283,7 @@
     counts

 class globals.Document.RenameFieldsMigration extends globals.Document.MajorMigration
-  # Fields is object
+  # Fields is object.
   constructor: (fields) ->
     super()

@@ -309,7 +312,7 @@
       $rename: {}

     for from, to of @fields
-      # Reversed
+      # Reversed.
       update.$rename[to] = from

     count = collection.update {_schema: currentSchema}, update, {multi: true}
@@ -323,13 +326,13 @@
   constructor: (@oldName, @newName) ->
     super()

-    @name = "Renaming collection from '#{ @oldName }' to '#{ @newName }'"
+    @name = "Renaming collection from '#{@oldName}' to '#{@newName}'"

   _rename: (collection, to) ->
     try
       collection.renameCollection to
     catch error
-      throw error unless /source namespace does not exist/.test "#{ error }"
+      throw error unless /source namespace does not exist/.test "#{error}"

   forward: (document, collection, currentSchema, newSchema) ->
     assert.equal collection.name, @oldName
@@ -338,9 +341,9 @@

     collection.name = @newName

-    # We renamed the collection, so let's update all documents to new schema version
-    counts = super document, collection, currentSchema, newSchema
-    # We migrated everything
+    # We renamed the collection, so let's update all documents to new schema version.
+    counts = super document, collection, currentSchema, newSchema
+    # We migrated everything.
     counts.migrated = counts.all
     counts

@@ -351,38 +354,33 @@

     collection.name = @oldName

-    # We renamed the collection, so let's update all documents to old schema version
-    counts = super document, collection, currentSchema, newSchema
-    # We migrated everything
+    # We renamed the collection, so let's update all documents to old schema version.
+    counts = super document, collection, currentSchema, newSchema
+    # We migrated everything.
     counts.migrated = counts.all
     counts

 globals.Document.addMigration = (migration) ->
   throw new Error "Migration is missing a name" unless migration.name
   throw new Error "Migration is not a migration instance" unless migration instanceof @_Migration
-  throw new Error "Migration with the name '#{ migration.name }' already exists" if migration.name in _.pluck @Meta.migrations, 'name'
+  throw new Error "Migration with the name '#{migration.name}' already exists" if migration.name in _.pluck @Meta.migrations, 'name'

   @Meta.migrations.push migration
+  allMigrationDescriptors.push
+    document: @
+    migration: migration

 globals.Document.renameCollectionMigration = (oldName, newName) ->
   @addMigration new @_RenameCollectionMigration oldName, newName

-globals.Document.migrateForward = (untilName) ->
-  # TODO: Implement
-  throw new Error "Not implemented yet"
-
-globals.Document.migrateBackward = (untilName) ->
-  # TODO: Implement
-  throw new Error "Not implemented yet"
-
-globals.Document.migrate = ->
+globals.Document.migrateForward = (untilMigration) ->
   schemas = ['1.0.0']
   currentSchema = '1.0.0'
   currentSerial = 0

   initialName = @Meta.collection._name
   for migration in @Meta.migrations by -1 when migration instanceof @_RenameCollectionMigration
-    throw new Error "Incosistent document renaming, renaming from '#{ migration.oldName }' to '#{ migration.newName }', but current name is '#{ initialName }' (new name and current name should match)" if migration.newName isnt initialName
+    throw new Error "Inconsistent document renaming, renaming from '#{migration.oldName}' to '#{migration.newName}', but current name is '#{initialName}' (new name and current name should match)" if migration.newName isnt initialName
     initialName = migration.oldName

   migrationsPending = Number.POSITIVE_INFINITY
@@ -412,14 +410,14 @@
     ).fetch()

     if migrations[0]
-      throw new Error "Unexpected migration recorded: #{ util.inspect migrations[0], depth: 10 }" if migrationsPending < Number.POSITIVE_INFINITY
+      throw new Error "Unexpected migration recorded: #{util.inspect migrations[0], depth: 10}" if migrationsPending < Number.POSITIVE_INFINITY

       if migrations[0].migrationName is migration.name and migrations[0].oldCollectionName is currentName and migrations[0].newCollectionName is newName and migrations[0].oldVersion is currentSchema and migrations[0].newVersion is newSchema
         currentSerial = migrations[0].serial
       else
-        throw new Error "Incosistent migration recorded, expected migrationName='#{ migration.name }', oldCollectionName='#{ currentName }', newCollectionName='#{ newName }', oldVersion='#{ currentSchema }', newVersion='#{ newSchema }', got: #{ util.inspect migrations[0], depth: 10 }"
+        throw new Error "Inconsistent migration recorded, expected migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}', got: #{util.inspect migrations[0], depth: 10}"
     else if migrationsPending is Number.POSITIVE_INFINITY
-      # This is the collection name recorded as the last, so we start with it
+      # This is the collection name recorded as the last, so we start with it.
       initialName = currentName
       migrationsPending = i

@@ -436,14 +434,19 @@
       _id: 1
   ).fetch(), '_id'

-  throw new Error "Documents with unknown schema version: #{ unknownSchema }" if unknownSchema.length
+  throw new Error "Documents with unknown schema version: #{unknownSchema}" if unknownSchema.length

   updateAll = false

   currentSchema = '1.0.0'
   currentSerial = 0
   currentName = initialName
+  previousMigration = null
   for migration, i in @Meta.migrations
+    if previousMigration is untilMigration
+      break
+    previousMigration = migration
+
     if migration instanceof @PatchMigration
       newSchema = semver.inc currentSchema, 'patch'
     else if migration instanceof @MinorMigration
@@ -452,7 +455,7 @@
       newSchema = semver.inc currentSchema, 'major'

     if i < migrationsPending and migration instanceof @_RenameCollectionMigration
-      # We skip all already done rename migrations (but we run other old migrations again, just with the last known collection name)
+      # We skip all already done rename migrations (but we run other old migrations again, just with the last known collection name).
       currentSchema = newSchema
       currentName = migration.newName
       continue
@@ -464,7 +467,7 @@

     if globals.Document.migrationsDisabled
       # Migrations are disabled but we are still running
-      # the code just to compute the latest schema version
+      # the code just to compute the latest schema version.
       currentSchema = newSchema
       currentName = newName
       continue
@@ -472,7 +475,7 @@
     migration._updateAll = false

     counts = migration.forward @, new DirectCollection(currentName), currentSchema, newSchema
-    throw new Error "Invalid return value from migration: #{ util.inspect counts }" unless 'migrated' of counts and 'all' of counts
+    throw new Error "Invalid return value from migration: #{util.inspect counts}" unless 'migrated' of counts and 'all' of counts

     updateAll = true if counts.migrated and migration._updateAll

@@ -488,9 +491,10 @@
           migrated: counts.migrated
           all: counts.all
       ,
-        multi: true # To catch any errors
-
-      throw new Error "Incosistent migration record state, missing migrationName='#{ migration.name }', oldCollectionName='#{ currentName }', newCollectionName='#{ newName }', oldVersion='#{ currentSchema }', newVersion='#{ newSchema }'" unless count is 1
+        # To catch any errors.
+        multi: true
+
+      throw new Error "Incosistent migration record state, missing migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 1
     else
       count = globals.Document.Migrations.find(
         migrationName: migration.name
@@ -500,10 +504,10 @@
         newVersion: newSchema
       ).count()

-      throw new Error "Incosistent migration record state, unexpected migrationName='#{ migration.name }', oldCollectionName='#{ currentName }', newCollectionName='#{ newName }', oldVersion='#{ currentSchema }', newVersion='#{ newSchema }'" unless count is 0
+      throw new Error "Incosistent migration record state, unexpected migrationName='#{migration.name}', oldCollectionName='#{currentName}', newCollectionName='#{newName}', oldVersion='#{currentSchema}', newVersion='#{newSchema}'" unless count is 0

       globals.Document.Migrations.insert
-        # Things should not be running in parallel here anyway, so we can get next serial in this way
+        # Things should not be running in parallel here anyway, so we can get next serial in this way.
         serial: globals.Document.Migrations.findOne({}, {sort: [['serial', 'desc']]}).serial + 1
         migrationName: migration.name
         oldCollectionName: currentName
@@ -515,17 +519,17 @@
         timestamp: new Date()

     if migration instanceof @_RenameCollectionMigration
-      Log.info "Renamed collection '#{ currentName }' to '#{ newName }'"
-      Log.info "Migrated #{ counts.migrated }/#{ counts.all } document(s) (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all
+      Log.info "Renamed collection '#{currentName}' to '#{newName}'"
+      Log.info "Migrated #{counts.migrated}/#{counts.all} document(s) (from #{currentSchema} to #{newSchema}): #{migration.name}" if counts.all
     else
-      Log.info "Migrated #{ counts.migrated }/#{ counts.all } document(s) in '#{ currentName }' collection (from #{ currentSchema } to #{ newSchema }): #{ migration.name }" if counts.all
+      Log.info "Migrated #{counts.migrated}/#{counts.all} document(s) in '#{currentName}' collection (from #{currentSchema} to #{newSchema}): #{migration.name}" if counts.all

     currentSchema = newSchema
     currentName = newName

-  # We do not check for not migrated documents if migrations are disabled
+  # We do not check for not migrated documents if migrations are disabled.
   unless globals.Document.migrationsDisabled
-    # For all those documents which lack schema information we assume they have the last schema
+    # For all those documents which lack schema information we assume they have the last schema.
     @Meta.collection.update
       _schema:
         $exists: false
@@ -535,59 +539,50 @@
     ,
       multi: true

-    notMigrated = _.pluck @Meta.collection.find(
-      _schema:
-        $ne: currentSchema
-    ,
-      fields:
-        _id: 1
-    ).fetch(), '_id'
-
-    throw new Error "Not all documents migrated to the latest schema version (#{ currentSchema }): #{ notMigrated }" if notMigrated.length
-
   @Meta.schema = currentSchema

-  # Return if updateAll should be called
+  # Return if updateAll should be called.
   updateAll

-globals.Document._setupMigrations = ->
-  updateAll = @migrate()
-
-  unless globals.Document.instanceDisabled
-    @Meta.collection.find(
-      _schema:
-        $exists: false
-    ,
-      fields:
-        _id: 1
-        _schema: 1
-    ).observeChanges
-      added: globals.Document._observerCallback true, (id, fields) =>
-        # TODO: Check if schema is known and complain if not
-        # TODO: We could automatically migrate old documents if we know of newer schema
-        return if fields._schema
-
-        @Meta.collection.update id,
-          $set:
-            _schema: @Meta.schema
-
-  # Return if updateAll should be called
+globals.Document.migrateBackward = (untilMigration) ->
+  # TODO: Implement.
+  throw new Error "Not implemented yet"
+
+globals.Document._setupMigrationsObserve = ->
+  @Meta.collection.find(
+    _schema:
+      $exists: false
+  ,
+    fields:
+      _id: 1
+      _schema: 1
+  ).observeChanges
+    added: globals.Document._observerCallback true, (id, fields) =>
+      # TODO: Check if schema is known and complain if not.
+      # TODO: We could automatically migrate old documents if we know of newer schema.
+      return if fields._schema
+
+      @Meta.collection.update id,
+        $set:
+          _schema: @Meta.schema
+
+migrateAllForward = ->
+  updateAll = false
+
+  for migrationDescriptor in allMigrationDescriptors
+    updateAll = migrationDescriptor.document.migrateForward(migrationDescriptor.migration) or updateAll
+
+  # Return if updateAll should be called.
   updateAll

-# TODO: What happens if this is called multiple times? We should make sure that for each document observrs are made only once
-setupMigrations = ->
-  updateAll = false
-  # Migrate only server collections.
+# TODO: What happens if this is called multiple times? We should make sure that for each document observers are made only once.
+setupMigrationsObserve = ->
+  # Setup migrations' observe only for server collections.
   for document in globals.Document.list when document.Meta.collection._connection is Meteor.server
-    # Always run setupMigrations, don't short circuit
-    updateAll = document._setupMigrations() or updateAll
-
-  if updateAll
-    Log.info "Migrations requested updating all references..."
-    globals.Document.updateAll()
+    document._setupMigrationsObserve()

 migrations = ->
-  if globals.Document.Migrations.find({}, limit: 1).count() == 0
+  if not globals.Document.migrationsDisabled and globals.Document.Migrations.find({}, limit: 1).count() == 0
     globals.Document.Migrations.insert
       serial: 1
       migrationName: null
@@ -599,7 +594,30 @@
       migrated: 0
       all: 0

-  setupMigrations()
+  # Even with disabled migrations this computes the latest schema versions for every document.
+  updateAll = migrateAllForward()
+
+  # Check that everything has been migrated to the latest schema version.
+  unless globals.Document.migrationsDisabled
+    # Check only for server collections.
+    for document in globals.Document.list when document.Meta.collection._connection is Meteor.server
+      notMigrated = _.pluck document.Meta.collection.find(
+        _schema:
+          $ne: document.Meta.schema
+      ,
+        fields:
+          _id: 1
+      ).fetch(), '_id'
+
+      throw new Error "Not all documents migrated to the latest schema version (#{document.Meta.schema}) for '#{document.Meta._name}': #{notMigrated}" if notMigrated.length
+
+  unless globals.Document.instanceDisabled
+    setupMigrationsObserve()
+
+  # "updateAll" can be "true" only if migrations are not disabled.
+  if updateAll
+    Log.info "Migrations requested updating all references..."
+    globals.Document.updateAll()

 globals.Document.migrationsDisabled = !!process.env.PEERDB_MIGRATIONS_DISABLED

@@ -607,7 +625,7 @@
   Log.info "Skipped migrations" if globals.Document.migrationsDisabled
   # We still run the code to determine schema version and setup
   # observer to set schema version when inserting new documents,
-  # but we then inside the code skip running migrations themselves
+  # but we then inside the code skip running migrations themselves.
   migrations()

 Document = globals.Document


Push test commit for travis

Push test commit for travis

--- server.coffee
+++ server.coffee
@@ -26,5 +26,5 @@
 router.route('/index').get routes.index
 router.route('/birds').get routes.species

-app.listen port, ->
+app.listen port,'localhost', ->
     console.log "server #{meta.name} start listenning on port #{port}."


Use combined logger, bodyParser.json

Use combined logger, bodyParser.json

--- server.coffee
+++ server.coffee
@@ -2,7 +2,6 @@
 bodyParser         = require 'body-parser'
 errorHandler       = require 'errorhandler'
 meshbluHealthcheck = require 'express-meshblu-healthcheck'
-debug              = require('debug')('nanocyte-engine-simple:server')
 morgan             = require 'morgan'
 MessagesController = require './src/controllers/messages-controller'

@@ -11,8 +10,8 @@
 PORT  = process.env.PORT ? 80

 app = express()
-app.use morgan('dev', immediate: false)
-app.use bodyParser()
+app.use morgan('combined', immediate: false)
+app.use bodyParser.json({limit: '50mb'})
 app.use errorHandler()
 app.use meshbluHealthcheck()


stripe command to c.coffee

stripe command to c.coffee

--- src/c.coffee
+++ src/c.coffee
@@ -24,8 +24,8 @@
         return db

 # get a connection to the db
-global.db = ->
-    return global.db = get_db()
+global.db = (cb) ->
+    return global.db = get_db(cb)
 console.log("db() -- sets global variable db to a database")

 global.gcloud = ->
@@ -199,3 +199,8 @@
     if filename.slice(filename.length - 5) != '.json'
         filename += '.json'
     JSON.parse(fs.readFileSync(filename))
+
+global.stripe = (account_id) ->
+    get_db (err, db) ->
+        db.stripe_update_customer(account_id:account_id,cb:done())
+console.log 'stripe [account_id] -- update stripe info about user'


Remove unneeded attachToDom() call in spec

--- src/packages/markdown-preview/spec/markdown-preview-spec.coffee
+++ src/packages/markdown-preview/spec/markdown-preview-spec.coffee
@@ -9,7 +9,6 @@
     rootView = new RootView(require.resolve('fixtures/markdown'))
     atom.loadPackage("markdown-preview")
     markdownPreview = MarkdownPreview.instance
-    rootView.attachToDom()

   afterEach ->
     rootView.deactivate()


Revert "removed credentials for cors"

Revert "removed credentials for cors"

This reverts commit f62c78b4ebeb524d1141c43b6223f7e7411283a8.

--- app.coffee
+++ app.coffee
@@ -18,7 +18,7 @@
 app.use express.bodyParser()
 app.use express.methodOverride()
 app.use express.session secret: process.env.SESSION_SECRET
-app.use cors({origin: true, headers: ['X-Requested-With']})
+app.use cors({origin: true, credentials: true, headers: ['X-Requested-With']})

 app.use passport.initialize()
 app.use passport.session()


require coffekup from lib

require coffekup from lib

--- app.coffee
+++ app.coffee
@@ -2,7 +2,7 @@
 sys = require("sys")
 http = require('http')
 _ = require('underscore')
-coffeekup = require('coffeekup')
+coffeekup = require('./lib/coffeekup.coffee')
 meryl = require('meryl')
 redis = require("redis")
 client = redis.createClient(9010, 'bass.redistogo.com')


up timeout to 6 mins

up timeout to 6 mins

--- app.coffee
+++ app.coffee
@@ -21,7 +21,7 @@
 # Compile requests can take longer than the default two
 # minutes (including file download time), so bump up the
 # timeout a bit.
-TIMEOUT = threeMinutes = 3 * 60 * 1000
+TIMEOUT = 6 * 60 * 1000
 app.use (req, res, next) ->
 	req.setTimeout TIMEOUT
 	res.setTimeout TIMEOUT


Change process.title

Change process.title

--- app.coffee
+++ app.coffee
@@ -64,6 +64,8 @@
 	warn 'config.enableManualUpdate is obsolete and ignored in this version'
 if config.basePath?
 	warn "config.basePath is deprecated. Advice the reverse proxy to pass a proper 'Host' header"
+
+process.title = "PackageServer #{config.packageFolder}"

 # initialize express
 app = do express


Add in status end point

Add in status end point

--- app.coffee
+++ app.coffee
@@ -77,6 +77,9 @@

 app.post("#{mountPoint}/repos/:owner/:repo/builds/latest", AuthenticationController.requireLogin, BuildController.buildLatestCommit)

+app.get "#{mountPoint}/status", (req, res, next) ->
+	res.send("github-latex-ci is alive")
+
 port = settings.internal.github_latex_ci.port
 host = settings.internal.github_latex_ci.host
 app.listen port, host, (error) ->


Remove the bodyParser. We don't need it.

--- app.coffee
+++ app.coffee
@@ -26,7 +26,6 @@
 app.set('view engine', 'jade')
 app.set('mongo url', "mongodb://127.0.0.1:27017/galegis-api-dev")
 app.use(express.favicon())
-app.use(express.bodyParser())
 app.use(express.methodOverride())
 app.use(app.router)
 app.use(require('stylus').middleware(__dirname + '/public'))


midifiers for events: control, alt, shift, meta

--- src/event.coffee
+++ src/event.coffee
@@ -36,6 +36,12 @@
         left: 37
         right: 39

+    keyModifiers =
+        alt: (event) -> event.altKey
+        control: (event) -> event.ctrlKey
+        meta: (event) -> event.metaKey
+        shift: (event) -> event.shiftKey
+
     makeEvent = (event, option) ->
         option = option or {}
         (code, args) ->
@@ -44,6 +50,7 @@
             filtered = false
             scan = true
             filter = {}
+            filterExt = []
             for k in args.slice(1)
                 if k is 'stop'
                     stop = true
@@ -62,7 +69,12 @@
                     continue
                 if not option.filtered
                     continue
+
                 filtered = true
+                if keyModifiers[k]
+                    filterExt.push k
+                    continue
+
                 if keyCodes[k]
                     k = keyCodes[k]
                 filter[k] = true
@@ -76,6 +88,11 @@
                     if filtered
                         if not filter[e.keyCode]
                             return
+                        if filterExt.length
+                            for extraKey in filterExt
+                                if not keyModifiers[extraKey](e)
+                                    return
+
                     if prevent
                         e.preventDefault()
                     if stop


Remove verbose extraneous selectors. Closes #17.

--- src/style.coffee
+++ src/style.coffee
@@ -31,16 +31,19 @@
         "  background-size: #{ width / pixelRatio }px #{ height / pixelRatio }px"
       ]
     ]
-    for image in images
-      attr = [
-        "  width: #{ image.cssw / pixelRatio }px"
-        "  height: #{ image.cssh / pixelRatio }px"
-        "  background-position: #{ -image.cssx / pixelRatio }px #{ -image.cssy / pixelRatio }px"
-      ]
-      image.style = @cssStyle attr
-      image.selector = @resolveImageSelector( image.name, image.path )
-
-      styles.push @css( [ @selector, image.selector ].join( '.' ), attr )
+
+    # Only add background-position, width and height for pixelRatio === 1.
+    if pixelRatio is 1
+      for image in images
+        attr = [
+          "  width: #{ image.cssw / pixelRatio }px"
+          "  height: #{ image.cssh / pixelRatio }px"
+          "  background-position: #{ -image.cssx / pixelRatio }px #{ -image.cssy / pixelRatio }px"
+        ]
+        image.style = @cssStyle attr
+        image.selector = @resolveImageSelector( image.name, image.path )
+
+        styles.push @css( [ @selector, image.selector ].join( '.' ), attr )

     styles.push ""
     css = styles.join "\n"


enlarge `crai` region

enlarge `crai` region


Added listener metadata ID

Added listener metadata ID

--- src/steam.coffee
+++ src/steam.coffee
@@ -214,13 +214,13 @@
     Init robot

     # GET 32-bit Steam ID
-    robot.respond /steam id( me)? (.+)/i, (res) ->
+    robot.respond /steam id( me)? (.+)/i, id: "steam.profile.id", (res) ->
         customURL = res.match[2]
         GetSteamID res, customURL, (steamID) ->
             res.reply "#{_PossessionModifier(customURL)} Steam ID is: #{steamID}"

     # GET Steam profile status
-    robot.respond /steam status (.+)/i, (res) ->
+    robot.respond /steam status (.+)/i, id: "steam.profile.status", (res) ->
         genericID = res.match[1]
         GetPlayerSummaries res, genericID, (player) ->
             status = _GetStatus player.personastate, player.communityvisibilitystate
@@ -228,7 +228,7 @@
             res.reply "#{genericID} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."

     # GET Player Dota 2 match history (overview)
-    robot.respond /dota history (.+)/i, (res) ->
+    robot.respond /dota history (.+)/i, id: "steam.dota.history", (res) ->
         genericID = res.match[1]
         GetMatchHistory res, genericID, (steamID, history) ->
             communityID = _GetCommunityID steamID
@@ -241,7 +241,7 @@
                 res.send "Match ID: #{match.match_id} | Lobby: #{DOTA_LOBBIES[match.lobby_type]} | Hero: #{hero} | #{date}"

     # GET Player Dota 2 match details
-    robot.respond /dota match (\d+)\s*(.+)?/i, (res) ->
+    robot.respond /dota match (\d+)\s*(.+)?/i, id: "steam.dota.match", (res) ->
         matchID = res.match[1]
         genericID = res.match[2]
         GetMatchDetails res, matchID, (match) ->


Use cnxauthoring settings

Use cnxauthoring settings

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -1,6 +1,7 @@
 define (require) ->
   _ = require('underscore')
   Backbone = require('backbone')
+  settings = require('settings')
   Collection = require('cs!models/contents/collection')
   Page = require('cs!models/contents/page')

@@ -182,9 +183,9 @@
         type: 'POST'
         xhrFields:
           withCredentials: true
-        url: 'http://localhost:8080/contents'
+        url: "#{location.protocol}//#{settings.cnxauthoring.host}:#{settings.cnxauthoring.port}/contents"
         data: JSON.stringify
           title: title
-        success: (data) =>
-          @add(data)
-          cb?(data)
+      .done(data) =>
+        @add(data)
+        cb?(data)


handle target host when calling agent in admin dashboards

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -31,7 +31,7 @@
         startScript: Scripts.bash.start app, version, instance, options, parameters
         stopScript: Scripts.bash.stop app, version, instance, options, parameters

-    agentUrl = Random.choice Settings.agentUrl()
+    agentUrl = if options?.targetHost then "http://#{options.targetHost}" else Random.choice Settings.agentUrl()
     HTTP.post "#{agentUrl}/app/install-and-run", callOpts, (err, result) ->
       console.log "Sent request to start instance. Response from the agent is", result
     ""


remove instance from DB (no more etcd)

remove instance from DB (no more etcd)

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -78,12 +78,12 @@

   clearInstance: (project, instance) ->
     console.log "Cluster.clearInstance #{project}, #{instance}"
-    inst = Instances.findOne project: project, name: instance
-    console.log "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true"
-    HTTP.del "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true", {}, (error, result) ->
-      console.log(error) if error
-      console.log "Cluster.clearInstance completed -> #{project}, #{instance}"
-      sync()
+    Instances.remove project: project, name: instance
+    # console.log "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true"
+    # HTTP.del "#{Settings.findOne().etcd}instances/#{project}/#{inst.application}/#{instance}?recursive=true", {}, (error, result) ->
+    #   console.log(error) if error
+    #   console.log "Cluster.clearInstance completed -> #{project}, #{instance}"
+    #   sync()
     ""

   saveApp: (name, version, definition) ->


View: Improve the serialization of model attributes, check for circular references View spec: Test attribute serialization, subviews, rendering and disposal

--- test/spec/view_spec.coffee
+++ test/spec/view_spec.coffee
@@ -1,9 +1,10 @@
 define [
   'jquery'
+  'chaplin/mediator'
   'chaplin/views/view'
   'chaplin/models/model'
   'chaplin/models/collection'
-], ($, View, Model, Collection) ->
+], ($, mediator, View, Model, Collection) ->
   'use strict'

   describe 'View', ->
@@ -11,6 +12,7 @@

     renderCalled = false
     view = model = collection = null
+    template = '
content
'

     beforeEach ->
       renderCalled = false
@@ -27,7 +29,7 @@
         collection = null

     setModel = ->
-      model = new Model
+      model = new Model()
       view.model = model

     setCollection = ->
@@ -39,7 +41,7 @@
       id: 'test-view'

       getTemplateFunction: ->
-        -> '
content
'
+        -> template

       initialize: ->
         super
@@ -154,7 +156,7 @@
       setCollection()
       spy = jasmine.createSpy()
       view.modelBind 'add', spy
-      collection.push new Model
+      collection.push new Model()
       expect(spy).toHaveBeenCalled()

     it 'should unbind handlers from model events', ->
@@ -172,7 +174,7 @@
       spy = jasmine.createSpy()
       view.modelBind 'add', spy
       view.modelUnbind 'add', spy
-      collection.push new Model
+      collection.push new Model()
       expect(spy).not.toHaveBeenCalled()

     it 'should force the context of model event handlers', ->
@@ -230,47 +232,165 @@
       model.set foo: 'bar'
       expect(input.val()).toBe 'bar'

-    it 'should add an return subviews', ->
+    it 'should add and return subviews', ->
       expect(typeof view.subview).toBe 'function'

-      subview = new View
+      subview = new View()
       view.subview 'fooSubview', subview
       expect(view.subview 'fooSubview').toBe subview
       expect(view.subviews.length).toBe 1

-      subview2 = new View
+      subview2 = new View()
       view.subview 'fooSubview', subview2
       expect(view.subview 'fooSubview').toBe subview2
       expect(view.subviews.length).toBe 1

     it 'should remove subviews', ->
-      subview = new View
+      expect(typeof view.removeSubview).toBe 'function'
+
+      # By name
+      subview = new View()
       view.subview 'fooSubview', subview
-      expect(typeof view.removeSubview).toBe 'function'
+
       view.removeSubview 'fooSubview'
       expect(typeof view.subview('fooSubview')).toBe 'undefined'
+      expect(view.subviews.length).toBe 0
+
+      # By view
+      subview = new View()
+      view.subview 'barSubview', subview
+
+      view.removeSubview subview
+      expect(typeof view.subview('barSubview')).toBe 'undefined'
+      expect(view.subviews.length).toBe 0

     it 'should render a template', ->
-
-    it 'should pass serialized attributes to the templating function', ->
+      view.render()
+      expect(view.$el.html()).toBe template
+
+    it 'should pass model attributes to the template function', ->
+      model1 = new Model
+        id: 1
+        foo: 'foo'
+      model2 = new Model
+        id: 2
+        bar: 'bar'
+      model3 = new Model
+        id: 3
+        qux: 'qux'
+      model1.set model2: model2
+      model2.set model3: model3
+      model2.set model2: model2 # Circular fun!
+      model3.set model2: model2 # Even more fun!
+
+      view = new TestView model: model1
+
+      passedTemplateData = null
+      view.getTemplateFunction = ->
+        (templateData) ->
+          passedTemplateData = templateData
+          template
+      view.render()
+
+      e = expectedTemplateData =
+        foo: 'foo'
+        model2:
+          bar: 'bar'
+          # Circular references are nullified
+          model2: null
+          model3:
+            qux: 'qux'
+            # Circular references are nullified
+            model2: null
+      d = passedTemplateData
+
+      #console.debug 'passedTemplateData', d
+
+      expect(typeof d).toBe 'object'
+      expect(d.foo).toBe e.foo
+
+      expect(typeof d.model2).toBe 'object'
+      expect(d.model2.bar).toBe e.model2.bar
+      expect(d.model2.model2).toBe e.model2.model2
+
+      expect(typeof d.model2.model3).toBe 'object'
+      expect(d.model2.model3.qux).toBe e.model2.model3.qux
+      expect(d.model2.model3.model2).toBe e.model2.model3.model2
+
+    xit 'should pass collection items to the template function', ->
+      # TODO

     it 'should dispose itself correctly', ->
-      view.dispose()
+      expect(typeof view.dispose).toBe 'function'
+      view.dispose()
+
       expect(view.disposed).toBe true
-
-      # To test:
-      # Dispose subviews
-      # Unbind handlers of global events
-      # Unbind all model handlers
-      # Remove all event handlers on this module
-      # Remove the topmost element from DOM
-      # Remove element references, options, model/collection references and subview lists
-      # Freezing
+      if Object.isFrozen
+        expect(Object.isFrozen(view)).toBe true
+
+    it 'should remove itself from the DOM', ->
+      view.$el
+        .attr('id', 'disposed-view')
+        .appendTo(document.body)
+      expect($('#disposed-view').length).toBe 1
+
+      view.dispose()
+
+      expect($('#disposed-view').length).toBe 0
+
+    it 'should dispose subviews', ->
+      subview = new View()
+      spyOn(subview, 'dispose').andCallThrough()
+      view.subview 'foo', subview
+
+      view.dispose()
+
+      expect(subview.disposed).toBe true
+      expect(subview.dispose).toHaveBeenCalled()
+
+    it 'should unsubscribe from Pub/Sub events', ->
+      pubSubSpy = jasmine.createSpy()
+      view.subscribeEvent 'foo', pubSubSpy
+
+      view.dispose()
+
+      mediator.publish 'foo'
+      expect(pubSubSpy).not.toHaveBeenCalled()
+
+    it 'should unsubscribe from model events', ->
+      setModel()
+      modelBindSpy = jasmine.createSpy()
+      view.modelBind 'foo', modelBindSpy
+
+      view.dispose()
+
+      model.trigger 'foo'
+      expect(modelBindSpy).not.toHaveBeenCalled()
+
+    it 'should remove all event handlers from itself', ->
+      viewBindSpy = jasmine.createSpy()
+      view.on 'foo', viewBindSpy
+
+      view.dispose()
+
+      view.trigger 'foo'
+      expect(viewBindSpy).not.toHaveBeenCalled()
+
+    it 'should remove instance properties', ->
+      view.dispose()
+
+      properties = [
+        'el', '$el',
+        'options', 'model', 'collection',
+        'subviews', 'subviewsByName',
+        '_callbacks'
+      ]
+      for prop in properties
+        expect(_(view).has prop).toBe false

     it 'should dispose itself when the model or collection is disposed', ->
-      model = new Model
+      model = new Model()
       view = new TestView model: model
-      spyOn view, 'dispose'
       model.dispose()
       expect(model.disposed).toBe true
       expect(view.disposed).toBe true


treat add_server for single-machine SMC separately

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -205,6 +205,10 @@
             cb           : required
         dbg = @dbg("add_server(#{opts.host})")
         dbg("adding compute server to the database by grabbing conf files, etc.")
+        if @_single
+            dbg("single machine server -- just copy files directly")
+            @_add_server_single(opts)
+            return

         if not opts.host
             i = opts.host.indexOf('-')
@@ -250,6 +254,41 @@
                     member_host  : opts.member_host
                     cb           : cb
         ], opts.cb)
+
+    _add_server_single: (opts) =>
+        opts = defaults opts,
+            timeout : 30
+            cb      : required
+        dbg = @dbg("_add_server_single")
+        dbg("adding the compute server to the database by grabbing conf files, etc.")
+        port = secret = undefined
+        {program} = require('smc-hub/compute-server')
+        async.series([
+            (cb) =>
+                async.parallel([
+                    (cb) =>
+                        fs.readFile program.port_file, (err, x) =>
+                            if x?
+                                port = parseInt(x.toString())
+                            cb(err)
+                    (cb) =>
+                        fs.readFile program.secret_file, (err, x) =>
+                            if x?
+                                secret = x.toString().trim()
+                            cb(err)
+                ], cb)
+            (cb) =>
+                dbg("update database")
+                @database.save_compute_server
+                    host         : 'localhost'
+                    dc           : ''
+                    port         : port
+                    secret       : secret
+                    experimental : false
+                    member_host  : false
+                    cb           : cb
+        ], opts.cb)
+

     # Choose a host from the available compute_servers according to some
     # notion of load balancing (not really worked out yet)


Update help texts

Update help texts

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -371,7 +371,7 @@

     render_create_button : ->
         <Button
-            style   = {fontSize:'40px', color:'#888', maxWidth:'100%'}
+            style   = {fontSize:'40px', color:'#888', maxWidth:'100%', textAlign:'left'}
             onClick = {=>@handle_click()} >
             <Icon name='plus-circle' /> {@button_text()}
         </Button>
@@ -379,13 +379,18 @@
     # TODO: Make better help text
     render_help_text : ->
         last_folder_index = @props.file_search?.lastIndexOf('/')
-        if @props.file_search?.length > 0 and last_folder_index > 1
-            if @props.file_search.indexOf('/') == last_folder_index
-                text = "This will create a folder named #{@props.file_search}"
-            else if last_folder_index == @props.file_search.length - 1
-                text = "This will create the nested folder structure #{@props.file_search}"
+        # Non-empty search and there is a file divisor ('/')
+        if @props.file_search?.length > 0 and last_folder_index > 0
+            # Ends with a '/' ie. only folders
+            if last_folder_index == @props.file_search.length - 1
+                if last_folder_index isnt @props.file_search.indexOf('/')
+                    # More than one sub folder
+                    text = "Path #{@props.file_search} will be ensured to exist."
+                else
+                    # Only one folder
+                    text = "Creates a folder named #{@props.file_search}"
             else
-                text = "This will create #{@full_path_text().slice(last_folder_index + 1)} under the folder path #{@props.file_search.slice(0, last_folder_index + 1)}"
+                text = "Creates #{@full_path_text().slice(last_folder_index + 1)} under the folder path #{@props.file_search.slice(0, last_folder_index + 1)}"
             <Alert style={marginTop: '10px'} bsStyle='info'>
                 {text}
             </Alert>
@@ -1348,9 +1353,12 @@
         file_search : ''

     render_help_info : ->
-        if @props.file_search?.length > 0 and @props.file_search?.lastIndexOf('/') < 1
+        if @props.file_search?.length > 0 and @props.files_displayed
+            firstFolderPosition = @props.file_search.indexOf('/')
             if @props.file_search == '/'
-                text = "Only showing folders"
+                text = "Showing all folders in this directory"
+            else if firstFolderPosition == @props.file_search.length - 1
+                text = "Showing folders matching #{@props.file_search.slice(0, @props.file_search.length - 1)}"
             else
                 text = "Showing files matching #{@props.file_search}"
             <Alert style={wordWrap:'break-word'} bsStyle='info'>


Only show if sidebar is open and meant to be shown

--- services/web/public/coffee/pdf/SyncButtonsView.coffee
+++ services/web/public/coffee/pdf/SyncButtonsView.coffee
@@ -44,20 +44,24 @@
 			###
 			return @

-		hide: () -> @$el.hide()
+		hide: () ->
+			@hidden = true
+			@$el.hide()

 		show: () ->
+			@hidden = false
+			@showIfNotHiddenAndOpen()
+
+		showIfNotHiddenAndOpen: () ->
 			state = @ide.editor.$splitter.layout().readState()
-			if !state.east?.initClosed
+			if !@hidden and !state.east?.initClosed
 				@$el.show()
+			else
+				@$el.hide()

 		repositionLeft: () ->
 			state = @ide.editor.$splitter.layout().readState()
 			if state.east?
 				@$el.css({right: state.east.size - 8})
-				if state.east.initClosed
-					@hide()
-				else
-					@show()
+			@showIfNotHiddenAndOpen();

-


code adjust

code adjust

--- client/Social/Activity/views/comments/inputeditwidget.coffee
+++ client/Social/Activity/views/comments/inputeditwidget.coffee
@@ -34,7 +34,7 @@
     @emit 'Submit'

     {id} = data = @getData()
-    payload        = @getPayload()
+    payload = @getPayload()

     { appManager } = KD.singletons

@@ -64,7 +64,7 @@
     KD.mixpanel "Comment edit, success", { length: activity?.body?.length }

-    getPayload: ->
+  getPayload: ->

     link_url   = @embedBox.url
     link_embed = @embedBox.getDataForSubmit()
@@ -86,6 +86,4 @@
       cssClass  : 'cancel-description'
       pistachio : 'Press Esc to cancel'

-    KD.utils.defer @bound 'setFocus'
-
-
+    KD.utils.defer @bound 'setFocus'


permission error on file save first part implemented

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -71,6 +71,8 @@
           # log "this file has changed, put a modal and block editing @fatihacet!"
           # fatihacet - this case works buggy.
         @askedForSave = no
+      else if err?.message?.indexOf? "permission denied" > -1
+        @notify "You don't have enough permission to save!", "error"

     file.on "fs.save.started", =>
       @lastContentsSentForSave = @getContents()


Fix include path

Fix include path

--- lib/layervault/services/upload.coffee
+++ lib/layervault/services/upload.coffee
@@ -1,5 +1,5 @@
 needle  = require 'needle'
-Md5Service = require '../services/md5'
+Md5Service = require './md5'

 module.exports = class UploadService
   S3_ENDPOINT = 'https://omnivore-scratch.s3.amazonaws.com'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -24,6 +24,7 @@

   tween: (duration, properties) ->
     properties = Object.extend({}, properties) # Make a local copy
+
     # TODO Extract special properties
     easing = properties.easing || "linear"


GameObject.Effect

GameObject.Effect

--- src/tween.coffee
+++ src/tween.coffee
@@ -12,7 +12,7 @@
     activeTweens: {}

   # Add events and methods here
-  self.bind "update", (elaps) ->
+  self.bind "update", (elapsedTime) ->
     for property, data of I.activeTweens
       if I.age >= data.endTime
         I[property] = data.end


--- tools/objects.coffee
+++ tools/objects.coffee
@@ -412,7 +412,6 @@

     else if objects.isArray source

-        t_index=0
         for item in source

             if objects.isObject(item) || objects.isArray(item)
@@ -423,11 +422,13 @@
                         if item.id is _item.id
                             value = _item
                             break;
-                else
-                    if value = target[t_index++]
-
-                        while value.id
-                            value = target[t_index++] ?= {}
+                ## removed this to prevent element merging if id attribute is not present
+                ##else
+                ##    t_index ?= 0
+                ##    if value = target[t_index++]
+                ##
+                ##        while value.id
+                ##            value = target[t_index++]

                 if !value
                     value = if objects.isArray(item) then [] else {}


#103: add mouse observer to target group

--- src/graph.coffee
+++ src/graph.coffee
@@ -176,6 +176,7 @@
           .data(targets)
         .enter().append("g")
           .attr('class', (d, i) -> "target target#{i}")
+          .call(observe_mouse)

       if type is 'line'
         target.append("path")
@@ -186,7 +187,6 @@
             .attr('fill', (d, i) -> if line_mode(d, i) is 'area' then color i)
             .style('fill-opacity', area_opacity)
             .attr('d', (d, i) -> line_fn(d, i)(d.values))
-            .call(observe_mouse)
       else if type is 'scatter'
         target.selectAll('circle')
             .data((d) -> d.values)


Inline `writeSync` in `safeWriteSync`

Inline `writeSync` in `safeWriteSync`

…so that we can re-use the same file descriptor to call `fdataSync`,
because using `fs.openSync(path, 'r+')` sometimes would report a
`ENOENT`.

--- src/file.coffee
+++ src/file.coffee
@@ -327,11 +327,11 @@
     undefined

   safeWriteSync: (text) ->
-    @writeSync(text)
-
     try
+      fd = fs.openSync(@getPath(), 'w')
+      fs.writeSync(fd, text)
+
       # Ensure file contents are really on disk before proceeding
-      fd = fs.openSync(@getPath(), 'r+')
       fs.fdatasyncSync(fd)
       fs.closeSync(fd)

@@ -350,6 +350,9 @@
     catch error
       if error.code is 'EACCES' and process.platform is 'darwin'
         runas ?= require 'runas'
+        # Use dd to read from stdin and write to the file path.
+        unless runas('/bin/dd', ["of=#{@getPath()}"], stdin: text, admin: true) is 0
+          throw error
         # Use sync to force completion of pending disk writes.
         if runas('/bin/sync', [], admin: true) isnt 0
           throw error
@@ -384,8 +387,8 @@
     catch error
       if error.code is 'EACCES' and process.platform is 'darwin'
         runas ?= require 'runas'
-        # Use dd to read from stdin and write to filePath, same thing could be
-        # done with tee but it would also copy the file to stdout.
+        # Use dd to read from stdin and write to the file path, same thing could
+        # be done with tee but it would also copy the file to stdout.
         unless runas('/bin/dd', ["of=#{filePath}"], stdin: text, admin: true) is 0
           throw error
       else


better hook documentation

better hook documentation

--- src/hooks.coffee
+++ src/hooks.coffee
@@ -23,17 +23,19 @@
     # # additional hooks which are useful for debugging:

     # # called when an instance is found
-    #
     # instanceFound: (chain, instance) ->

     # # called when a factory is found
-    #
     # factoryFound: (chain, factory, factoryDependencyIds) ->

-    # called right before resolved dependencies are injected into a function:
-    # inject: () ->
-    #
-    # called right before an instance is added to instances
-    # newInstance: (id, instance) ->
-    #
-    # resolve
+    # # called when a factory returns an instance
+    # instance: (chain, instance) ->
+
+    # # called when a factory returns a promise
+    # promise: (chain, promise) ->
+
+    # # called when a promise is resolved
+    # resolution: (chain, instance) ->
+
+    # called right before a factory is called
+    # factory: (chain, factory, arguments) ->


:bug: fix behavior of navigation from link

--- src/renderer/components/link-component.coffee
+++ src/renderer/components/link-component.coffee
@@ -25,7 +25,10 @@

   navigate: (e) ->
     e.preventDefault()
-    @context.ctx.routeAction.navigate(@props.href)
+    href = @props.href
+    if href[0] != '/'
+      href = "#{document.location.pathname}/#{href}"
+    @context.ctx.routeAction.navigate(href)

   render: ->
     <a href={@props.href} onClick={@navigate.bind(@)} style={@props.style}>


HLSSegmenter: Use queue instead of calling inject function directly

--- src/streammachine/rewind/hls_segmenter.coffee
+++ src/streammachine/rewind/hls_segmenter.coffee
@@ -12,52 +12,11 @@
         # as an runshift and will go backward in time. We need to listen
         # and construct segments for each.

-        @injectFunc = (chunk) =>
-            # do we have a segment that this chunk should go into?
-            last_seg    = @_segments[ @_segments.length - 1 ]
-            first_seg   = @_segments[ 0 ]
+        @_qRunning = false
+        @_q = []

-            if last_seg && ( last_seg.ts <= chunk.ts < last_seg.end_ts )
-                # in our chunk going forward
-                last_seg.buffers.push chunk
-
-            else if first_seg && ( first_seg.ts <= chunk.ts < first_seg.end_ts )
-                # in our chunk going backward
-                first_seg.buffers.unshift chunk
-
-            else if !last_seg || (chunk.ts >= last_seg.end_ts)
-                # create a new segment for it
-                seg = @_createSegment chunk.ts
-                seg.buffers.push chunk
-
-                @_segments.push seg
-
-                # check whether the segment before ours should be finalized
-                l = @_segments.length
-                @_finalizeSegment @_segments[l-2] if l > 2
-
-            else if chunk.ts < first_seg.ts
-                # create a new segment for it
-                seg = @_createSegment chunk.ts
-                seg.buffers.push chunk
-
-                @_segments.unshift seg
-
-                # check whether the segment after ours should be finalized
-                @_finalizeSegment @_segments[1] if @_segments.length > 2
-
-            else
-                console.log "Not sure where to place segment!!! ",
-                    chunk:chunk
-                    first:
-                        start:  first_seg.ts
-                        end:    first_seg.end_ts
-                    last:
-                        start:  last_seg.ts
-                        end:    last_seg.end_ts
-
-        @rewind.on "rpush",     @injectFunc
-        @rewind.on "runshift",  @injectFunc
+        @rewind.on "rpush",     (c) => @_queueChunk c
+        @rewind.on "runshift",  (c) => @_queueChunk c

         @rewind.on "rshift", (chunk) =>
             # Data is being removed from the rewind buffer.  we should
@@ -71,6 +30,82 @@

     #----------

+    _queueChunk: (c) ->
+        @_q.push c
+        @_runQueue()
+
+    #----------
+
+    _runQueue: ->
+        @_qRunning = true
+
+        if @_q.length > 0
+            c = @_q.shift()
+            @_inject c, => @_runQueue
+        else
+            @_qRunning = false
+
+    #----------
+
+    _inject: (chunk,cb) ->
+        # do we have a segment that this chunk should go into?
+        last_seg    = @_segments[ @_segments.length - 1 ]
+        first_seg   = @_segments[ 0 ]
+
+        if last_seg && ( last_seg.ts <= chunk.ts < last_seg.end_ts )
+            # in our chunk going forward
+            last_seg.buffers.push chunk
+            return cb()
+
+        else if first_seg && ( first_seg.ts <= chunk.ts < first_seg.end_ts )
+            # in our chunk going backward
+            first_seg.buffers.unshift chunk
+            return cb()
+
+        else if !last_seg || (chunk.ts >= last_seg.end_ts)
+            # create a new segment for it
+            seg = @_createSegment chunk.ts
+            seg.buffers.push chunk
+
+            @_segments.push seg
+
+            # check whether the segment before ours should be finalized
+            l = @_segments.length
+
+            if l > 2
+                @_finalizeSegment @_segments[l-2], cb
+            else
+                cb()
+
+            return true
+
+        else if chunk.ts < first_seg.ts
+            # create a new segment for it
+            seg = @_createSegment chunk.ts
+            seg.buffers.push chunk
+
+            @_segments.unshift seg
+
+            # check whether the segment after ours should be finalized
+            if @_segments.length > 2
+                @_finalizeSegment @_segments[1], cb
+            else
+                cb()
+
+            return true
+
+        else
+            console.log "Not sure where to place segment!!! ",
+                chunk:chunk
+                first:
+                    start:  first_seg.ts
+                    end:    first_seg.end_ts
+                last:
+                    start:  last_seg.ts
+                    end:    last_seg.end_ts
+
+    #----------
+
     _createSegment: (ts) ->
         seg_id = Math.floor( Number(ts) / 1000 / @segment_length )

@@ -81,13 +116,12 @@

     #----------

-    _finalizeSegment: (segment) ->
+    _finalizeSegment: (segment,cb) ->
         duration = 0
         segment.data        = Buffer.concat( _(segment.buffers).chain().sortBy("ts").collect((b) -> duration += b.duration; b.data).value() )
         segment.duration    = duration
         segment.header      = null

-        #segment.buffers.length = 0
         delete segment.buffers

         # add the segment to our finalized list
@@ -99,6 +133,8 @@
         # add the segment to our index lookup
         @segment_idx[ segment.id ] = segment

+        cb()
+
     #----------

     pumpSegment: (id,cb) ->


dyn-new broker address fix

dyn-new broker address fix

--- config/main.dyn-new.coffee
+++ config/main.dyn-new.coffee
@@ -106,7 +106,7 @@
       version   : version
       mainUri   : 'http://new.koding.com:4040'
       broker    :
-        sockJS  : "https://mq.koding.com:#{brokerPort}/subscribe"
+        sockJS  : "https://new.koding.com:#{brokerPort}/subscribe"
       apiUri    : 'https://api.koding.com'
       # Is this correct?
       appsUri   : 'https://app.koding.com'


CollaborativeEditorPane: Support to select theme from argument

--- client/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
+++ client/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
@@ -78,16 +78,19 @@
       message: "$0 saved #{@getData().name}"
       by     : KD.nick()

-  setEditorTheme: ->
-    if document.getElementById "codemirror-ambiance-style"
-      return  @codeMirrorEditor.setOption "theme", "ambiance"
-    link       = document.createElement "link"
-    link.rel   = "stylesheet"
-    link.type  = "text/css"
-    link.href  = "#{cdnRoot}/theme/ambiance.css"
-    link.id    = "codemirror-ambiance-style"
-    document.head.appendChild link
-    @codeMirrorEditor.setOption "theme", "ambiance"
+  setEditorTheme: (theme = 'ambiance')->
+
+    styleId = "codemirror-#{theme}-style"
+
+    unless document.getElementById styleId
+      link       = document.createElement "link"
+      link.rel   = "stylesheet"
+      link.type  = "text/css"
+      link.href  = "#{cdnRoot}/theme/#{theme}.css"
+      link.id    = styleId
+      document.head.appendChild link
+
+    @codeMirrorEditor.setOption "theme", theme

   setEditorMode:(fileExtension) ->


not everyone has a storage

not everyone has a storage

--- client/landing/app/MainApp/sidebar/findersettingsbuttonview.coffee
+++ client/landing/app/MainApp/sidebar/findersettingsbuttonview.coffee
@@ -41,8 +41,7 @@

   hideFiles:(state)->
     @appStorage.fetchStorage (storage)=>
-      unless storage
-        return KD.showError "An error occured. Please try again later."
+      return  unless storage
       @appStorage.setValue "hideDotFiles", state
       @emit "dotFileStateChanged", state


Removed no longer used view

Removed no longer used view

--- core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
@@ -1,41 +0,0 @@
-class window.SubCommentsView extends Backbone.Marionette.CollectionView
-  className: 'sub-comments'
-  emptyView: Backbone.Factlink.EmptyLoadingView
-  itemView: ReactView
-
-  itemViewOptions: (model) ->
-    if model instanceof SubComment
-      component: ReactSubComment(model: model)
-    else # emptyView
-      collection: @collection
-
-  _initialEvents: ->
-    @listenTo @collection, "add remove sync", @render
-
-  initialize: ->
-    @collection.fetch()
-
-    if Factlink.Global.signed_in
-      @_addViewContainer = new ReactView
-        component: new ReactSubCommentsAdd
-          addToCollection: @collection
-
-  onRender: ->
-    return if @collection.loading()
-
-    @closeEmptyView() if @collection.length <= 0
-
-    # Cannot use region as the AddView needs to be sibling in the DOM tree of the other SubCommentContainerViews
-    if Factlink.Global.signed_in
-      @_renderAddViewContainer()
-      @$el.append @_addViewContainer.el
-
-  _renderAddViewContainer: ->
-    return if @_addViewContainerRendered
-
-    @_addViewContainer.render()
-    @_addViewContainerRendered = true
-
-  onClose: ->
-    if Factlink.Global.signed_in
-      @_addViewContainer.close()


Added RequireJS json plugin support

Added RequireJS json plugin support

--- src/trace.coffee
+++ src/trace.coffee
@@ -25,6 +25,7 @@

   foundModuleNames = []
   textFiles = {}
+  jsonFiles = {}

   resolveModuleName = (moduleName, relativeTo = "") ->

@@ -33,6 +34,12 @@
     # get rid of text! prefix
     if (isText)
       moduleName = moduleName.replace('text!', '')
+
+    isJson = (moduleName.indexOf('json!') != -1)
+
+    # get rid of text! prefix
+    if (isJson)
+      moduleName = moduleName.replace('json!', '')

     # deal with module path prefixes
     if config.paths and !config.paths[moduleName]
@@ -54,6 +61,10 @@
     if (isText)
       textFiles[moduleName] = true

+    # add resolved name to list of json files
+    if (isJson)
+      jsonFiles[moduleName] = true
+
     return moduleName

@@ -116,12 +127,14 @@
     module = null
     isTextFile = !!textFiles[moduleName]

+    isJsonFile = !!jsonFiles[moduleName]
+
     # console.log("Resolving", moduleName, fileName)

     async.waterfall([

       (callback) ->
-        fileLoader(fileName, callback, isTextFile)
+        fileLoader(fileName, callback, isTextFile || isJsonFile)

       (file, callback) ->

@@ -140,6 +153,9 @@

         if (isTextFile)
           file.stringContents = 'define(function(){ return ' + JSON.stringify(file.stringContents) + '; });'
+
+        if (isJsonFile)
+          file.stringContents = 'define(function(){ return JSON.parse(' + JSON.stringify(file.stringContents) + '); });';

         module = new Module(moduleName, file)
         callback(null, file)


Router: Destroy router instance after each test.

--- src/tests/core/test_router.coffee
+++ src/tests/core/test_router.coffee
@@ -14,6 +14,10 @@
     router.addRoute '/activity',    -> flag = 'activity'
     router.addRoute '/profile',     -> flag = 'profile'
     router.addRoute '/profile/:id', -> flag = 'profile with id'
+
+
+  afterEach ->
+    router.destroy()

   it 'should extend spark.core.Object', ->


Cut long lines.

Cut long lines.

--- src/views.coffee
+++ src/views.coffee
@@ -148,7 +148,8 @@
                 location = unit.get('location')
                 if location?
                     coords = location.coordinates
-                    popup = L.popup(closeButton: false, autoPan: false).setContent "
#{unit.get_text 'name'}
"
+                    html_content = "
#{unit.get_text 'name'}
"
+                    popup = L.popup(closeButton: false, autoPan: false).setContent html_content
                     marker = L.marker([coords[1], coords[0]], icon: icon)
                         .bindPopup(popup)


get background sync messages decoded and written to local fs

--- nsync/fs-interceptor.coffee
+++ nsync/fs-interceptor.coffee
@@ -3,7 +3,7 @@
 Sync = require './sync'
 FileStat = require './file-stat'

-serverURI = 'ws://vm02.students.learn.co:3304/something'
+serverURI = 'ws://vm02.students.learn.co:3304/background_sync'
 token     = atom.config.get('integrated-learn-environment.oauthToken')

 convert =
@@ -92,7 +92,7 @@
   #-------------

   fetch: (paths) ->
-    pathsToFetch = paths.map (path) -> convert.localToRemote(path, @localRoot)
+    pathsToFetch = paths.map (path) => convert.localToRemote(path, @localRoot)
     @send {command: 'fetch', paths: pathsToFetch}

   #--------------------
@@ -127,7 +127,8 @@
     dirname = _path.dirname(localPath)
     return unless localPath? and dirname?
     fs.makeTreeSync(dirname) unless fs.existsSync(dirname)
-    fs.writeFile(localPath, contents)
+    decoded = new Buffer(contents, 'base64').toString('utf8')
+    fs.writeFile(localPath, decoded)

   onRescue: ({message}) ->
     console.log "RESCUE: #{message}"


re-implemented ⍺!⍵ and enabled tests, issue #21

--- src/vocabulary/exclamation.coffee
+++ src/vocabulary/exclamation.coffee
@@ -50,17 +50,21 @@
     # 3!5   <=> 10  #      0   0   0       (!⍵)÷(!⍺)×!⍵-⍺
     # 5!3   <=> 0   #      0   0   1       0
     #               #      0   1   0       Domain Error
-    ##3!¯5  <=> ¯35 #      0   1   1       (¯1*⍺)×⍺!⍺-⍵+1
-    ##¯3!5  <=> 0   #      1   0   0       0
+    # 3!¯5  <=> ¯35 #      0   1   1       (¯1*⍺)×⍺!⍺-⍵+1
+    # ¯3!5  <=> 0   #      1   0   0       0
     #               #      1   0   1       Cannot arise
-    ##¯5!¯3 <=> 6   #      1   1   0       (¯1*⍵-⍺)×(|⍵+1)!(|⍺+1)
-    ##¯3!¯5 <=> 0   #      1   1   1       0
-    dyad: real (n, k) ->
-      if isInt(k, 0, 100) and isInt(n, 0, 100)
-        if n < k then return 0
-        if 2 * k > n then k = n - k # do less work
-        u = v = 1
-        for i in [0...k] by 1 then (u *= n - i; v *= i + 1)
-        u / v
-      else
-        Math.exp lnΓ(n + 1) - lnΓ(k + 1) - lnΓ(n - k + 1)
+    # ¯5!¯3 <=> 6   #      1   1   0       (¯1*⍵-⍺)×(|⍵+1)!(|⍺+1)
+    # ¯3!¯5 <=> 0   #      1   1   1       0
+    dyad: Beta = real (n, k) ->
+      r =
+        if 0 <= n < k or k < 0 <= n or n < k < 0
+          0
+        else if n < 0 <= k
+          if not isInt k then throw DomainError()
+          Math.pow(-1, k) * Beta k - n - 1, k
+        else if k <= n < 0
+          if not isInt n - k then throw DomainError()
+          Math.pow(-1, n - k) * Beta Math.abs(k + 1), Math.abs(n + 1)
+        else
+          Math.exp lnΓ(n + 1) - lnΓ(k + 1) - lnΓ(n - k + 1)
+      if isInt(n) and isInt(k) then Math.round r else r


Fix infinite loop on trying to place pugicorn pets into level editor

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -154,10 +154,10 @@
   'Raven Pet': ['Nevermore']
   'Cougar Pet': ['Kitty']
   'Frog Pet': ['Hypnotoad']
-  'Griffin Pet': ['']
-  'Pugicorn Pet': ['']
+  'Baby Griffin Pet': ['Griffin']
+  'Pugicorn Pet': ['Peggy']
   'Polar Bear Pet': ['Klondike']
-  'Wolf Pet': ['']
+  'Wolf Pup Pet': ['Wolf']
   'Horse': [
     # Animal
     'Abby'


fromNow helpers returns if no date provided


Check if body exists

Check if body exists

--- nylas-connection.coffee
+++ nylas-connection.coffee
@@ -70,12 +70,12 @@

         if error or response.statusCode > 299
           unless error
-            if _.isString(body.message)
-              error = new Error(body.message)
+            if _.isString(body?.message)
+              error = new Error(body?.message)
             else
-              error = new Error(JSON.stringify(body.message))
-          error.code = body.type if body.type
-          error.server_error = body.server_error if body.server_error
+              error = new Error(JSON.stringify(body?.message))
+          error.code = body.type if body?.type
+          error.server_error = body.server_error if body?.server_error
           logOnError(error, response, body)
           reject(error)
         else


Add timeout to re-enabling of map

Add timeout to re-enabling of map

--- www/js/views/checkins.coffee
+++ www/js/views/checkins.coffee
@@ -125,7 +125,9 @@
         })

     blurSearch: ->
-      $('#map').removeClass('hide-on-mobile')
+      setTimeout ->
+        $('#map').removeClass('hide-on-mobile')
+      , 300

     changeSectionSearch: (event) ->
       @section = $(event.target).children('option')[event.target.selectedIndex].value


remove check for jQuery in init since no longer importing it

--- src/init.coffee
+++ src/init.coffee
@@ -2,14 +2,11 @@
 {parse} = require './parser'
 {Actions} = require './actions'

-# Register jquery plugin if in broswer
-$ = {}
-if jQuery?
-  $ = jQuery
-  $.fn.storyturtle = (options) ->
-    config = $.extend true, config, options
-    @each ->
-      init $(@)
+$ = jQuery
+$.fn.storyturtle = (options) ->
+  config = $.extend true, config, options
+  @each ->
+    init $(@)

 init = (game) ->
   game.hide()


update result format

update result format

--- legilimens.coffee
+++ legilimens.coffee
@@ -31,7 +31,7 @@
       switch response.statusCode
         when 200
           lastedReleaseTime = new Date(JSON.parse(body).created_at)
-          console.log "Last release time is #{lastedReleaseTime.toLocaleTimeString()} #{lastedReleaseTime.toLocaleDateString()}\n"
+          console.log "Last release time is #{lastedReleaseTime.toLocaleDateString()} #{lastedReleaseTime.toLocaleTimeString()}\n"
           resolve(lastedReleaseTime)
         when 404
           console.log "No releases before"
@@ -56,10 +56,9 @@

 printPullRequestsReport = (pullRequests) ->
   console.log "New merged pull requests:"
-  index = 1
   pullRequests.forEach (pullRequest) ->
-    console.log "#{index}. #{pullRequest.title} by @#{pullRequest.user.login}"
-    index++
+    mergedAt = new Date(pullRequest.merged_at)
+    console.log "##{pullRequest.number} #{pullRequest.title} by @#{pullRequest.user.login} (merged at #{mergedAt.toLocaleDateString()} #{mergedAt.toLocaleTimeString()})"

 getLastedReleaseTime.then (lastedReleaseTime) ->
   getClosedPullRequestsAfter(lastedReleaseTime)


Handle when the Mark is too far off the right side viewport


Newline

Newline

--- lib/sensu-dashboard/assets/javascripts/models/stash.coffee
+++ lib/sensu-dashboard/assets/javascripts/models/stash.coffee
@@ -23,4 +23,5 @@
         success: (model, response, opts) =>
           @successCallback.apply(this, [model, response, opts]) if @successCallback
         error: (model, xhr, opts) =>
-          @errorCallback.apply(this, [model, xhr, opts]) if @errorCallback+          @errorCallback.apply(this, [model, xhr, opts]) if @errorCallback
+


[#59677630] make the transaction assertion go away

--- app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
@@ -2,6 +2,10 @@
   needs: ['geoEntities', 'terms', 'units', 'sources', 'purposes']
   content: null
   currentShipment: null
+
+  init: ->
+    transaction = @get('store').transaction()
+    @set('transaction', transaction)

   columns: [
     'id', 'year', 'appendix', 'taxonConcept.fullName',
@@ -216,7 +220,9 @@
       unless shipment.get('isValid')
         shipment.send("becameValid")
       unless shipment.get('isSaving')
-        shipment.get('transaction').commit()
+        transaction = @get('transaction')
+        transaction.add(shipment)
+        transaction.commit()
       # this is here so that after another validation
       # the user gets the secondary validation warning
       shipment.set('propertyChanged', false)


Added development feature

Added development feature

--- controllers/contact.coffee
+++ controllers/contact.coffee
@@ -59,13 +59,16 @@
                     Thank you!"

         # @TODO Redirect to original page to prevent multiple requests from reloading?
-        return ctrl.app.get('mailgun').messages.send message, (error, response, body) ->
-            if response.statusCode is 200
-                res.locals.form_success = true
-            else
-                res.locals.form_failure = false
-            ctrl._render res, 'contact-us/index'
-            res.end()
+        if 'development' is ctrl.app.get 'env'
+            console.log message
+        else
+            return ctrl.app.get('mailgun').messages.send message, (error, response, body) ->
+                if response.statusCode is 200
+                    res.locals.form_success = true
+                else
+                    res.locals.form_failure = false
+                ctrl._render res, 'contact-us/index'
+                res.end()

     ctrl._render res, 'contact-us/index'


add failing test for skipWhile

add failing test for skipWhile

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -367,6 +367,18 @@
         odd = src.map((x) -> x % 2).toProperty()
         src.skipWhile(odd)
       [2,3,4,4,8,7])
+  describe "for synchronous sources", ->
+    describe "skips filter predicate holds true", ->
+      expectStreamEvents(
+        -> Bacon.fromArray([1, 2, 3, 2]).skipWhile(lessThan(3))
+        [3, 2])
+    describe "can filter by Property value", ->
+      expectStreamEvents(
+        ->
+          src = Bacon.fromArray([1,1,2,3,4,4,8,7])
+          odd = src.map((x) -> x % 2).toProperty()
+          src.skipWhile(odd)
+        [2,3,4,4,8,7])

 describe "EventStream.skipUntil", ->
   describe "skips events until one appears in given starter stream", ->


Load new entry-point

Load new entry-point

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -2,7 +2,7 @@

 fs = require('fs')
 glob = require('glob')
-jsfaker = require('../lib')
+jsfaker = require('../')

 pick = (obj, key) ->
   parts = key.split('.')


Import Range from native atom class

Import Range from native atom class

--- tests/test-refactor.coffee
+++ tests/test-refactor.coffee
@@ -1,5 +1,6 @@
 expect = require 'expect.js'
 { Refactor, Range } = require '../lib/Refactor'
+# Range = require '/Applications/Atom.app/Contents/Resources/app/node_modules/text-buffer/lib/range'

 describe 'Refactor', ->


Add missing adapter for waitUntilCommandResolves

--- src/index.coffee
+++ src/index.coffee
@@ -419,25 +419,12 @@
     wiredRemotes = []

-  ###*
-  * @name waitUntilQueryIsReady
-  * @param {Function} callback which executes the query once
-  * @description
-  *
-  * Helps to deal with commands which rely on eventual consistent data from queries.
-  * Executes the given query callback repeatedly until the query yields a result.
-  *
-  * Example:
-  * ```javascript
-  * eventricTesting.waitUntilQueryIsReady(function() {
-  *   return context.query('getAccountById', {id: 'account-1'});
-  * }).then(function(queryData) {
-  *   console.log('query returns data now', queryData);
-  * });
-  * ```
-  ###
-  waitUntilQueryIsReady: (queryCallback) ->
-    commandQueryFactory.waitUntilQueryIsReady queryCallback
+  waitUntilQueryIsReady: (args...) ->
+    commandQueryFactory.waitUntilQueryIsReady args...
+
+
+  waitUntilCommandResolves: (args...) ->
+    commandQueryFactory.waitUntilCommandResolves args...

 module.exports = eventric.testing


Define forceReindexing before callbacking

--- src/index.coffee
+++ src/index.coffee
@@ -158,8 +158,9 @@

     defineRequests requestsToSave, (err) ->
         return callback err if err
-        callback null

         # in the background
         reindex = forceIndexRequests.bind null, requestsToSave
         module.exports.forceReindexing = reindex
+
+        callback null


Add HTTPD support

Add HTTPD support

The use case for this feature is so scripts that utilize Hubot's router
(HTTPD) features can be tested. I (and many others) have several Hubot
scripts that respond to GET and POST requests via the Hubot's Web Hook
API. I use these to configure SSH keys stored in Redis (robot.brain) and
for sending notifications from build/deploy scripts so Hubot can
announce to users when a build is complete.

--- src/index.coffee
+++ src/index.coffee
@@ -3,8 +3,8 @@
 Hubot = require('hubot')

 class MockRobot extends Hubot.Robot
-  constructor: ->
-    super null, null, false, 'hubot'
+  constructor: (httpd=false) ->
+    super null, null, httpd, 'hubot'

   loadAdapter: ->
     @adapter = new Room(@)
@@ -33,8 +33,8 @@
   constructor: (scriptsPath) ->
     @scriptsPath = Path.resolve(Path.dirname(module.parent.filename), scriptsPath)

-  createRoom: ->
-    robot = new MockRobot
+  createRoom: (options={}) ->
+    robot = new MockRobot(options.httpd)

     if Fs.statSync(@scriptsPath).isDirectory()
       for file in Fs.readdirSync(@scriptsPath).sort()


Fix default supported file types

Fix default supported file types

--- src/index.coffee
+++ src/index.coffee
@@ -9,7 +9,7 @@
   @tmpDir = opts.tmpDir or require('os').tmpdir()
   @minFileSize = opts.minFileSize or 1
   @maxFileSize = opts.maxFileSize or 10000000000
-  @acceptFileTypes = opts.acceptFileTypes or /\.(gif|jpe?g|pg)$/i
+  @acceptFileTypes = opts.acceptFileTypes or /\.(gif|jpe?g|png)$/i

   @


fix import statements

fix import statements


webkit update

webkit update

--- src/main.coffee
+++ src/main.coffee
@@ -320,7 +320,7 @@
     increment_in_em = 3.5 # setting up location paramater for legend labels
     legendObject.push("" + "<div style=\"width:" + (2*@options.cell_width) + "px; position: relative; height: 0; text-align: left; color: #{label1_color_option}; font-weight: bold;" + "  -webkit-transform: translate(#{1*increment_in_em-6-increment_in_em}em,-7em); -moz-transform: translate(#{1*increment_in_em-6-increment_in_em}em,-7em); -o-transform: translate(#{1*increment_in_em-6-increment_in_em}em,-7em); -ms-transform: translate(#{1*increment_in_em-6-increment_in_em}em,-7em); transform: translate(#{1*increment_in_em-increment_in_em}em,#{label1_position_option}em);\">" + "
" + @.options.nameCols[1] + "

")
     legendObject.push("" + "<div style=\"width:" + (2*@options.cell_width) + "px; position: relative; height: 0; text-align: left; color: #{label1_color_option};" + "  -webkit-transform: translate(#{(col)*increment_in_em+1-increment_in_em}em,-6em); -moz-transform: translate(#{(col)*increment_in_em+1-increment_in_em}em,-6em); -o-transform: translate(#{(col)*increment_in_em+1-increment_in_em}em,-6em); -ms-transform: translate(#{(col)*increment_in_em+1-increment_in_em}em,-6em); transform: translate(#{(col)*increment_in_em+4-increment_in_em}em,-4em);\">" + "
" + gdrow[col].y + "

") for col in [0...ysize]
-    legendObject.push("" + "<div style=\"width:" + (2*@options.cell_width) + "px; position: relative; height: 0; text-align: left;  color: #{label2_color_option}; font-weight: bold;" + "  -webkit-transform: translate(#{xsize*increment_in_em+6+increment_in_em}em,-7em); -moz-transform: translate(#{xsize*increment_in_em+6+increment_in_em}em,-7em); -o-transform: translate(#{xsize*increment_in_em+6+increment_in_em}em,-7em); -ms-transform: translate(#{xsize*increment_in_em+6+increment_in_em}em,-7em); transform: translate(#{xsize*increment_in_em+2}em,#{ysize*increment_in_em}em); -webkit-transform: translate(#{-cell_width/7}em,#{label2_position_option}em);\">" + "
" + @.options.nameCols[0] + "

")
+    legendObject.push("" + "<div style=\"width:" + (2*@options.cell_width) + "px; position: relative; height: 0; text-align: left;  color: #{label2_color_option}; font-weight: bold;" + "  -webkit-transform: translate(#{-cell_width/7}em,#{label2_position_option}em); -moz-transform: translate(#{-cell_width/7}em,#{label2_position_option}em); -o-transform: translate(#{-cell_width/7}em,#{label2_position_option}em); -ms-transform: translate(#{-cell_width/7}em,#{label2_position_option}em); transform: translate(#{-cell_width/7}em,#{label2_position_option}em); -webkit-transform: translate(#{-cell_width/7}em,#{label2_position_option}em);\">" + "
" + @.options.nameCols[0] + "

")
     for j in [0...ysize]
         legendRowObject = []
         from = gdrow[j].i * xintervalSize


update

update

--- src/main.coffee
+++ src/main.coffee
@@ -175,6 +175,7 @@
 @_nodejs_input_to_pull_source = ( P... ) -> STPS.source P...

 #-----------------------------------------------------------------------------------------------------------
+### consider using https://github.com/dominictarr/pull-fs instead ###
 @new_file_source              = ( P... ) -> @_new_file_source_using_stps      P...
 @new_file_sink                = ( P... ) -> @_new_file_sink_using_stps        P...
 @_new_file_source_using_stps  = ( P... ) -> STPS.source FS.createReadStream   P...


add visits array to schema; remove non-schematic values

--- imports/api/vessels/vessels.coffee
+++ imports/api/vessels/vessels.coffee
@@ -8,7 +8,7 @@
 	update: -> true
 	remove: -> true

-Vessels.schemaObj =
+Vessels.schema = new SimpleSchema

 	_id:
 		type: String
@@ -26,7 +26,6 @@
 		optional: true
 	type:
 		type: String
-		dropdown: 'vesselType'
 		allowedValues: ['c', 'cr', 'mf', 'mt', 'p', 'pt', 'rc', 'f', 'ro', 't', 'unknown']
 	callsign:
 		type: String
@@ -40,30 +39,23 @@
 	mmsi:
 		type: String
 		optional: true
-	lastVisit:
-		type: Date
-		optional: true
-	harborGroup:
-		type: String
-		readonly: true
-	nextVisit:
-		type: Date
-		optional: true
-	languages:
-		type: String
-		optional: true
-	comments:
-		type: String
-		optional: true
-
-schemaObj = {}
-
-for schemaObjKey, schemaObjValue of Vessels.schemaObj
-	schemaObj[schemaObjKey] = {}
-
-	for keyAttr, keyValue of schemaObjValue when keyAttr not in ['dropdown', 'readonly']
-		schemaObj[schemaObjKey][keyAttr] = keyValue
-
-Vessels.schema = new SimpleSchema(schemaObj)
+	visits:
+		type: Array
+	'visits.$': new SimpleSchema
+		userId:
+			type: String
+		isUserVisible:
+			type: Boolean
+		harborGroupId:
+			type: String
+		date:
+			type: Date
+			optional: true
+		dateNext:
+			type: Date
+			optional: true
+		languages:
+			type: String
+			optional: true

 Vessels.attachSchema = Vessels.schema


:lipstick:

:lipstick:

--- src/main.coffee
+++ src/main.coffee
@@ -33,13 +33,12 @@

 parseCommandLine = ->
   args = optimist(process.argv[1..]).argv
-  devMode = args['dev']
   executedFrom = args['executed-from'] ? process.cwd()
   pathsToOpen = args._

   if args['resource-path']
     resourcePath = args['resource-path']
-  else if devMode
+  else if args['dev']
     resourcePath = path.join(getHomeDir(), 'github/atom')

   try


More informative variable name.

More informative variable name.

--- src/math.coffee
+++ src/math.coffee
@@ -123,8 +123,8 @@

     # Return the vector created by rotating this vector by the given angle
     # in degrees.
-    rotate : (a) ->
-        r = a * Math.PI / 180
+    rotate : (degrees) ->
+        r = degrees * Math.PI / 180

         # By rights, this should be matrix multiplication, but we
         # can hack this here.


[nav] fix search for key/value pairs

[nav] fix search for key/value pairs

--- app/assets/javascripts/models/host.js.coffee
+++ app/assets/javascripts/models/host.js.coffee
@@ -75,7 +75,7 @@
     url: ->
       s = "/rest/hosts"
       if @query
-        s += "?q=" + @query.replace(/#/, "%23")
+        s += "?q=" + encodeURIComponent(@query.replace(/#/, "%23"))
       return s

     comparator: (host) ->


add tests for builds-item

add tests for builds-item

--- tests/unit/components/builds-item-test.coffee
+++ tests/unit/components/builds-item-test.coffee
@@ -6,12 +6,29 @@
 }

 test 'it renders', (assert) ->
-  assert.expect 2

-  # creates the component instance
-  component = @subject()
-  assert.equal component._state, 'preRender'
+  attributes = {
+    id: 10000,
+    state: 'passed'
+    number: 11,
+    branch: 'foobarbranch',
+    message: undefined,
+    pullRequest: false,
+    eventType: 'push',
+    commit: {
+      sha: "a5e8093098f9c0fb46856b753fb8943c7fbf26f3",
+      branch: 'foobarbranch',
+      authorName: 'Test Author',
+      authorEmail: 'author@example.com'
+    }
+    repo: {
+      slug: 'foo/bar'
+    }
+  }
+  component = @subject(build: attributes)
+  @append()

-  # renders the component to the page
-  @render()
-  assert.equal component._state, 'inDOM'
+  ok component.$().hasClass('passed'), 'component has right status class'
+  equal component.$('.tile-main h2 small').text(), 'foobarbranch', 'component renders branch if event is push'
+  equal component.$('.tile-author img').attr('src'), 'https://www.gravatar.com/avatar/5c1e6d6e64e12aca17657581a48005d1?s=40&d=https%3A%2F%2Ftravis-ci.org%2Fimages%2Fui%2Fdefault-avatar.png', 'component renders right gravatar image'
+  equal component.$('.build-status + p a').attr('href'), 'https://github.com/foo/bar/commit/a5e8093098f9c0fb46856b753fb8943c7fbf26f3', 'component generates right commit link'


Add 'explain why' comments.

Add 'explain why' comments.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -114,7 +114,7 @@
   args: (args...) ->
     _check_args_usage(@, args...)
     _set_args_for_current_signature(@, args)
-    _check_for_duplicate_signatures(@)
+    _check_for_duplicate_signatures(@)                          # check after setting args as args are part of the signature
     _set_state(@, "args")
     @

@@ -124,7 +124,7 @@
   #
   returns: (value) ->
     _check_returns_usage(@, value)
-    _check_for_duplicate_signatures(@)
+    _check_for_duplicate_signatures(@)                          # args() may not be used so need to check for dups here as well
     _set_return_for_current_signature(@, value)
     _set_state(@, "returns")
     @
@@ -138,7 +138,7 @@
   #
   throws: (error) ->
     _check_throws_usage(@, error)
-    _check_for_duplicate_signatures(@)
+    _check_for_duplicate_signatures(@)                          # args() may not be used so need to check for dups here as well
     _set_throws_for_current_signature(@, error)
     _set_state(@, "throws")
     @


add rate limits for autocompiles

add rate limits for autocompiles

global rate limit for all users and a lower rate limit for free users

--- services/web/app/coffee/Features/Compile/CompileManager.coffee
+++ services/web/app/coffee/Features/Compile/CompileManager.coffee
@@ -34,12 +34,16 @@
 						return callback(error) if error?
 						for key, value of limits
 							options[key] = value
-						# only pass user_id down to clsi if this is a per-user compile
-						compileAsUser = if Settings.disablePerUserCompiles then undefined else user_id
-						ClsiManager.sendRequest project_id, compileAsUser, options, (error, status, outputFiles, clsiServerId, validationProblems) ->
-							return callback(error) if error?
-							logger.log files: outputFiles, "output files"
-							callback(null, status, outputFiles, clsiServerId, limits, validationProblems)
+						# Put a lower limit on autocompiles for free users
+						CompileManager._checkIfFreeAutoCompileLimitHasBeenHit options.isAutoCompile, limits.compileGroup, (err, canCompile)->
+							if !canCompile
+								return callback null, "autocompile-backoff", []
+							# only pass user_id down to clsi if this is a per-user compile
+							compileAsUser = if Settings.disablePerUserCompiles then undefined else user_id
+							ClsiManager.sendRequest project_id, compileAsUser, options, (error, status, outputFiles, clsiServerId, validationProblems) ->
+								return callback(error) if error?
+								logger.log files: outputFiles, "output files"
+								callback(null, status, outputFiles, clsiServerId, limits, validationProblems)

 	stopCompile: (project_id, user_id, callback = (error) ->) ->
@@ -75,15 +79,41 @@
 	_checkIfAutoCompileLimitHasBeenHit: (isAutoCompile, callback = (err, canCompile)->)->
 		if !isAutoCompile
 			return callback(null, true)
-		opts =
+		Metrics.inc "auto-compile"
+		opts =
 			endpointName:"auto_compile"
 			timeInterval:20
 			subjectName:"everyone"
-			throttle: 25
+			throttle: 200
 		rateLimiter.addCount opts, (err, canCompile)->
 			if err?
 				canCompile = false
 			logger.log canCompile:canCompile, opts:opts, "checking if auto compile limit has been hit"
+			if !canCompile
+				Metrics.inc "auto-compile-rate-limited"
+			callback err, canCompile
+
+
+	_checkIfFreeAutoCompileLimitHasBeenHit: (isAutoCompile, compileGroup, callback = (err, canCompile)->)->
+		if !isAutoCompile
+			return callback(null, true)
+
+		if compileGroup is "priority"
+			Metrics.inc "auto-compile-priority"
+			return callback(null, true)
+
+		Metrics.inc "auto-compile-free"
+		opts =
+			endpointName:"auto_compile"
+			timeInterval:20
+			subjectName:"free"
+			throttle: 100
+		rateLimiter.addCount opts, (err, canCompile)->
+			if err?
+				canCompile = false
+			logger.log canCompile:canCompile, opts:opts, "checking if free users auto compile limit has been hit"
+			if !canCompile
+				Metrics.inc "auto-compile-free-rate-limited"
 			callback err, canCompile

 	_ensureRootDocumentIsSet: (project_id, callback = (error) ->) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -1,6 +1,8 @@
 Function::once = ->
   ran = false
   memo = undefined
+
+

   _.once = function(func) {
     var ran = false, memo;


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -3,7 +3,7 @@
   memo = undefined

   return ->
-    if
+    return

   _.once = function(func) {
     var ran = false, memo;


cleanup

cleanup


Teamwork: Put a flag for syncWorkspace method to avoid creating panel over and over again.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -42,6 +42,9 @@
     @sessionKeysRef = @firebaseRef.child  "session_keys"

   syncWorkspace: ->
+    return if @workspaceSynced
+    @workspaceSynced = yes
+
     @workspaceRef.once "value", (snapshot) =>
       if @getOptions().sessionKey
         unless @reviveSnapshot(snapshot)?.keys


Client: Activity: Feed: Added getMenuItems method

--- client/activity/lib/components/messageitemmenu/index.coffee
+++ client/activity/lib/components/messageitemmenu/index.coffee
@@ -61,6 +61,13 @@
     onAbort            : @bound "closeBlockUserPromptModal"
     onClose            : @bound "closeBlockUserPromptModal"

+
+  getMenuItems: ->
+
+    if checkFlag('super-admin')
+    then @getAdminMenuItems()
+    else @getDefaultMenuItems()
+
   render: ->

     { message } = @props


activity: init message thread for sure when a message is loaded

--- client/activity/lib/flux/stores/messagethreadssstore.coffee
+++ client/activity/lib/flux/stores/messagethreadssstore.coffee
@@ -48,7 +48,7 @@
   ensureThread: (threads, { message }) ->

     unless threads.has message.id
-      return initThread threads, message.id
+      return threads = initThread threads, message.id

     return threads


managed: Added missing "to" from announcement text

--- client/activity/lib/views/activityannouncementwidget.coffee
+++ client/activity/lib/views/activityannouncementwidget.coffee
@@ -16,6 +16,6 @@
     return """

New: Connect your own machine!

-
You can now connect any Ubuntu Linux machine with a public IP address your Koding account.

+
You can now connect any Ubuntu Linux machine with a public IP address to your Koding account.

       Learn how.
-    """+    """


Spring cleanup ಠ_ಠ

Spring cleanup ಠ_ಠ

--- client/ide/filetree/controllers/findertreecontroller.coffee
+++ client/ide/filetree/controllers/findertreecontroller.coffee
@@ -475,9 +475,7 @@
   cmOpenFile:      (nodeView, contextMenuItem)-> @openFile nodeView
   cmPreviewFile:   (nodeView, contextMenuItem)-> @previewFile nodeView
   # cmCompile:       (nodeView, contextMenuItem)-> @compileApp nodeView
-  # cmRunApp:        (nodeView, contextMenuItem)-> @runApp nodeView
   # cmMakeNewApp:    (nodeView, contextMenuItem)-> @makeNewApp nodeView
-  # cmDownloadApp:   (nodeView, contextMenuItem)-> @downloadAppSource nodeView
   # cmPublish:       (nodeView, contextMenuItem)-> @publishApp nodeView
   cmOpenFileWithApp: (nodeView, contextMenuItem)-> @openFileWithApp  nodeView, contextMenuItem
   cmCloneRepo:     (nodeView, contextMenuItem)-> @cloneRepo nodeView


prevent image loading when the dialog is resolved

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
@@ -62,7 +62,11 @@
         utils.canvasToBlob canvas, 'image/jpeg', 0.95,
           (blob) =>
             canvas.width = canvas.height = 1
-            if file.state() != 'pending' or @file != file
+            if (
+              file.state() != 'pending' or
+              @dialogApi.state() != 'pending' or
+              @file != file
+            )
               return

             src = URL.createObjectURL(blob)


disabled double click to zoom if double click is on a node

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -70,6 +70,7 @@

     #not currently implemented
     nodeDoubleClick: (c) ->
+        d3.event.stopPropagation()
         if not alchemy.conf.extraDataSource or
             c.expanded or
             alchemy.conf.unexpandable.indexOf c.type is not -1 then return


Added support for header, format and private options.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -15,6 +15,9 @@

     options = @options
       separator: grunt.util.linefeed
+      private:   false
+      header:    false
+      format:    "markdown"

     # Iterate over all specified file groups.
     @files.forEach (f) ->
@@ -27,7 +30,7 @@
         return exist
       ).map((filepath) ->
         code = grunt.file.read filepath
-        chalkboard.compile code, {}, filepath
+        chalkboard.compile code, options, filepath
       ).join(grunt.util.normalizelf(options.separator))

       # Write the destination file.


Only concatenate buffers and create a new buffer, for new data received for a message, if there is existing buffered data.

--- src/message-io.coffee
+++ src/message-io.coffee
@@ -14,7 +14,10 @@
     @payloadBuffer = new Buffer(0)

   eventData: (data) =>
-    @packetBuffer = new Buffer(@packetBuffer.concat(data))
+    if (@packetBuffer.length > 0)
+      @packetBuffer = new Buffer(@packetBuffer.concat(data))
+    else
+      @packetBuffer = data

     while isPacketComplete(@packetBuffer)
       length = packetLength(@packetBuffer)


temporarily disable ttl behaviour

temporarily disable ttl behaviour

allow existing packs without temporary flag to expire

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -204,7 +204,7 @@
 			return callback(err) if err?
 			if not pack?
 				MongoAWS.unArchivePack project_id, doc_id, pack_id, callback
-			else if pack.expiresAt? and not pack.temporary
+			else if pack.expiresAt? and not pack.temporary and false # TODO: remove false, temporarily disabled
 				# we only need to touch the TTL on the listing of changes in the project
 				# because diffs on individual documents are always done after that
 				PackManager.increaseTTL pack, callback


Correctly reference home directory

Correctly reference home directory

--- lib/config.coffee
+++ lib/config.coffee
@@ -1,5 +1,7 @@
+path = require('path')
+
 module.exports =

 	# TODO: Should be configurable
 	baseUrl: 'https://staging.resin.io'
-	dataPrefix: '~/.resin'
+	dataPrefix: path.join(process.env.HOME, '.resin')


We need a callback here

We need a callback here

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -61,12 +61,12 @@
     @components = {}
     registerLoader.register @, (err) =>
       if err
-        return callback err if callback
+        return callback err
         throw err
       @processing = false
       @ready = true
       @emit 'ready', true
-      callback null, @components if callback
+      callback null, @components
     return

   # Load an instance of a specific component. If the


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/oscillator.coffee
+++ src/oscillator.coffee
@@ -5,7 +5,7 @@
 @constructor
 @param {Number} amplitude How much to scale the oscillator function value
 @param {Number} period How fast the osciallator function repeats
-@param {Number} offset How much to offset the created osciallator function. Useful for translating between sin and cosine functions.
+@param {Number} offset How much to offset the created oscillator function. Useful for translating between sin and cosine functions.
 ###
 Oscillator = (options={}) ->
   {amplitude, period, offset} = options


creep_actions: compiler fix

creep_actions: compiler fix

--- src/logic/creep_actions.coffee
+++ src/logic/creep_actions.coffee
@@ -65,7 +65,7 @@
   if attack_buildings(creep)
     return
   delete creep.memory.attacking
-  return_to_base(creep)
+  return_to_closest_spawn(creep)

 no_attack = (creep) ->
   if run_from_battle(creep)


feat (Session) expose the session on the supersonic global object


fix infinite loop

fix infinite loop

--- lib/new-cc.coffee
+++ lib/new-cc.coffee
@@ -710,6 +710,7 @@
                                         e = current_e.parentEnv
                                         while e isnt prior_e
                                                 add_func_env(func_envs, e)
+                                                e = e.parentEnv
                                 idx -= 1