add more assertions to config check

add more assertions to config check

--- src/celerity.coffee
+++ src/celerity.coffee
@@ -16,10 +16,14 @@
         throw new Error 'config.timespanMs argument is missing'
     unless isInt config.timespanMs
         throw new Error 'config.timespanMs argument must be an integer'
-    unless config.timespanMs?
+    if 10 > config.timespanMs
+        throw new Error 'config.timespanMs argument must be at least 10'
+    unless config.bucketCount?
         throw new Error 'config.bucketCount argument is missing'
     unless isInt config.bucketCount
         throw new Error 'config.bucketCount argument must be an integer'
+    if 1 < config.bucketCont
+        throw new Error 'config.bucketCount argument must be greater than 1'
     if config.prefix? and 'string' isnt typeof config.prefix
         throw new Error 'config.prefix argument must be a string'
     unless isInt getBucketMs(config)


Fixed child_process spawn options

Fixed child_process spawn options

--- src/dispatch.coffee
+++ src/dispatch.coffee
@@ -47,11 +47,13 @@
     else
       # i bet this doesn't work on windows.
       command = [ "/bin/sh", "-c", command ]
-      if not settings.exec.env? then settings.exec.env = process.env
-      if not settings.exec.cwd? then settings.exec.cwd = process.cwd()
-      if not settings.exec.stdio? then settings.exec.stdio = "inherit"

-      p = child_process.spawn command[0], command[1...], settings.exec
+      execSettings = settings.exec or {}
+      execSettings.env ?= process.env
+      execSettings.cwd ?= process.cwd()
+      execSettings.stdio ?= "inherit"
+
+      p = child_process.spawn command[0], command[1...], execSettings
       logging.debug "spawn #{p.pid}: #{util.inspect(command)}"
       p.on "exit", (code, signal) ->
         if signal?


DONE: now further decoupled, will fill detail by implement search sync afterward

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -45,7 +45,7 @@
   # -------------------------
   autoPreview: false
   preventAutoPreview: false
-  preventSyncToProviderEditor: false
+  preventSyncToEditor: false
   ignoreChangeOnEditor: false
   destroyed: false
   items: []
@@ -65,8 +65,6 @@
     # Special item used to translate narrow editor row to items without pain
     @promptItem = Object.freeze({_prompt: true, skip: true})
     @itemAreaStart = Object.freeze(new Point(1, 0))
-
-    @providerEditor = @provider.editor

     # Setup narrow-editor
     # -------------------------
@@ -96,8 +94,8 @@
         @rowMarker?.destroy()

     if @provider.boundToEditor
-      @bindToEditor(@provider.editor)
-      @disposables.add @providerEditor.onDidDestroy(@destroy.bind(this))
+      @setSyncToEditor(@provider.editor)
+      @disposables.add @provider.editor.onDidDestroy(@destroy.bind(this))

     @constructor.register(this)
     @disposables.add new Disposable =>
@@ -147,7 +145,7 @@
   destroy: ->
     return if @destroyed
     @destroyed = true
-    @editorSubcriptions?.dispose()
+    @syncSubcriptions?.dispose()
     @disposables.dispose()
     @editor.destroy()
     @activateProviderPane()
@@ -190,7 +188,7 @@

     if @direction is 'down' and @provider.boundToEditor
       # Prevent side scroll of narrow editor
-      point = @providerEditor.getCursorBufferPosition()
+      point = @provider.editor.getCursorBufferPosition()
       if point.isGreaterThanOrEqual(_.last(@items).point)
         return

@@ -250,7 +248,7 @@
       if @isActive()
         @selectItemForRow(@findNormalItem(1, 'next'))
       else
-        @syncToProviderEditor() if @provider.boundToEditor
+        @syncToEditor() if @provider.boundToEditor
       @ignoreChangeOnEditor = false

   renderItems: (items) ->
@@ -330,11 +328,11 @@

       @preview() if @isAutoPreview()

-  syncToProviderEditor: ->
-    return if @preventSyncToProviderEditor
+  syncToEditor: ->
+    return if @preventSyncToEditor
     # Detect item
     # - cursor position is equal or greather than that item.
-    cursorPosition = @providerEditor.getCursorBufferPosition()
+    cursorPosition = @syncingEditor.getCursorBufferPosition()
     foundItem = null
     for item in @items by -1 when item.point?.isLessThanOrEqual(cursorPosition)
       foundItem = item
@@ -360,12 +358,12 @@
     editor.decorateMarker(@rowMarker, type: 'line', class: 'narrow-result')

   preview: ->
-    @preventSyncToProviderEditor = true
+    @preventSyncToEditor = true
     @confirm(keepOpen: true).then ({editor, point}) =>
       if editor.isAlive()
         @setRowMarker(editor, point)
         @focus()
-        @preventSyncToProviderEditor = false
+        @preventSyncToEditor = false

   isNormalItem: (item) ->
     item? and not item.skip
@@ -437,21 +435,22 @@
     @ignoreChangeOnEditor = false
     range

-  bindToEditor: (editor) ->
-    @editorSubcriptions?.dispose()
-    @editorSubcriptions = new CompositeDisposable
-    @editorSubcriptions.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
-      @syncToProviderEditor() if item is editor
-
-    @editorSubcriptions.add editor.onDidStopChanging =>
+  setSyncToEditor: (editor) ->
+    @syncingEditor = editor
+    @syncSubcriptions?.dispose()
+    @syncSubcriptions = new CompositeDisposable
+    @syncSubcriptions.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
+      @syncToEditor() if item is editor
+
+    @syncSubcriptions.add editor.onDidStopChanging =>
       # Skip is not activeEditor, important to skip auto-refresh on direct-edit.
       @refresh(force: true) if isActiveEditor(editor)

-    @editorSubcriptions.add editor.onDidChangeCursorPosition (event) =>
+    @syncSubcriptions.add editor.onDidChangeCursorPosition (event) =>
       if isActiveEditor(editor) and
           (not event.textChanged) and
           (event.oldBufferPosition.row isnt event.newBufferPosition.row)
-        @syncToProviderEditor()
+        @syncToEditor()

   # vim-mode-plus integration
   # -------------------------


refactor

refactor

--- public/coffee/application/Application.coffee
+++ public/coffee/application/Application.coffee
@@ -178,6 +178,7 @@

 DS.SocketAdapter.map 'App.Box',
   box: { key: 'boxs' }
+

 # Create ember-data datastore and define our adapter
 App.store = DS.Store.create


Make window.FACTLINK equal to the Factlink object in the iframe, no funny proxy methods. Use other explicit names for the other things window.FACTLINK was used for

--- js-library/app/js/loader.coffee
+++ js-library/app/js/loader.coffee
@@ -1,4 +1,5 @@
-return if window.FACTLINK?
+return if window.FACTLINK_LOADED?
+window.FACTLINK_LOADED = true

 #### Create iframe

@@ -47,23 +48,12 @@
   coreScriptTag.onload = coreScriptTag.onreadystatechange = null
   scriptLoaded = true

-  proxy = (func) ->
-    window.FACTLINK[func] = ->
-      iframe.contentWindow.Factlink[func].apply iframe.contentWindow.Factlink, arguments
-
-  proxy 'on'
-  proxy 'off'
-  proxy 'hideDimmer'
-  proxy 'triggerClick'
-  proxy 'stopAnnotating'
-  proxy 'getSelectionInfo'
-
+  window.FACTLINK = iframe.contentWindow.Factlink
   jQuery?(window).trigger 'factlink.libraryLoaded'

 #### Load iframe with script tag

-window.FACTLINK = {}
-window.FACTLINK.iframeLoaded = ->
+window.FACTLINK_ON_IFRAME_LOAD = ->
   iframe.contentWindow.document.head.appendChild coreScriptTag

 iframeDoc = iframe.contentWindow.document
@@ -72,7 +62,7 @@
   <!DOCTYPE html>

 """


style yale header

style yale header

--- app/assets/javascripts/partials/yale-header.cjsx
+++ app/assets/javascripts/partials/yale-header.cjsx
@@ -24,15 +24,21 @@

-
+

-            Transcribe
-            <Link to='/#/about' activeClassName='selected' className='main-header-item main-header-button'>About</Link>
+
+              Transcribe
+

+
+
+              About
+

+


Consistent unambigious naming.

Consistent unambigious naming.

--- app/assets/javascripts/roofpig/Display.js.coffee
+++ app/assets/javascripts/roofpig/Display.js.coffee
@@ -57,7 +57,7 @@
     this.animate()

-  # this function is executed on each animation frame
+  # this is called for each redraw
   animate: ->
     now = (new Date()).getTime()

@@ -76,11 +76,11 @@
     if @changers[category] then @changers[category].finish()
     @changers[category] = changer

-  next: ->
+  to_next: ->
     unless @alg.at_end()
       this.add_changer('move', @alg.next_move().show_do(@world3d))

-  prev: ->
+  to_prev: ->
     unless @alg.at_start()
       this.add_changer('move', @alg.prev_move().show_undo(@world3d))

@@ -104,9 +104,9 @@
       when 'pause'
         @alg.stop()
       when 'next'
-        this.next()
+        this.to_next()
       when 'prev'
-        this.prev()
+        this.to_prev()
       when 'reset'
         this.to_start()


todo note about soundness of model loader

--- node_modules/bongo-client/src/modelloader.coffee
+++ node_modules/bongo-client/src/modelloader.coffee
@@ -10,6 +10,8 @@
     @constructor.one {@_id}, (err, model)=>
       @emit 'load', err, model

+  # TODO: I wrote this, but I'm not sure if it makes any sense.
+  #   Review this, make sure it works, and doesn't suck. C.T.
   load:(listener)->
     @once 'load', listener
     unless @isLoading


Change reducers.

Change reducers.

--- src/redux-axios-reducers.coffee
+++ src/redux-axios-reducers.coffee
@@ -36,32 +36,32 @@
     return (state=@default, action) =>
       return state_ = switch action.type

-        when @TYPES.FETCHING
-          {state..., @reduceFetching(state, action)...}
+        when @TYPES.FETCHING then @reduceFetching(state, action)

-        when @TYPES.FETCH_SUCCESS
-          {state..., @reduceSuccess(state, action)...}
+        when @TYPES.FETCH_SUCCESS then @reduceSuccess(state, action)

-        when @TYPES.FETCH_FAIL
-          {state..., @reduceFail(state, action)...}
+        when @TYPES.FETCH_FAIL then @reduceFail(state, action)

         else state

   # Reducers
-  reduceFetching: ->
-    return
-      fetching: true
+  reduceFetching: (state)->
+    return {state..., fetching: true}

   reduceSuccess: (state, action) ->
-    return
-      data: action.response.data
-      fetching: false
-      error: null
+    return {
+        state...,
+        data: action.response.data,
+        error: null,
+        fetching: false,
+    }

   reduceFail: (state, action) ->
-    return
-      error: action.error
-      fetching: false
+    return {
+        state...,
+        error: action.error
+        fetching: false
+    }

   # Make a request with axios (global or configured instance)
   request: (config) =>
@@ -166,7 +166,7 @@
       delete state.byId[singleId]
       state.data = (id for id in state.data when id != singleId)

-    return fetching: false, error: null
+    return {state..., fetching: false, error: null}

   transformConfig: (config) ->
     config = super(config)


Re-enqueueing failed downloads

Re-enqueueing failed downloads

--- creepy.coffee
+++ creepy.coffee
@@ -224,8 +224,16 @@

   crawlNext: (sha1, callback)->
     done = (error, status)=>
-      @db.hset "batch:#{@batch}:crawl_state", sha1, 'crawled', ()->
-        callback(error, status)
+      if error
+        console.dir error
+        @db.multi()
+          .rpush("batch:#{@batch}:crawl_queue", sha1)
+          .hset("batch:#{@batch}:crawl_state", sha1, 'failed')
+          .exec ()->
+            callback(error, status)
+      else
+        @db.hset "batch:#{@batch}:crawl_state", sha1, 'crawled', ()->
+          callback(error, status)

     console.log "Loading info for #{sha1}"
     @db.multi()


Emit event when snippets are fully loaded

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -54,6 +54,7 @@
     async.eachSeries snippetsDirPaths, @loadSnippetsDirectory.bind(this), @doneLoading.bind(this)

   doneLoading: ->
+    atom.packages.emit 'snippets:loaded'
     @loaded = true

   loadSnippetsDirectory: (snippetsDirPath, callback) ->


webapp/course: mention an article about how to import the CSV grades files into excel

--- src/smc-webapp/course/settings_panel.cjsx
+++ src/smc-webapp/course/settings_panel.cjsx
@@ -318,10 +318,14 @@
                 <Button onClick={@save_grades_to_py}><Icon name='file-code-o'/> Python file...</Button>
             </ButtonToolbar>

-
+

                 Export all the grades you have recorded
                 for students in your course to a csv or Python file.
-
+

+                In Microsoft Excel, you can {' '}
+
+                import the CSV file.
+

         </Panel>

     ###


increase timeout for tests

increase timeout for tests

--- src/spec/integration/products.spec.coffee
+++ src/spec/integration/products.spec.coffee
@@ -171,7 +171,7 @@
       ]
       .then -> done()
       .catch (error) -> done(_.prettify(error))
-    , 5000 # 5sec
+    , 20000 # 5sec
   , 30000 # 30sec

   it "should query using 'byQueryString'", (done) ->
@@ -199,7 +199,7 @@
         expect(result.body.results[0].slug.en).toBe slugToLookFor
         done()
       .catch (error) -> done(_.prettify(error))
-    , 5000 # 5sec
+    , 20000 # 5sec
   , 20000 # 20sec

   it 'should uses search endpoint', (done) ->
@@ -220,7 +220,7 @@
         expect(result.body.results[0].slug.en).toBe slugToLookFor
         done()
       .catch (error) -> done(_.prettify(error))
-    , 5000 # 5sec
+    , 20000 # 5sec
   , 20000 # 20sec

   it 'should search with full-text for special characters', (done) ->
@@ -239,5 +239,5 @@
         expect(result.body.results[0].name.en).toBe specialChar
         done()
       .catch (error) -> done(_.prettify(error))
-    , 5000 # 5sec
-  , 20000 # 20sec
+    , 20000 # 5sec
+  , 20000 # 20sec


Change purging message in OpServ

Change purging message in OpServ

--- handlers/opserv.coffee
+++ handlers/opserv.coffee
@@ -122,7 +122,7 @@
 			db.checkPermissionByMessage message, 'opserv.purge', (hasPermission) ->
 				if hasPermission
 					commands.purge (err) ->
-						api.replyTo message, (if err? then __("Failed to purge configuration of module “%s”", parameters) else __("Purged configuration of module %s", parameters)), no, callback
+						api.replyTo message, (if err? then __("Failed to purge module “%s”", parameters) else __("Purged module %s", parameters)), no, callback
 					, parameters
 				else
 					callback?()


Fix keybinding deactivation

Fix keybinding deactivation

--- lib/autocomplete-haskell.coffee
+++ lib/autocomplete-haskell.coffee
@@ -97,6 +97,7 @@
   deactivate: ->
     @backendHelperDisp?.dispose()
     @globalDisposables.dispose()
+    atom.keymaps.removeBindingsFromSource 'autocomplete-haskell'
     @disposables = null
     @globalDisposables = null
     @backendHelper = null


Return empty link if url not found

Return empty link if url not found

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -58,7 +58,9 @@

   createIssueUrl: (packageName, deprecation, stack) ->
-    {metadata} = atom.packages.getActivePackage(packageName)
+    return '' unless pack = atom.packages.getActivePackage(packageName)
+
+    {metadata} = pack
     repoUrl = metadata.repository?.url or metadata.repository
     title = "#{deprecation.getOriginName()} is deprecated."
     stacktrace = stack.map(({functionName, location}) -> "#{functionName} (#{location})").join("\n")


No errors for non-REST nodes

No errors for non-REST nodes

--- cursor.coffee
+++ cursor.coffee
@@ -22,17 +22,18 @@
     @forEach (row) ->

       for nodeAlias, node of row
-        nodes[node.id] ?= columns: [nodeAlias]
-        nodes[node.id].columns = _.union nodes[node.id].columns, [nodeAlias]
-        nodes[node.id] = _.extend nodes[node.id], node
-        if nodes[node.id]?.meta
-          nodes[node.id].meta = undefined
-          delete nodes[node.id].meta
+        if node?id
+          nodes[node.id] ?= columns: [nodeAlias]
+          nodes[node.id].columns = _.union nodes[node.id].columns, [nodeAlias]
+          nodes[node.id] = _.extend nodes[node.id], node
+          if nodes[node.id]?._service
+            nodes[node.id]._service = undefined
+            delete nodes[node.id]._service

-        MongoCollection.upsert
-          id: node.id
-        ,
-          $set: nodes[node.id]
+          MongoCollection.upsert
+            id: node.id
+          ,
+            $set: nodes[node.id]

     return MongoCollection


Decode query if needed

Decode query if needed

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -28,6 +28,10 @@
       if params[key] == 'cur'
         params[key] = @currentSearch && @currentSearch[key]
         redir = true
+
+    if params.q and params.q.match(/%\w{2}%/)
+      params.q = decodeURIComponent(params.q)
+      redir = true

     if params.lat == 'cur'
       params.lat = @currentSearch?.lat


added intransitive class to remove transitions from sort icons in spreadsheet

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -675,9 +675,9 @@
           else
             cls.push 'sort'
           if currentSort == col
-            cls.push 'sort-'+(if currentSortDirection then 'desc' else 'asc')
+            cls.push 'intransitive sort-'+(if currentSortDirection then 'desc' else 'asc')
           else
-            cls.push 'sortable'
+            cls.push 'intransitive sortable'
           cls

         ################################# Backend saving


Support custom S3 endpoints

Support custom S3 endpoints

--- app/coffee/S3PersistorManager.coffee
+++ app/coffee/S3PersistorManager.coffee
@@ -16,10 +16,15 @@
 Errors = require("./Errors")
 _ = require("underscore")
 awsS3 = require "aws-sdk/clients/s3"
+URL = require('url')

 thirtySeconds = 30 * 1000

 buildDefaultOptions = (bucketName, method, key)->
+	if settings.filestore.s3.endpoint
+		endpoint = "#{settings.filestore.s3.endpoint}/#{bucketName}"
+	else
+		endpoint = "https://#{bucketName}.s3.amazonaws.com"
 	return {
 			aws:
 				key: settings.filestore.s3.key
@@ -27,32 +32,47 @@
 				bucket: bucketName
 			method: method
 			timeout: thirtySeconds
-			uri:"https://#{bucketName}.s3.amazonaws.com/#{key}"
+			uri:"#{endpoint}/#{key}"
 	}

-defaultS3Client = new awsS3({
-	credentials:
-		accessKeyId: settings.filestore.s3.key,
-		secretAccessKey: settings.filestore.s3.secret
-})
+getS3Options = (credentials) ->
+	options =
+		credentials:
+			accessKeyId: credentials.auth_key
+			secretAccessKey: credentials.auth_secret
+
+	if settings.filestore.s3.endpoint
+		options.endpoint = settings.filestore.s3.endpoint
+		options.sslEnabled = false
+
+	return options
+
+defaultS3Client = new awsS3(getS3Options({
+	auth_key: settings.filestore.s3.key,
+	auth_secret: settings.filestore.s3.secret
+}))

 getS3Client = (credentials) ->
 	if credentials?
-		return new awsS3({
-			credentials:
-				accessKeyId: credentials.auth_key
-				secretAccessKey: credentials.auth_secret
-		})
+		return new awsS3(getS3Options(credentials))
 	else
 		return defaultS3Client

+getKnoxClient = (bucketName) =>
+	options =
+		key: settings.filestore.s3.key
+		secret: settings.filestore.s3.secret
+		bucket: bucketName
+	if settings.filestore.s3.endpoint
+		endpoint = URL.parse(settings.filestore.s3.endpoint)
+		options.endpoint = endpoint.hostname
+		options.port = endpoint.port
+	return knox.createClient(options)
+
 module.exports =

 	sendFile: (bucketName, key, fsPath, callback)->
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		putEventEmiter = s3Client.putFile fsPath, key, (err, res)->
 			if err?
 				logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath,"something went wrong uploading file to s3"
@@ -171,10 +191,7 @@
 			_callback = () ->

 		logger.log key: key, bucketName: bucketName, "deleting directory"
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		s3Client.list prefix:key, (err, data)->
 			if err?
 				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"
@@ -200,10 +217,7 @@

 	directorySize:(bucketName, key, callback)->
 		logger.log bucketName:bucketName, key:key, "get project size in s3"
-		s3Client = knox.createClient
-			key: settings.filestore.s3.key
-			secret: settings.filestore.s3.secret
-			bucket: bucketName
+		s3Client = getKnoxClient(bucketName)
 		s3Client.list prefix:key, (err, data)->
 			if err?
 				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"


Add toggleOnlyFixesNotification command

Add toggleOnlyFixesNotification command

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -17,6 +17,7 @@
       'rubocop-auto-correct:current-file': => @run(atom.workspace.getActiveTextEditor())
       'rubocop-auto-correct:toggle-auto-run': => @toggleAutoRun()
       'rubocop-auto-correct:toggle-notification': => @toggleNotification()
+      'rubocop-auto-correct:toggle-only-fixes-notification': => @toggleOnlyFixesNotification()
       'rubocop-auto-correct:toggle-correct-file': => @toggleCorrectFile()
       'rubocop-auto-correct:toggle-debug-mode': => @toggleDebugMode()


Use atom.notifications

Use atom.notifications

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -1,4 +1,5 @@
 {BufferedProcess} = require 'atom'
+path = require 'path'

 module.exports =
   config:
@@ -13,10 +14,11 @@
        @run(event.currentTarget.getModel())

   autoCorrect: (filePath)  ->
+    basename = path.basename(filePath)
     command = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
     args = ['-a', filePath]
-    console.log("#{command} -a #{filePath}")
-    process = new BufferedProcess({command, args})
+    exit = (code) -> atom.notifications.addSuccess("#{command} -a #{basename}")
+    process = new BufferedProcess({command, args, exit})
     process

   run: (editor) ->


Update jadelint.coffee

Update jadelint.coffee

As requested, removing explicit return

--- src/jadelint.coffee
+++ src/jadelint.coffee
@@ -31,7 +31,7 @@

             lastDir = dir
             dir = path.join dir, '..'
-        return def
+        def

     conf ?= getDefaultConf()


fix for firefox

fix for firefox

--- src/keyboard.coffee
+++ src/keyboard.coffee
@@ -50,14 +50,14 @@
       @editor.selection.deleteRange()
       this.insertText("\n")
     )
-    this.addHotkey(ScribeKeyboard.KEYS.BACKSPACE, (selection) =>
-      unless @editor.selection.deleteRange()
-        @editor.deleteAt(selection.start.index - 1, 1) if selection.start.index > 0
-    )
-    this.addHotkey(ScribeKeyboard.KEYS.DELETE, (selection) =>
-      unless @editor.selection.deleteRange()
-        @editor.deleteAt(selection.start.index, 1) if selection.start.index < @editor.doc.length - 1
-    )
+    #this.addHotkey(ScribeKeyboard.KEYS.BACKSPACE, (selection) =>
+    #  unless @editor.selection.deleteRange()
+    #    @editor.deleteAt(selection.start.index - 1, 1) if selection.start.index > 0
+    #)
+    #this.addHotkey(ScribeKeyboard.KEYS.DELETE, (selection) =>
+    #  unless @editor.selection.deleteRange()
+    #    @editor.deleteAt(selection.start.index, 1) if selection.start.index < @editor.doc.length - 1
+    #)
     this.addHotkey(Scribe.Keyboard.HOTKEYS.BOLD, (selection) =>
       this.toggleFormat(selection, 'bold')
     )


Stubbed out hasOne tests for relationships

--- test/generators/relational/has_one.coffee
+++ test/generators/relational/has_one.coffee
@@ -348,6 +348,26 @@
           assert.equal(1, paging_info.total_rows, "Counted reverses. Expected: 1. Actual: #{paging_info.total_rows}")
           done()

+    it 'Should manage backlinks', (done) ->
+      # TODO: implement embedded
+      return done() if embed
+
+      Owner.cursor().limit(2).include('reverse').toModels (err, owners) ->
+        assert.ok(!err, "No errors: #{err}")
+        assert.equal(2, owners.length, "Found owners. Expected: 2. Actual: #{owners.length}")
+
+        checkReverseFn = (reverse, expected_owner) -> return (callback) ->
+          assert.ok(reverse, "Reverse exists")
+          reverse.get 'owner', (err, reverse_owner) ->
+            assert.ok(!err, "No errors: #{err}")
+            assert.deepEqual(expected_owner.toJSON(), reverse.get('owner').toJSON(), "Reverse owner is a match.\nExpected: #{util.inspect(expected_owner.toJSON())}.\nActual: #{util.inspect(reverse.get('owner').toJSON())}")
+            callback()
+
+        queue = new Queue(1)
+        queue.defer checkReverseFn(owners[0].get('reverse'), owners[0])
+        queue.defer checkReverseFn(owners[1].get('reverse'), owners[1])
+        queue.await done
+
 # TODO: explain required set up

 # each model should have available attribute 'id', 'name', 'created_at', 'updated_at', etc....


Make project tabs just pile up on each other

--- src/smc-webapp/desktop_app.cjsx
+++ src/smc-webapp/desktop_app.cjsx
@@ -149,7 +149,7 @@
             width:'100vw'
             overflow:'auto'

-        use_dropdown_menu = $(window).width() - 550 < @props.open_projects.size * 120
+        use_dropdown_menu = $(window).width() - 550

         <div ref="page" style={style}>
             {<FileUsePageWrapper /> if @props.show_file_use}


Add renaming of channels to the import

Add renaming of channels to the import

--- packages/rocketchat-importer-slack/server.coffee
+++ packages/rocketchat-importer-slack/server.coffee
@@ -192,10 +192,9 @@

 							if not _.isEmpty channel.topic?.value
 								roomUpdate.topic = channel.topic.value
-								lastSetTopic = channel.topic.last_set
-
-							if not _.isEmpty(channel.purpose?.value) and channel.purpose.last_set > lastSetTopic
-								roomUpdate.topic = channel.purpose.value
+
+							if not _.isEmpty(channel.purpose?.value)
+								roomUpdate.description = channel.purpose.value

 							RocketChat.models.Rooms.update { _id: channel.rocketId }, { $set: roomUpdate, $addToSet: { importIds: channel.id } }

@@ -203,7 +202,7 @@
 			@collection.update { _id: @channels._id }, { $set: { 'channels': @channels.channels }}

 			missedTypes = {}
-			ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true, 'channel_name': true }
+			ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true }
 			@updateProgress Importer.ProgressStep.IMPORTING_MESSAGES
 			for channel, messagesObj of @messages
 				do (channel, messagesObj) =>
@@ -256,9 +255,14 @@

 												RocketChat.sendMessage botUser, msgObj, room, true
 											else if message.subtype is 'channel_purpose'
-												RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.purpose, @getRocketUser(message.user), msgDataDefaults
+												if @getRocketUser(message.user)?
+													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_description', room._id, message.purpose, @getRocketUser(message.user), msgDataDefaults
 											else if message.subtype is 'channel_topic'
-												RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user), msgDataDefaults
+												if @getRocketUser(message.user)?
+													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user), msgDataDefaults
+											else if message.subtype is 'channel_name'
+												if @getRocketUser(message.user)?
+													RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser room._id, message.name, @getRocketUser(message.user), msgDataDefaults
 											else if message.subtype is 'pinned_item'
 												if message.attachments
 													msgObj =


putting back the renderCallback, because the "dom" variable is needed

--- canvg.coffee
+++ canvg.coffee
@@ -22,6 +22,7 @@
 #     offsetY: int => draws at a y offset
 #     scaleWidth: int => scales horizontally to width
 #     scaleHeight: int => scales vertically to height
+#     renderCallback: function => will call the function after the first render is completed
 #     forceRedraw: function => will call the function on every frame, if it returns true, will redraw

 svg = undefined
@@ -283,7 +284,7 @@
     p.y += window.scrollY  if window.scrollY
     p

-  draw: ->
+  draw: (dom)->
     console.log "start of draw() function"
     ctx = @ctxFromLoadXMLDoc
     e = @eFromLoadXMLDoc
@@ -344,8 +345,10 @@
       ctx.clearRect 0, 0, cWidth, cHeight
       console.log "clearing: " + cWidth + " " + cHeight
     e.render ctx
-    #if @isFirstRender
-    #  @isFirstRender = false
+    if @isFirstRender
+      @isFirstRender = false
+      if typeof (@opts["renderCallback"]) == "function"
+        @opts["renderCallback"](dom)

@@ -382,7 +385,7 @@
     waitingForImages = true
     if @ImagesLoaded()
       waitingForImages = false
-      @draw()
+      @draw(dom)

     console.log ">>>>>> starting interval with fps: " + @FRAMERATE
     @intervalID = setInterval(=>


revived the tests

revived the tests

--- src/test/tests/testCache.coffee
+++ src/test/tests/testCache.coffee
@@ -35,7 +35,6 @@
 expectedQuery = null
 mySqlWrap = (query, callback) ->
   if checkEquality
-    console.trace()
     expect(query).to.deep.equal(expectedQuery)

   if not allowQuery
@@ -56,366 +55,366 @@
 describe "Cache tests", ->
   @timeout(40 * 1000)

-  # describe "(sanity check) apply count", ->
-  #   it "should have the same results for different drivers", testEquality {
-  #     drivers: ['mySqlCached', 'mySql']
-  #     query: [
-  #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #       { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #     ]
-  #   }
-
-  # describe 'topN Cache', ->
-  #   setUpQuery = [
-  #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #       { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #       { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
-  #     ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       allowQuery = false
-  #       done()
-  #       return
-  #     )
-
-  #   after -> allowQuery = true
-
-  #   describe "split page; apply deleted; combine descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
-
-  #   describe "split page; apply deleted, count; combine descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
-
-
-  # describe "different sorting works", ->
-  #   describe "split page; apply deleted; combine descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
-
-  #   describe "split page; apply deleted; combine ascending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'ascending' }, limit: 5 }
-  #       ]
-  #     }
-
-  #   describe "split page; apply deleted; combine Page, descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Page', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
-
-  #   describe "split page; apply deleted; combine Page, ascending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Page', direction: 'ascending' }, limit: 5 }
-  #       ]
-  #     }
-
-
-  # # Cache Test
-  # describe "split time; apply count; apply added", ->
-  #   setUpQuery = [
-  #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #       { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #       { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #     ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       allowQuery = false
-  #       done()
-  #       return
-  #     )
-
-  #   after -> allowQuery = true
-
-  #   describe "split time; apply count", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #       ]
-  #     }
-
-  #   describe "split time; apply count; combine not by time", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'ascending' } }
-  #       ]
-  #     }
-
-  #   describe "split time; apply count; filter within another time filter", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 26, 12, 0, 0))] }
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #       ]
-  #     }
-
-  #   describe "split time; apply count; limit", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' }, limit: 5 }
-  #       ]
-  #     }
-
-
-  #   # Cache Test 2
-  # describe "filter; split time; apply count; apply added", ->
-  #   setUpQuery = [
-  #       { operation: 'filter', type: 'and', filters: [
-  #         { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #       ]}
-  #       { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #       { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #     ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       allowQuery = false
-  #       done()
-  #       return
-  #     )
-
-  #   after -> allowQuery = true
-
-  #   describe "filter; split time; apply count; apply added", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type: 'and', filters: [
-  #           { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #           { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         ]}
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #       ]
-  #     }
-
-  #   describe "filter; split time; apply count; apply added; combine time descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type: 'and', filters: [
-  #           { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #           { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         ]}
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'descending' } }
-  #       ]
-  #     }
-
-
-  # describe "fillTree test", ->
-  #   describe "filter; split time; apply count; apply added", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type: 'and', filters: [
-  #           { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #           { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         ]}
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
-  #       ]
-  #     }
-
-  #   describe "filter; split time; apply count; apply added; combine time descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type: 'and', filters: [
-  #           { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #           { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         ]}
-  #         { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'descending' } }
-  #       ]
-  #     }
-
-  # describe "splitCache fills filterCache as well", ->
-  #   setUpQuery = [
-  #     { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #     { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #     { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #     { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #     { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #   ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       allowQuery = false
-  #       done()
-  #       return
-  #     )
-
-  #   after -> allowQuery = true
-
-  #   describe "filter; split time; apply count; apply added; combine time descending", ->
-  #     testQuery = [
-  #       { operation: 'filter', type: 'and', filters: [
-  #         { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #       ]}
-  #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #       { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #     ]
-
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: testQuery
-  #     }
-
-  # describe "doesn't query for known data", ->
-  #   setUpQuery = [
-  #     { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #     { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #     { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #     { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #     { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #   ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       checkEquality = true
-  #       done()
-  #       return
-  #     )
-
-  #   after -> checkEquality = false
-
-  #   describe "filter; split time; apply count; apply added; combine time descending", ->
-  #     before ->
-  #       expectedQuery = [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #       ]
-
-  #     after ->
-  #       expectedQuery = null
-
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
-  #         { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
-
-  # describe "handles multiple splits", ->
-  #   setUpQuery = [
-  #     { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #     { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #     { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #     { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #     { operation: 'split', name: 'Namespace', bucket: 'identity', attribute: 'namespace' }
-  #     { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #     { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #   ]
-
-  #   before (done) ->
-  #     driverFns.mySqlCached(setUpQuery, (err, result) ->
-  #       throw err if err?
-  #       allowQuery = false
-  #       done()
-  #       return
-  #     )
-
-  #   after -> allowQuery = true
-
-  #   describe "filter; split time; apply count; split time; apply count; combine count descending", ->
-  #     it "should have the same results for different drivers", testEquality {
-  #       drivers: ['mySqlCached', 'mySql']
-  #       query: [
-  #         { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-  #         { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #         { operation: 'split', name: 'Namespace', bucket: 'identity', attribute: 'namespace' }
-  #         { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-  #         { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-  #       ]
-  #     }
+  describe "(sanity check) apply count", ->
+    it "should have the same results for different drivers", testEquality {
+      drivers: ['mySqlCached', 'mySql']
+      query: [
+        { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+        { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+        { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+      ]
+    }
+
+  describe 'topN Cache', ->
+    setUpQuery = [
+        { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+        { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+        { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+        { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+        { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
+      ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        allowQuery = false
+        done()
+        return
+      )
+
+    after -> allowQuery = true
+
+    describe "split page; apply deleted; combine descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+    describe "split page; apply deleted, count; combine descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+
+  describe "different sorting works", ->
+    describe "split page; apply deleted; combine descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+    describe "split page; apply deleted; combine ascending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Deleted', direction: 'ascending' }, limit: 5 }
+        ]
+      }
+
+    describe "split page; apply deleted; combine Page, descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Page', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+    describe "split page; apply deleted; combine Page, ascending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Page', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Page', direction: 'ascending' }, limit: 5 }
+        ]
+      }
+
+
+  # Cache Test
+  describe "split time; apply count; apply added", ->
+    setUpQuery = [
+        { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+        { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+        { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+        { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+        { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+      ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        allowQuery = false
+        done()
+        return
+      )
+
+    after -> allowQuery = true
+
+    describe "split time; apply count", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+        ]
+      }
+
+    describe "split time; apply count; combine not by time", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'ascending' } }
+        ]
+      }
+
+    describe "split time; apply count; filter within another time filter", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 26, 12, 0, 0))] }
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+        ]
+      }
+
+    describe "split time; apply count; limit", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' }, limit: 5 }
+        ]
+      }
+
+
+    # Cache Test 2
+  describe "filter; split time; apply count; apply added", ->
+    setUpQuery = [
+        { operation: 'filter', type: 'and', filters: [
+          { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+        ]}
+        { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+        { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+        { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+        { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+      ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        allowQuery = false
+        done()
+        return
+      )
+
+    after -> allowQuery = true
+
+    describe "filter; split time; apply count; apply added", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type: 'and', filters: [
+            { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+            { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          ]}
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+        ]
+      }
+
+    describe "filter; split time; apply count; apply added; combine time descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type: 'and', filters: [
+            { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+            { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          ]}
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'descending' } }
+        ]
+      }
+
+
+  describe "fillTree test", ->
+    describe "filter; split time; apply count; apply added", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type: 'and', filters: [
+            { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+            { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          ]}
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'ascending' } }
+        ]
+      }
+
+    describe "filter; split time; apply count; apply added; combine time descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type: 'and', filters: [
+            { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+            { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          ]}
+          { operation: 'split', name: 'Time', bucket: 'timePeriod', attribute: 'time', period: 'PT1H', timezone: 'Etc/UTC' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Time', direction: 'descending' } }
+        ]
+      }
+
+  describe "splitCache fills filterCache as well", ->
+    setUpQuery = [
+      { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+      { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+      { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+      { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+      { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+    ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        allowQuery = false
+        done()
+        return
+      )
+
+    after -> allowQuery = true
+
+    describe "filter; split time; apply count; apply added; combine time descending", ->
+      testQuery = [
+        { operation: 'filter', type: 'and', filters: [
+          { operation: 'filter', attribute: 'language', type: 'is', value: 'en' }
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+        ]}
+        { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+        { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+      ]
+
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: testQuery
+      }
+
+  describe "doesn't query for known data", ->
+    setUpQuery = [
+      { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+      { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+      { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+      { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+      { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+    ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        checkEquality = true
+        done()
+        return
+      )
+
+    after -> checkEquality = false
+
+    describe "filter; split time; apply count; apply added; combine time descending", ->
+      before ->
+        expectedQuery = [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+
+      after ->
+        expectedQuery = null
+
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'apply', name: 'Added', aggregate: 'sum', attribute: 'added' }
+          { operation: 'apply', name: 'Deleted', aggregate: 'sum', attribute: 'deleted' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+  describe "handles multiple splits", ->
+    setUpQuery = [
+      { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+      { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+      { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+      { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+      { operation: 'split', name: 'Namespace', bucket: 'identity', attribute: 'namespace' }
+      { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+      { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+    ]
+
+    before (done) ->
+      driverFns.mySqlCached(setUpQuery, (err, result) ->
+        throw err if err?
+        allowQuery = false
+        done()
+        return
+      )
+
+    after -> allowQuery = true
+
+    describe "filter; split time; apply count; split time; apply count; combine count descending", ->
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+          { operation: 'split', name: 'Namespace', bucket: 'identity', attribute: 'namespace' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+      }

   describe "handles filtered splits", ->
     setUpQuery = [
@@ -522,104 +521,104 @@
         ]
       }

-    # describe "only with a new element", ->
-    #   before ->
-    #     checkEquality = true
-    #     expectedQuery = [
-    #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-    #       { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #       {
-    #         operation: 'split'
-    #         name: 'Namespace'
-    #         bucket: 'identity'
-    #         attribute: 'namespace',
-    #         bucketFilter: {
-    #           prop: 'Language'
-    #           type: 'in'
-    #           values: ['it']
-    #         }
-    #       }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #     ]
-
-    #   after -> checkEquality = false
-
-    #   it "should have the same results for different drivers", testEquality {
-    #     drivers: ['mySqlCached', 'mySql']
-    #     query: [
-    #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-    #       { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #       {
-    #         operation: 'split'
-    #         name: 'Namespace'
-    #         bucket: 'identity'
-    #         attribute: 'namespace',
-    #         bucketFilter: {
-    #           prop: 'Language'
-    #           type: 'in'
-    #           values: ['it']
-    #         }
-    #       }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #     ]
-    #   }
-
-    # describe "combine all filters", ->
-    #   before -> allowQuery = false
-    #   after -> allowQuery = true
-
-    #   it "should have the same results for different drivers", testEquality {
-    #     drivers: ['mySqlCached', 'mySql']
-    #     query: [
-    #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-    #       { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #       {
-    #         operation: 'split'
-    #         name: 'Namespace'
-    #         bucket: 'identity'
-    #         attribute: 'namespace',
-    #         bucketFilter: {
-    #           prop: 'Language'
-    #           type: 'in'
-    #           values: ['en','de','fr','it']
-    #         }
-    #       }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #     ]
-    #   }
-
-    # describe "same filter in a different order", ->
-    #   before -> allowQuery = false
-    #   after -> allowQuery = true
-
-    #   it "should have the same results for different drivers", testEquality {
-    #     drivers: ['mySqlCached', 'mySql']
-    #     query: [
-    #       { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
-    #       { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #       {
-    #         operation: 'split'
-    #         name: 'Namespace'
-    #         bucket: 'identity'
-    #         attribute: 'namespace',
-    #         bucketFilter: {
-    #           prop: 'Language'
-    #           type: 'in'
-    #           values: ['fr','en']
-    #         }
-    #       }
-    #       { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
-    #       { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
-    #     ]
-    #   }+    describe "only with a new element", ->
+      before ->
+        checkEquality = true
+        expectedQuery = [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+          {
+            operation: 'split'
+            name: 'Namespace'
+            bucket: 'identity'
+            attribute: 'namespace',
+            bucketFilter: {
+              prop: 'Language'
+              type: 'in'
+              values: ['it']
+            }
+          }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+
+      after -> checkEquality = false
+
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+          {
+            operation: 'split'
+            name: 'Namespace'
+            bucket: 'identity'
+            attribute: 'namespace',
+            bucketFilter: {
+              prop: 'Language'
+              type: 'in'
+              values: ['it']
+            }
+          }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+    describe "combine all filters", ->
+      before -> allowQuery = false
+      after -> allowQuery = true
+
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+          {
+            operation: 'split'
+            name: 'Namespace'
+            bucket: 'identity'
+            attribute: 'namespace',
+            bucketFilter: {
+              prop: 'Language'
+              type: 'in'
+              values: ['en','de','fr','it']
+            }
+          }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+      }
+
+    describe "same filter in a different order", ->
+      before -> allowQuery = false
+      after -> allowQuery = true
+
+      it "should have the same results for different drivers", testEquality {
+        drivers: ['mySqlCached', 'mySql']
+        query: [
+          { operation: 'filter', type:'within', attribute:'time', range: [ new Date(Date.UTC(2013, 2-1, 26, 0, 0, 0)), new Date(Date.UTC(2013, 2-1, 27, 0, 0, 0))] }
+          { operation: 'split', name: 'Language', bucket: 'identity', attribute: 'language' }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+          {
+            operation: 'split'
+            name: 'Namespace'
+            bucket: 'identity'
+            attribute: 'namespace',
+            bucketFilter: {
+              prop: 'Language'
+              type: 'in'
+              values: ['fr','en']
+            }
+          }
+          { operation: 'apply', name: 'Count', aggregate: 'sum', attribute: 'count' }
+          { operation: 'combine', combine: 'slice', sort: { compare: 'natural', prop: 'Count', direction: 'descending' }, limit: 5 }
+        ]
+      }


Fausses modifs

Fausses modifs

--- State.coffee
+++ State.coffee
@@ -15,21 +15,21 @@
 		else
 			@param = {}
 		this.init()
-	apply: (event) ->
-		this[event](@param)
+	apply: (event, param) ->
+		this[event](param)

 	touchstart: -> #throw "undefined"
 	touchmove: -> #throw "undefined"
 	touchend: -> #throw "undefined"

-	xthrow: (name, params) -> $("debug").innerHTML = "throw " + name + " params: " + @params + "\n" + $("debug").innerHTML #Futur trigger
+	xthrow: (name, params) -> $("debug").innerHTML = "throw " + name + " params: " + @param + "\n" + $("debug").innerHTML #Futur trigger

 class NoTouch extends GenericState
 	description: -> "NoTouch state"
 	touchstart: (event) ->
-		@param.initX = event.touches[0].clientX
+		@param.initX = event
 		@param.llsdfsdf = "ok"
 		@machine.setState(new FirstTouch @machine)

@@ -87,13 +87,13 @@

 window.onload = ->
 	machine = new StateMachine
-	$("body").addEventListener "mousedown", (event) -> machine.apply("touchstart")
-	$("body").addEventListener "mouseup", (event) -> machine.apply("touchend")
-	$("body").addEventListener "mousemove", (event) -> machine.apply("touchmove")
+	$("body").addEventListener "mousedown", (event) -> machine.apply("touchstart", event)
+	$("body").addEventListener "mouseup", (event) -> machine.apply("touchend", event)
+	$("body").addEventListener "mousemove", (event) -> machine.apply("touchmove", event)

-	$("body").addEventListener "touchstart", (event) -> machine.apply("touchstart")
-	$("body").addEventListener "touchend", (event) -> machine.apply("touchend")
-	$("body").addEventListener "touchmove", (event) -> machine.apply("touchmove")
+	$("body").addEventListener "touchstart", (event) -> machine.apply("touchstart", event)
+	$("body").addEventListener "touchend", (event) -> machine.apply("touchend", event)
+	$("body").addEventListener "touchmove", (event) -> machine.apply("touchmove", event)


Include question in classification

Include question in classification

--- app/stores/classify-store.coffee
+++ app/stores/classify-store.coffee
@@ -42,9 +42,9 @@
   createNewClassification: (workflow, subject) ->
     classification = api.type('classifications').create
       annotations: [
-        {key: workflowTaskKeys.first, task: "", value: 0}
-        {key: workflowTaskKeys.second, task: "", value: null}
-        {key: workflowTaskKeys.third, task: "", value: null}
+        {key: workflowTaskKeys.first, task: workflow.tasks[workflowTaskKeys.first].question, value: 0}
+        {key: workflowTaskKeys.second, task: workflow.tasks[workflowTaskKeys.second].question, value: null}
+        {key: workflowTaskKeys.third, task: workflow.tasks[workflowTaskKeys.third].question, value: null}
       ]
       metadata:
         workflow_version: workflow.version


D D thumbloader now stops reloading once the images are all loaded. The problem was the alive function was seeing if the array.length was < 0 rather then == 0 .... I don't know how that happened. <(-w-)>

--- filemanager/static/script/thumbloader.coffee
+++ filemanager/static/script/thumbloader.coffee
@@ -46,9 +46,13 @@
            console.log "What do we have? We have got #{this.datalist[i][0]} and #{pk}"
            console.log "By the by, the datalist is #{this.datalist}"
            if "#{this.datalist[i][0]}" == "#{pk}"
+               console.log "It seems that the pk is found!!!!"
+               console.log "the value of i is #{i}"
+               console.log "OH, oh oh!!! and the datalist.length is #{this.datalist.length}"
                this.datalist.splice(i, 1)
+               console.log "Well........... it was, but now it is #{this.datalist.length}"
     alive: () ->
-        if this.datalist.length<0
+        if this.datalist.length==0
             return false
         else
             return true


Don't display errored thumbnails

Don't display errored thumbnails

--- app/talk/discussion-preview.cjsx
+++ app/talk/discussion-preview.cjsx
@@ -21,10 +21,10 @@

-        <PromiseRenderer promise={talkClient.type('comments').get(discussion.links.comments[0])}>{(comment) =>
+        <PromiseRenderer catch={null} promise={talkClient.type('comments').get(discussion.links.comments[0])}>{(comment) =>
           if comment.focus_id and (comment.focus_type is 'Subject')

-              <PromiseRenderer promise={apiClient.type('subjects').get(comment.focus_id)}>{(subject) =>
+              <PromiseRenderer catch={null} promise={apiClient.type('subjects').get(comment.focus_id)}>{(subject) =>
                 <Thumbnail src={getSubjectLocation(subject).src} width={100} />
               }</PromiseRenderer>


fix(cibuild): apm needs dependencies, fix for npm3

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -163,7 +163,18 @@
     cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee|cjsx|jsx)$/
     cp 'static', path.join(appDir, 'static')

-    cp path.join('apm', 'node_modules', 'atom-package-manager'), path.resolve(appDir, '..', 'new-app', 'apm'), filter: filterNodeModule
+    # Move all of the node modules inside /apm/node_modules to new-app/apm/node_modules
+    apmInstallDir = path.resolve(appDir, '..', 'new-app', 'apm')
+    mkdir apmInstallDir
+    cp path.join('apm', 'node_modules'), path.resolve(apmInstallDir, 'node_modules'), filter: filterNodeModule
+
+    # Move /apm/node_modules/atom-package-manager to new-app/apm. We're essentially
+    # pulling the atom-package-manager module up outside of the node_modules folder,
+    # which is necessary because npmV3 installs nested dependencies in the same dir.
+    apmPackageDir = path.join(apmInstallDir, 'node_modules', 'atom-package-manager')
+    for name in fs.readdirSync(apmPackageDir)
+      fs.renameSync path.join(apmPackageDir, name), path.join(apmInstallDir, name)
+    fs.unlinkSync(apmPackageDir)

     if process.platform is 'darwin'
       grunt.file.recurse path.join('build', 'resources', 'mac'), (sourcePath, rootDirectory, subDirectory='', filename) ->


Encode all these attributes as they wind up as URL components

--- apps/artwork/client/share.coffee
+++ apps/artwork/client/share.coffee
@@ -15,9 +15,9 @@
     { @artwork } = options

     @templateData =
-      url: encodeURIComponent(sd.ARTSY_URL + sd.CURRENT_PATH)
-      media: @artwork.defaultImageUrl('large')
-      description: @artwork.toAltText()
+      url: encodeURIComponent sd.ARTSY_URL + sd.CURRENT_PATH
+      media: encodeURIComponent @artwork.defaultImageUrl('large')
+      description: encodeURIComponent @artwork.toAltText()

     super


Return after logging errors to avoid more failures

--- routes/api.coffee
+++ routes/api.coffee
@@ -14,6 +14,7 @@
     if error
       console.log "Error: #{error}"
       res.send 'Error'
+      return
     res.json form
   fulcrum.forms.find constants.form_id, callback

@@ -22,6 +23,7 @@
     if error
       console.log "Error: #{error}"
       res.send 'Error'
+      return
     geojson = recordsToFeatureCollection records
     res.json geojson
   params =
@@ -34,6 +36,7 @@
     if error
       console.log "Error: #{error}"
       res.send String(error), 500
+      return
     record_feature = recordToFeature record.record
     res.json record_feature
   fulcrum.records.create req.body, callback
@@ -53,6 +56,7 @@
     if error
       console.log "Error: #{error}"
       res.send 'Error'
+      return
     res.json photo
   fulcrum.photos.find req.params.photo_id, callback

@@ -61,6 +65,7 @@
     if error
       console.log "Error: #{error}"
       res.send 'Error'
+      return
     res.json classfication_set
   fulcrum.classification_sets.find req.params.classification_set_id, callback


Search completion; tweak for engines without completers.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -382,6 +382,8 @@

     haveCompletionEngine = CompletionEngines.haveCompletionEngine searchUrl
     # If this is a custom search engine and we have a completer, then exclude results from other completers.
+    # The truthiness of filter? also serves to distinguish two very different vomnibar behaviors, one for
+    # custom search engines with completers (filter? is true), and one for those without (filter? is false).
     filter =
       if custom and haveCompletionEngine
         (suggestion) -> suggestion.type == description
@@ -390,22 +392,26 @@

     # For custom search engines, we add an auto-selected suggestion.
     if custom
+      # Note. This suggestion always appears at the top of the suggestion list.  Its settings serves to serve
+      # to configure various vomnibar behaviors.
       suggestions.push new Suggestion
         queryTerms: queryTerms
         type: description
         url: Utils.createSearchUrl queryTerms, searchUrl
         title: query
         relevancy: 1
-        highlightTerms: false
-        insertText: query
-        # NOTE (smblott) Disbaled pending consideration of how to handle text selection within the vomnibar
-        # itself.
-        # autoSelect: true
-        # Always reset the selection to this suggestion on query change.  The UX is weird otherwise.
-        # forceAutoSelect: true
-        # Suppress the "w" from "w query terms" in the vomnibar input.
+        insertText: if filter? then query else null
+        # For all custom search engines, we suppress the leading keyword, for example "w something" becomes
+        # "something" in the vomnibar.
         suppressLeadingKeyword: true
+        # We complete suggestions only for custom search engines where we have an associated completion
+        # engine.
         completeSuggestions: filter?
+        # We only use autoSelect and highlight query terms (on custom search engines) when we do not have a
+        # completer.
+        autoSelect: not filter?
+        forceAutoSelect: not filter?
+        highlightTerms: not filter?

     # Post suggestions and bail if there is no prospect of adding further suggestions.
     if queryTerms.length == 0 or not haveCompletionEngine


send at most 100,000 characters of  console session history, which avoids potential issues with big terminal reconnects killing bandwidth

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -3131,7 +3131,7 @@
                 mesg = message.session_connected
                     session_uuid : opts.session_uuid
                     data_channel : channel
-                    history      : console_socket.history
+                    history      : console_socket.history.slice(console_socket.history.length - 100000)   # only last 100,000
                 opts.cb(false, mesg)

                 # console --> client:


[feat] redo preview pane to save a round-trip

--- assets/js/panes/previewer.coffee
+++ assets/js/panes/previewer.coffee
@@ -16,29 +16,11 @@
 module.service "previewer", ["$http", "$timeout", "url", "settings", "notifier", ($http, $timeout, url, settings, notifier) ->
 ]

-module.run [ "$q", "$http", "$timeout", "url", "panes", "session", "settings", "notifier", ($q, $http, $timeout, url, panes, session, settings, notifier) ->
-
-  class Preview
-    constructor: (options = {}) ->
-      @delegate = options.delegate or -> files: {'index.html': {content: ""}}
-      @previewId = options.previewId or ""
-      @handler = options.handler or ->
-
-    refresh: (force = false) ->
-      preview = @
-
-      $timeout.cancel(@cancelCurrentRefresh) if @cancelCurrentRefresh
-
-      @cancelCurrentRefresh = $timeout ->
-        json = if angular.isFunction(preview.delegate) then preview.delegate() else preview.delegate
-        req = $http.post("#{url.run}/#{preview.previewId}", json, cache: false).then (res) ->
-          if res.status >= 400 then notifier.error("Preview refresh failed")
-          else
-            preview.previewId = res.data.id
-            preview.handler(res.data)
-
-        , (err) -> notifier.error("Preview refresh failed")
-      , if force then 0 else settings.previewer.delay
+module.run [ "$q", "$document", "$timeout", "url", "panes", "session", "settings", "notifier", ($q, $document, $timeout, url, panes, session, settings, notifier) ->
+
+  genid = (len = 16, prefix = "", keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") ->
+    prefix += keyspace.charAt(Math.floor(Math.random() * keyspace.length)) while len-- > 0
+    prefix

   panes.add
     id: "preview"
@@ -50,37 +32,34 @@
     """
     size: "50%"
     template: """
-

-

+

+

-

+

                 <button id="refresh-preview" ng-click="refresh()" class="btn btn-mini btn-success" title="Manually trigger a refresh of the preview"></button>
-                <button id="expand-preview" ng-click="expand()" ng-switch-when="false" class="btn btn-mini btn-primary" title="Launch the preview in a separate window"></button>
-                <button id="expand-preview" ng-click="contract()" ng-switch-when="true" class="btn btn-mini btn-danger" title="Close the child preview window"></button>
+                <button id="expand-preview" ng-click="expand()" class="btn btn-mini btn-primary" title="Launch the preview in a separate window"></button>

-
+

-

+

Previeweing in windowed mode

You've switched to previewing your work in windowed mode. This can be useful for using the developer tools without having to navigate
               down through the iframe that is used for the in-window preview.

You can return to the in-window preview at any time simply by clicking below.

+              <button class="btn btn-success" ng-click="refresh()">
+
+                Refresh
+              </button>
               <button class="btn btn-danger" ng-click="contract()">

                 Close
               </button>

-

-

-
Auto-refresh disabled

-
While you are in windowed mode, Plunker cannot automatically trigger a refresh of the preview because of cross-domain security.
-              To preview your changes, simply hit refresh on the preview window. Plunker should have updated the preview's code automagically for
-              you.

@@ -90,48 +69,55 @@
       iframe = $("iframe.plunker-previewer-iframe", $el)[0]
       childWindow = null

+      $scope.previewUrl ||= "#{url.run}/#{genid()}/" # Create a random new preview id
+      $scope.iframeUrl = "about:blank" # Initial url until a windowed -> embedded switch
       $scope.session = session
-      $scope.windowed = false
-      $scope.preview = new Preview
-        delegate: -> files: session.toJSON().files
-        handler: (data) ->
-          if $scope.windowed
-            if !childWindow or childWindow?.closed
-              childWindow = window.open data.run_url, "plnk_previewer", "resizable=yes,scrollbars=yes,status=yes,toolbar=yes"
-            else
-              childWindow.location.reload(true) # Fails on recent Chrome... try anyway ;-)
+      $scope.mode = "disabled"
+
+      $scope.expand = -> $scope.mode = "windowed"
+      $scope.contract = -> $scope.mode = "embedded"
+
+      $scope.refresh = -> $timeout ->
+        return if $scope.mode is "disabled"
+
+        form = angular.element("""<form style="display: none;" method="post" action="#{$scope.previewUrl}" target="plunkerPreviewTarget"><form>""")
+
+        for filename, file of session.toJSON().files
+          field = angular.element("""<input type="hidden" name="files[#{filename}][content]">""")
+          field.attr "value", file.content
+
+          form.append(field)
+
+        #$document.append(form)
+
+        form.submit()
+
+        $timeout -> form.remove()
+
+      $scope.$watch "mode", (mode, old_mode) ->
+        switch mode
+          when "embedded"
+            childWindow?.close()
+            childWindow = null
+
+            $timeout -> $scope.refresh() # Refresh on next tick to make sure DOM udpated first
+          when "windowed"
+            childWindow = window.open "about:blank", "plunkerPreviewTarget", "resizable=yes,scrollbars=yes,status=yes,toolbar=yes"
+            $timeout -> $scope.refresh() # Refresh on next tick to make sure DOM udpated first

-          else
-            loc = iframe.contentWindow.location
-
-            if loc is data.run_url then loc.reload(true)
-            else loc.replace(data.run_url)
-
-      $scope.refresh = -> $scope.preview.refresh()
-      $scope.expand = -> $scope.windowed = true
-      $scope.contract = -> $scope.windowed = false
-
-      $scope.$watch "windowed", (windowed) ->
-        if windowed
-          pane.class = "windowed"
-          iframe.contentWindow.location.replace("about:blank")
-        else
-          pane.class = ""
-          childWindow?.close()
-          childWindow = null
-        $scope.preview.refresh() if pane.active

       $scope.$watch "pane.active", (active) ->
-        if active then $scope.preview.refresh()
-        else iframe.contentWindow.location.replace("about:blank")
+        if !active and $scope.mode is "embedded" then $scope.mode = "disabled"
+        if active and $scope.mode is "disabled" then $timeout ->
+          $scope.mode = "embedded"

       $scope.$watch "session.updated_at", ->
-        $scope.preview.refresh() if settings.previewer.auto_refresh and (pane.active or $scope.windowed)
+        $scope.refresh() if settings.previewer.auto_refresh and (pane.active or $scope.mode is "windowed")

       setInterval ->
-        if $scope.windowed and childWindow?.closed
+        if $scope.mode is "windowed" and childWindow?.closed
           $scope.$apply ->
-            $scope.windowed = false
+            $scope.contract()
             childWindow = null
       , 1000
 ]