Passed environment to callback in Environment.get() if provided.

--- src/Environment.coffee
+++ src/Environment.coffee
@@ -8,9 +8,11 @@
       if env?
         env
       else if process.env.ROOT_URL?.indexOf('//localhost:3000') >= 0
-        'development'
+        env = 'development'
       else
-        'production'
+        env = 'production'
+      callback?(env)
+      env
     else
       Promises.serverMethodCall('Environment.get', callback)


<test> Adding test helper to create drivers.

--- tests/helpers.coffee
+++ tests/helpers.coffee
@@ -7,6 +7,8 @@
 ###

 path = require 'path'
+os = require 'os'
+fs = require 'fs'
 fixtures = require 'cozy-fixtures'
 if process.env.USE_JS
     prefix = path.join __dirname, '../build/'
@@ -17,6 +19,7 @@
 ds = require 'cozydb/lib/utils/client'

 Sensor = require "#{prefix}server/models/sensor"
+Driver = require "#{prefix}server/models/driver"

 TESTPORT = process.env.PORT or 8013

@@ -47,6 +50,22 @@
             baseSensor = new Sensor(data)
             Sensor.create baseSensor, (err, sensor) =>
                 @sensor = sensor
+                done err
+
+    createDriver: (filename) ->
+        (done) ->
+            # Copy file so the original doesn't get deleted during the creation:
+            fs.createReadStream(filename).pipe(fs.createWriteStream(os.tmpdir() + filename))
+            data =
+                file:
+                    originalFilename: os.tmpdir() + filename
+                    path: os.tmpdir() + filename
+
+            console.log("CREATING DRIVER")
+            baseDriver = new Driver(data)
+            Driver.create baseDriver, (err, driver) =>
+                console.log("CREATED DRIVER")
+                @driver = driver
                 done err

     makeTestClient: (done) ->


dedupe

dedupe

--- src/scripts/redis_brain.coffee
+++ src/scripts/redis_brain.coffee
@@ -1,26 +0,0 @@
-# sets up hooks to persist the brain into redis.
-module.exports = (robot) ->
-  info   = Url.parse process.env.REDISTOGO_URL || 'redis://localhost:6379'
-  client = Redis.createClient(info.port, info.hostname)
-
-  if info.auth
-    client.auth info.auth.split(":")[1]
-
-  client.on "error", (err) ->
-    console.log "Error #{err}"
-
-  client.on "connect", ->
-    console.log "Successfully connected to Redis"
-
-    client.get "hubot:storage", (err, reply) ->
-      if err
-        throw err
-      else if reply
-        robot.brain.mergeData JSON.parse(reply.toString())
-
-  robot.brain.on 'save', (data) ->
-    client.set 'hubot:storage', data
-
-  robot.brain.on 'close', ->
-    client.quit()
-


Electron: Storage: write: add callback function

--- elektron/src/storage.coffee
+++ elektron/src/storage.coffee
@@ -22,9 +22,11 @@
     fs.stat @path, (err) => @write @template  if err

-  write: (data) ->
+  write: (data, callback) ->

-    fs.writeFile @path, JSON.stringify(data), => console.log "Storage file is saved at: #{@path}"
+    fs.writeFile @path, JSON.stringify(data), =>
+      console.log "Storage file is saved at: #{@path}"
+      callback()

   get: ->


change alert close method

change alert close method


fix typos

fix typos

--- bokehjs/src/coffee/renderer/properties.coffee
+++ bokehjs/src/coffee/renderer/properties.coffee
@@ -357,10 +357,10 @@

     set_vectorize: (ctx, i) ->
       did_change = false
-      if ctx.fillStyle != cache.fillStyle[i]
+      if ctx.fillStyle != @cache.fillStyle[i]
         ctx.fillStyle = @cache.fillStyle[i]
         did_change = true
-      if ctx.globalAlpha != @last_globalalpha
+      if ctx.globalAlpha != @cache.globalAlpha[i]
         ctx.globalAlpha = @cache.globalAlpha[i]
         did_change = true


Disable edited cell's navigation in a systematic way

--- bokehjs/src/coffee/widget/cell_editors.coffee
+++ bokehjs/src/coffee/widget/cell_editors.coffee
@@ -26,7 +26,24 @@
       @args = args
       @model = @args.column.editorModel
       @render()
+
+    render: () ->
+      @renderEditor()
+      @disableNavigation()
       @$el.appendTo(@args.container)
+
+    renderEditor: () ->
+
+    disableNavigation: () ->
+      @$el.keydown (event) =>
+        stop = () -> event.stopImmediatePropagation()
+        switch event.keyCode
+          when $.ui.keyCode.LEFT      then stop()
+          when $.ui.keyCode.RIGHT     then stop()
+          when $.ui.keyCode.UP        then stop()
+          when $.ui.keyCode.DOWN      then stop()
+          when $.ui.keyCode.PAGE_UP   then stop()
+          when $.ui.keyCode.PAGE_DOWN then stop()

     destroy: () -> @remove()

@@ -65,30 +82,17 @@

     validate: () -> return @validateValue(@getValue())

-    disable_horizontal_navigation: () ->
-      @$el.bind "keydown", (event) =>
-        if event.keyCode == $.ui.keyCode.LEFT or event.keyCode == $.ui.keyCode.RIGHT
-          event.stopImmediatePropagation()
-
-    disable_vertical_navigation: () ->
-      @$el.bind "keydown", (event) =>
-        if event.keyCode == $.ui.keyCode.UP   or event.keyCode == $.ui.keyCode.PAGE_UP   or
-           event.keyCode == $.ui.keyCode.DOWN or event.keyCode == $.ui.keyCode.PAGE_DOWN
-          event.stopImmediatePropagation()
-
   class StringEditorView extends CellEditorView

     emptyValue: ""

     el: '<input type="text" class="bk-cell-editor bk-cell-editor-string" />'

-    render: () ->
+    renderEditor: () ->
       completions = @model.get("completions")
       if not _.isEmpty(completions)
         @$el.autocomplete(source: completions)
         @$el.autocomplete("widget").addClass("bk-cell-editor-completion")
-      @disable_horizontal_navigation()
-      @disable_vertical_navigation()
       @$el.focus().select()

     loadValue: (item) ->
@@ -116,10 +120,9 @@

   class SelectEditorView extends CellEditorView

-    el: '<select tabIndex="0" class="bk-cell-editor bk-cell-editor-select" />'
-
-    render: () ->
-      @disable_vertical_navigation()
+    el: '<select class="bk-cell-editor bk-cell-editor-select" />'
+
+    renderEditor: () ->
       for option in @model.get("options")
         @$el.append($('<option>').attr(value: option).text(option))
       @focus()
@@ -159,10 +162,8 @@

     el: '<input type="text" class="bk-cell-editor bk-cell-editor-int" />'

-    render: () ->
+    renderEditor: () ->
       @$el.spinner(step: @model.get("step"))
-      @disable_horizontal_navigation()
-      @disable_vertical_navigation()
       @$el.focus().select()

     remove: () ->
@@ -196,10 +197,8 @@

     el: '<input type="text" class="bk-cell-editor bk-cell-editor-number" />'

-    render: () ->
+    renderEditor: () ->
       @$el.spinner(step: @model.get("step"))
-      @disable_horizontal_navigation()
-      @disable_vertical_navigation()
       @$el.focus().select()

     remove: () ->


Expect an anchor name without a hash, as per Turbolinks.Location#anchor

--- src/turbolinks/snapshot.coffee
+++ src/turbolinks/snapshot.coffee
@@ -13,7 +13,7 @@
     @body = body

   hasAnchor: (anchor) ->
-    @body.querySelector(anchor)?
+    @body.querySelector("##{anchor}")?

   hasSameTrackedHeadElementsAsSnapshot: (snapshot) ->
     @getTrackedHeadElementSet().isEqualTo(snapshot.getTrackedHeadElementSet())


Remove extraneous - from variant.

Remove extraneous - from variant.

--- components/Title.cjsx
+++ components/Title.cjsx
@@ -18,7 +18,7 @@
         cx = new Classes('_Title', @props.className)
         if @props.link
             contents = {@props.title}
-            cx.add('-link')
+            cx.add('link')
         else
             contents = @props.title
         return React.createElement(tag, className: cx, contents)


Disable drag drop

Disable drag drop

--- assets/javascripts/application.coffee
+++ assets/javascripts/application.coffee
@@ -20,6 +20,3 @@
       widget_margins: Dashing.widget_margins
       widget_base_dimensions: Dashing.widget_base_dimensions
       avoid_overlapped_widgets: !Dashing.customGridsterLayout
-      draggable:
-        stop: Dashing.showGridsterInstructions
-        start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions()


Scroll past end

Scroll past end

--- config.cson
+++ config.cson
@@ -38,6 +38,7 @@
     ]
   editor:
     fontSize: 12
+    scrollPastEnd: true
     showIndentGuide: true
     softWrap: true
   "file-watcher":
@@ -70,9 +71,13 @@
   "terminal-fusion":
     core:
       workingDirectory: "Active File"
+    iconColors:
+      pink: "#ff0080"
+      purple: "#610661"
     style: {}
   "tool-bar":
     position: "Left"
+    visible: false
   "tree-view":
     autoReveal: true
     hideVcsIgnoredFiles: false


Update de-DE.coffee

Update de-DE.coffee

6hours of translating, only some Classroom stuff needs to be translated now.

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -36,43 +36,43 @@
     teaching_computer_science: "Informatik zu unterrichten benötigt keinen teuren Abschluss, weil wir Tools bereitstellen, die Lehrkräften im Hintergrund helfen."
     accessible_to: "Zugänglich für "
     everyone: "jeden"
-#    democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code."
+    democratizing: "Den Prozess programmieren zu erlernen zu demokratisierien steht im Mittelpunkt unserer Philosophie. Jeder sollte die Möglichkeit haben programmieren zu erlernen."
     forgot_learning: "Ich glaube sie vergessen sogar, dass sie gerade etwas lernen."
     wanted_to_do: " Programmieren ist etwas was ich immer wollte und ich hätte nie gedacht, dass ich es in der Schule lernen könnte."
     why_games: "Warum ist lernen durch Spiele wichtig?"
-#    games_reward: "Games reward the productive struggle."
-#    encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
-#    excel: "Games excel at rewarding"
-#    struggle: "productive struggle"
-#    kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
-#    motivating: "motivating"
-#    not_tedious: "not tedious."
+    games_reward: "Spiele belohnen die produktive Anstrengung."
+    encourage: "Spiele sind ein Medium, das Interaktionen, Entdeckungen und Versuch-und-Irrtum fördert. Ein gutes Spiel fordert den Spieler dazu auf Fähigkeiten über die Zeit hinweg zu erwerben, was der selbe entscheidende Prozess ist, durch den Schüler gehen, während sie etwas lernen."
+    excel: "Spiele glänzen im belohnen von"
+    struggle: "produktiven Anstrengungen"
+    kind_of_struggle: ", also den Anstrengungen die in einem Lernen resultieren, dass gewinnbringend und"
+    motivating: "motivierend ist"
+    not_tedious: "und nicht langweilig."
     gaming_is_good: "Studien legen nahe, dass spielen gut für das kindliche Gehirn ist. (Es ist wahr!)"
-#    game_based: "When game-based learning systems are"
-#    compared: "compared"
-#    conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and"
-#    perform_at_higher_level: "perform at a higher level of achievement"
-#    feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."
-#    real_game: "A real game, played with real coding."
-#    great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
-#    agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
-#    curious: "Curious? Request a demo and we'll show you the ropes"
-#    create_class: "Or create a class and see it for yourself!"
-#    request_demo: "Request a Demo"
+    game_based: "Wenn spielbasierte Lernsysteme "
+    compared: "verglichen"
+    conventional: "werden mit konventionellen Lehrmethoden, ist der Unterschied klar: Spiele sind besser darin den Schülern zu helfen Wissen zu erwerben, sich zu konzentrieren und darin eine"
+    perform_at_higher_level: "höhere Ebene der Leistung zu erreichen."
+    feedback: "Spiele bieten außerdem ein Echtzeit Feedback, das den Schülern erlaubt ihre Lösungswege anzupassen und Konzepte ganzheitlich zu verstehen, anstelle nur limitiert zu sein auf  “Korrekt“ oder  “Inkorrekt“ Antworten."
+    real_game: "Ein echtes Spiel, gespielt mit echtem programmieren."
+    great_game: "In einem guten Spiel geht es um mehr als nur Abzeichen und Erfolge - es geht um die Reise eines Spielers, gut-designte Rätsel, und die Fähigkeit Herausforderungen mit Kraft und Selbsvertrauen anzupacken."
+    agency: "CodeCombat ist ein Spiel, das Spielern die Kraft und das Selbstbewusstsein gibt durch unsere robuste Programmiereingabe-Engine, die sowohl Anfänger als auch fortgeschrittenen Schülern dabei hilft richtigen, validen Code zu schreiben."
+    curious: "Neugierig? Fragen Sie nach einer Demo und wir werden Ihnen zeigen wie es geht"
+    create_class: "Oder erstellen Sie eine Klasse und sehen Sie selbst!"
+    request_demo: "Demo anfragen"
     create_a_class: "Erstellen Sie eine Klasse"
     have_an_account: "Haben Sie bereits einen Account?"
     logged_in_as: "Sie sind zurzeit eingeloggt als"
     view_my_classes: "Zeige meine Klassen"
-#    computer_science: "Computer science courses for all ages"
-#    show_me_lesson_time: "Show me lesson time estimates for:"
-#    curriculum: "Total curriculum hours:"
+    computer_science: "Informatikkurse für alle Altersgruppen"
+    show_me_lesson_time: "Zeig mir die vorgeschlagene Stundenzeit für:"
+    curriculum: "Gesamte Lehrstunden:"
     ffa: "Kostenlos für alle Schüler"
     lesson_time: "Lektionszeit:"
     coming_soon: "Coming soon!"
     courses_available_in: "Kurse sind verfügbar in JavaScript, Python, und Java (coming soon!)"
-#    boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
-#    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
-#    run_class: "Everything you need to run a computer science class in your school today, no CS background required."
+    boast: "Bietet Rätsel die komplex genug sind um Spieler und Programmiere zu faszinieren."
+    winning: "Eine gewinnbringende Kombination aus RPG-Gameplay und Programmierhausaufgaben die aufgeht und kindgerechte Bildung berechtigterweise unterhaltsam macht."
+    run_class: "Alles was man braucht um einen Informatikkurs in der Schule zu geben, ohne dass man Hintergrundwissen braucht."

   nav:
     play: "Spielen" # The top nav bar entry where players choose which levels to play
@@ -607,63 +607,66 @@
     editor_config_behaviors_description: "Vervollständigt automatisch Klammern und Anführungszeichen."

   about:
-#    main_title: "If you want to learn to program, you need to write (a lot of) code."
-#    main_description: "At CodeCombat, our job is to make sure you're doing that with a smile on your face."
-#    mission_link: "Mission"
-#    team_link: "Team"
-#    community_link: "Community"
-#    story_link: "Story"
-#    careers_link: "Careers"
-#    press_link: "Press"
-#    mission_title: "Our mission: make programming accessible to every student on Earth."
-#    mission_description_1: "Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code." # {change}
-#    mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming." # {change}
-#    team_title: "Meet the CodeCombat team"
-#    team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
-    nick_title: "Programmierer" # {change}
+    main_title: "Wenn du das Programmieren erlernen willst, musst du (viel) Code schreiben."
+    main_description: "Bei CodeCombat ist es unser Job das du das mit einem Lächeln im Gesicht tust."
+    mission_link: "Mission"
+    team_link: "Team"
+    community_link: "Community"
+    story_link: "Geschichte"
+    careers_link: "Karrieren"
+    press_link: "Presse"
+    mission_title: "Unsere Mission: Programmieren jedem Schüler auf der Erde zugänglich zu machen."
+    mission_description_1: "Programmieren ist Magie. Es ist die Fähigkeit Dinge aus purer Vorstellungskraft zu erschaffen.
+    Wir starteten CodeCombat um Lernen dieses Gefühl der Zauberkraft in ihren Fingerspitzen zu geben während sie getippten Code benutzen." # {change}
+    mission_description_2: "Wie sich herausstellt, können sie dadurch auch schneller lernen. Sehr viel schneller.
+    Es ist mehr wie eine Unterhaltung als das Lesen einer Bedienungsanleitung. Wir wollen diese Unterhaltung in jede Schule zu
+    jedem Schüler bringen, weil jeder sollte die Chance haben die Magie des Programmierens zu erlernen." # {change}
+    team_title: "Triff das CodeCombat Team"
+    team_values: "Wir legen Wert auf einen offenen und respektvollen Dialog, wo die besten Ideen gewinnen. Unsere Entscheidungen basieren auf Benutzerforschung und unser Fortschritt richtet sich danach ihnen handfeste Resultate zu liefern. Jeder packt mit an von unserem Geschäftsführer bis hin zu unseren  Github-Unterstützern, weil wir Wert auf Zuwachs und Lernen in unserem Team legen."
+    nick_title: "Mitbegründer, Geschäftsführer" # {changed}
     nick_blurb: "Motivationsguru"
-    matt_title: "Programmierer" # {change}
+    matt_title: "Mitbegründer, Technischer Leiter" # {changed}
     matt_blurb: "Radfahrer"
-    cat_title: "Chief Artisan" # {change}
-    cat_blurb: "Luftbändiger"
-    scott_title: "Programmierer" # {change}
+    cat_title: "Game Designerin" # {changed}
+    cat_blurb: "Luftbändigerin"
+    scott_title: "Mitbegründer, Software Ingeneur" # {changed}
     scott_blurb: "Der Vernünftige"
-#    maka_title: "Customer Advocate"
-#    maka_blurb: "Storyteller"
-#    rob_title: "Software Engineer"
-#    rob_blurb: "Codes things and stuff"
-#    josh_c_title: "Game Designer"
-#    josh_c_blurb: "Designs games"
-#    robin_title: "UX Design & Research"
-#    robin_blurb: "Scaffolding"
-    josh_title: "Spiel Designer"
+    maka_title: "Kundenabteilung"
+    maka_blurb: "Geschichtenerzähler"
+    rob_title: "Software Engineer"
+    rob_blurb: "Programmiert Zeug und Dinge"
+    josh_c_title: "Game Designer"
+    josh_c_blurb: "Designt Spieler"
+    robin_title: "Nutzererfahrung Design & Forschung"
+    robin_blurb: "Das Gerüst"
+    josh_title: "Game Designer"
     josh_blurb: "Boden ist Lava"
     retrostyle_title: "Illustration"
     retrostyle_blurb: "RetroStyle Games"
     jose_title: "Musik"
     jose_blurb: "Taking Off"
-#    carlos_title: "Region Manager, Brazil"
-#    carlos_blurb: "Celery Man"
-#    community_title: "...and our open-source community"
-#    community_subtitle: "Over 450 contributors have helped build CodeCombat, with more joining every week!"
-#    community_description_1: "CodeCombat is a community project, with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
-#    community_description_link: "contribute page"
-#    community_description_2: "for more info."
-#    number_contributors: "Over 450 contributors have lent their support and time to this project."
-#    story_title: "Our story so far"
-#    story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
-#    story_statistic_1a: "5,000,000+"
-#    story_statistic_1b: "total players"
-#    story_statistic_1c: "have started their programming journey through CodeCombat"
-#    story_statistic_2a: "We’ve been translated into over 50 languages — our players hail from"
-#    story_statistic_2b: "200+ countries"
-#    story_statistic_3a: "Together, they have written"
-#    story_statistic_3b: "1 billion lines of code and counting"
-#    story_statistic_3c: "across six different programming languages"
-#    story_long_way_1: "Though we've come a long way..."
-#    story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
-#    story_long_way_2: "we still have much to do before we complete our quest, so..."
-#    jobs_title: "Come work with us and help write CodeCombat history!"
+    carlos_title: "Regional Manager, Brasilien"
+    carlos_blurb: "Sellerie Mann"
+    community_title: "...und unsere Open-Source Community"
+    community_subtitle: "Mehr als 450 Unterstützer haben dabei geholfen CodeCombat zu erstellen, und es werden jede Woche mehr!"
+    community_description_1: "CodeCombat ist ein Communityprojekt, mit hunderten von Spielern die freiwillig Level erstellen, neue Features zum Quellcode beditragen, Bugs beheben, testen und sogar das Spiel in bisher mehr als 50 Sprachen übersetzen. Angestellte, Unterstützer und die Seite profitieren vom Teilen von Ideen und von den gemeinsamen Anstrengungen, so wie die Open-Source-Community im Allgemeinen. Die Seite basiert auf mehreren Open Source Projekten und wir sind Open-Source um der Community etwas zurückzugeben und neugierigen Spielern ein familiäres Projekt zum entdecken und experimentieren verfügbar zu machen. Jeder kann der CodeCombat-Community beitreten! Schau auf unserer"
+    community_description_link: " Mitwirken Seite"
+    community_description_2: "für weitere Informationen vorbei."
+    number_contributors: "Mehr als 450 Unterstützer haben ihre Zeit und Hilfe für dieses Projekt zur Verfügung gestellt."
+    story_title: "Unsere bisherige Geschichte"
+    story_subtitle: "Seit 2013 ist CodeCombat aus einigen Skizzen zu einem lebenden, florierenden Spiel gewachsen."
+   story_statistic_1a: "Mehr als 5,000,000"
+  story_statistic_1b: "Spieler"
+    story_statistic_1c: "haben ihre Programmierreise durch CodeCombat begonnen."
+    story_statistic_2a: "Wir wurden in über 50 Sprachen übersetzt — unsere Spieler kommen aus mehr als"
+    story_statistic_2b: "200 Staaten"
+    story_statistic_3a: "Zusammen haben sie mehr als"
+    story_statistic_3b: "1 Milliarde Zeilen Code geschrieben"
+    story_statistic_3c: "in sechs verschiedenen Programmiersprachen geschrieben"
+    story_long_way_1: "Obwohl wir einen langen Weg gegangen sind..."
+    story_sketch_caption: "Nick's allererste Skizze zeigt ein Programmierspiel in Aktion."
+    story_long_way_2: "haben wir noch viel zu tun bevor unser Auftrag abgeschlossen ist, also..."
+    jobs_title: "Komm arbeite mit uns und hilf mit CodeCombat Geschichte zu schreiben!"
 #    jobs_subtitle: "Don't see a good fit but interested in keeping in touch? See our \"Create Your Own\" listing."
 #    jobs_benefits: "Employee Benefits"
 #    jobs_benefit_1: "Competitive salary and options"
@@ -672,24 +675,24 @@
 #    jobs_benefit_4: "Unlimited sick/personal days"
 #    jobs_benefit_5: "Professional development and continuing education support"
 #    jobs_benefit_6: "Medical/dental/vision insurance"
-#    learn_more: "Learn More"
-#    jobs_custom_title: "Create Your Own"
-#    jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
-#    jobs_custom_contact_1: "Send us a note at"
-#    jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"
-#    contact_title: "Press & Contact"
-#    contact_subtitle: "Need more information? Get in touch with us at"
-#    screenshots_title: "Game Screenshots"
-#    screenshots_hint: "(click to view full size)"
-#    downloads_title: "Download Assets & Information"
-#    about_codecombat: "About CodeCombat"
-#    logo: "Logo"
-#    screenshots: "Screenshots"
-#    character_art: "Character Art"
-#    download_all: "Download All"
-#    previous: "Previous"
-#    next: "Next"
-#    location_title: "We're located in downtown SF:"
+    learn_more: "Erfahre mehr"
+    jobs_custom_title: "Kreiere deinen Eigenen"
+    jobs_custom_description: "Du bist fasziniert von CodeCombat aber siehst keinen Job aufgeführt, der zu deinen Qualifikationen passt? Schreib uns und überzeuge uns wie du denkst, wie du in unserem Team mitwirken kannst. Wir freuen uns von dir zu hören!"
+    jobs_custom_contact_1: "Sende uns eine Nachricht in der du "
+    jobs_custom_contact_2: "dich vorstellst und wir sehen uns vielleicht in naher Zukunft!"
+    contact_title: "Presse & Kontakt"
+    contact_subtitle: "Brauchst du weitere Informationen? Melde dich bei uns unter"
+    screenshots_title: "Game Screenshots"
+    screenshots_hint: "(Klicken um in Vollbild zu betrachten)"
+    downloads_title: "Downloade Vorzüge & Informationen"
+    about_codecombat: "Über CodeCombat"
+    logo: "Logo"
+    screenshots: "Screenshots"
+    character_art: "Character Art"
+    download_all: "Downloade Alles"
+    previous: "Vorheriges"
+    next: "Nächstes"
+    location_title: "Wir haben unseren Sitz in Downtown SanFrancisco:"

   teachers:
     who_for_title: "An wen richtet sich CodeCombat?"
@@ -705,30 +708,30 @@
 #    title: "Request a Quote"
 #    subtitle: "Get CodeCombat in your classroom, club, school or district!"
 #    email_exists: "User exists with this email."
-#    phone_number: "Phone number"
+    phone_number: "Telephonnummer"
 #    phone_number_help: "Where can we reach you during the workday?"
-#    role_label: "Your role"
-#    role_help: "Select your primary role."
+    role_label: "Ihre Rolle"
+    role_help: "Wählen Sie ihre primäre Rolle."
 #    tech_coordinator: "Technology coordinator"
 #    advisor: "Advisor"
 #    principal: "Principal"
 #    superintendent: "Superintendent"
-#    parent: "Parent"
-#    organization_label: "Name of School/District"
-#    city: "City"
-#    state: "State"
-#    country: "Country"
+    parent: "Elternteil"
+    organization_label: "Name der Schule/Bezirk"
+    city: "Stadt"
+    state: "Bundesland"
+    country: "Land"
 #    num_students_help: "How many do you anticipate enrolling in CodeCombat?"
 #    education_level_label: "Education Level of Students"
 #    education_level_help: "Choose as many as apply."
-#    elementary_school: "Elementary School"
-#    high_school: "High School"
-#    please_explain: "(please explain)"
-#    middle_school: "Middle School"
-#    college_plus: "College or higher"
-#    anything_else: "Anything else we should know?"
+    elementary_school: "Grundschule"
+    high_school: "Gymnasium"
+    please_explain: "(bitte erklären)"
+    middle_school: "Mittelschule"
+    college_plus: "Hochschule oder höher"
+    anything_else: "Weitere Dinge die wir wissen sollten?"
 #    thanks_header: "Thanks for requesting a quote!"
-#    thanks_p: "We'll be in touch soon. Questions? Email us:"
+    thanks_p: "Wir melden uns bald. Fragen? Schreiben Sie uns eine Email :"
 #    thanks_anon: "Login or sign up with your account below to access your two free enrollments (we’ll notify you by email when they have been approved, which usually takes less than 48 hours). As always, the first hour of content is free for an unlimited number of students."
 #    thanks_logged_in: "Your two free enrollments are pending approval. We’ll notify you by email when they have been approved (usually within 48 hours). As always, the first hour of content is free for an unlimited number of students."

@@ -740,7 +743,7 @@
     cla_url: "CLA"
     cla_suffix: ") akzeptieren."
     cla_agree: "Ich stimme zu"
-#    owner_approve: "An owner will need to approve it before your changes will become visible."
+    owner_approve: "Ein Eigentümer muss deine Änderungen genehmigen, bevor sie sichtbar werden."

   contact:
     contact_us: "Kontaktiere CodeCombat"
@@ -996,13 +999,13 @@
     greetings: "Grüße!"
     learn_p: "Erlerne Python"
     learn_j: "Erlerne JavaScript"
-#    language_cannot_change: "Language cannot be changed once students join a class."
-#    back_classrooms: "Back to my classrooms"
-#    back_courses: "Back to my courses"
+    language_cannot_change: "Die Sprache kann nicht geändert werden sobald ein Schüler der Klasse beigetreten ist."
+    back_classrooms: "Zurück zu meinem Klassenraum"
+    back_courses: "Zurück zu meinen Kursen"
 #    edit_details: "Edit class details"
 #    enrolled_courses: "enrolled in paid courses:"
 #    purchase_enrollments: "Purchase Enrollments"
-#    remove_student: "remove student"
+    remove_student: "Schüler entfernen"
 #    assign: "Assign"
 #    to_assign: "to assign paid courses."
     teacher: "Lehrer"
@@ -1021,7 +1024,7 @@
 #    not_enrolled1: "Not enrolled"
 #    not_enrolled2: "Ask your teacher to enroll you in the next course."
     next_course: "Nächster Kurs"
-#    coming_soon1: "Coming soon"
+    coming_soon1: "Coming soon"
 #    coming_soon2: "We are hard at work making more courses for you!"
 #    available_levels: "Available Levels"
 #    welcome_to_courses: "Adventurers, welcome to Courses!"
@@ -1308,7 +1311,7 @@
     ambassador_join_note_strong: "Anmerkung"
     ambassador_join_note_desc: "Eine unserer Top Prioritäten ist Mehrspieler Level zu entwerfen. Dort können Spieler, die noch Probleme mit bestimmten Themen haben erfahrenere Zauberer beschwören um ihnen zu helfen. Dies wird ein hervorragender Weg für unsere Botschafter sein, um ihren Job zu erledigen. Wir halten dich auf dem Laufenden!"
     ambassador_subscribe_desc: "Erhalte Emails über Support-Updates und Mehrspieler-Entwicklungen."
-#    teacher_subscribe_desc: "Get emails on updates and announcements for teachers."
+    teacher_subscribe_desc: "Erhalte Emails über Updates und Ankündigungen für Lehrkräfte."
     changes_auto_save: "Änderungen an Checkboxen werden automatisch gespeichert."
     diligent_scribes: "Unsere fleißigen Schreiber:"
     powerful_archmages: "Unsere mächtigen Erzmagier:"
@@ -1321,7 +1324,7 @@
     please_login: "Bitte logge dich zunächst ein, bevor du ein Ladder-Game spielst."
     my_matches: "Meine Matches"
     simulate: "Simuliere"
-    simulation_explanation: "Durch das Simulieren von Spielen kannst du deine Spiele schneller rangiert bekommen!"
+#    simulation_explanation: "Durch das Simulieren von Spielen kannst du deine Spiele schneller rangiert bekommen!"{to change}
 #    simulation_explanation_leagues: "You will mainly help simulate games for allied players in your clans and courses."
     simulate_games: "Simuliere Spiele!"
     games_simulated_by: "Spiele die durch dich simuliert worden:"
@@ -1354,7 +1357,7 @@
     tutorial_skip: "Überspringe Tutorial"
     tutorial_not_sure: "Nicht sicher was hier ab geht?"
     tutorial_play_first: "Spiele zuerst das Tutorial."
-    simple_ai: "Einfache KI" # {change}
+#    simple_ai: "Einfache KI" # {change}
     warmup: "Aufwärmen"
     friends_playing: "spielende Freunde"
     log_in_for_friends: "Melde dich an, um mit deinen Freunden zu spielen!"
@@ -1468,8 +1471,8 @@
     could_not_load: "Fehler beim Laden vom Server"
     connection_failure: "Verbindung fehlgeschlagen." # {change}
 #    connection_failure_desc: "It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page."
-#    login_required: "Login Required"
-#    login_required_desc: "You need to be logged in to access this page."
+    login_required: "Login benötigt"
+    login_required_desc: "Du musst eingeloggt sein um auf diese Seite zuzugreifen."
     unauthorized: "Du musst angemeldet sein. Hast du Cookies ausgeschaltet?"
     forbidden: "Sie haben nicht die nötigen Berechtigungen." # {change}
 #    forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!"
@@ -1512,7 +1515,7 @@
     if_statements: "If Anweisungen"
     input_handling: "Eingabebehandlung"
     math_operations: "Mathematische Operationen"
-#    object_literals: "Object Literals"
+    object_literals: "Objekt Literale"
     parameters: "Parameter"
     strings: "Strings"
     variables: "Variablen"


Update de-DE.coffee

Update de-DE.coffee

Translation of error messages.

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -2234,14 +2234,14 @@
     email_taken: "Diese E-Mail wird bereits verwendet"
     username_taken: "Benutzername ist bereits vergeben"

-#  esper:
-#    line_no: "Line $1: "
-#    x_not_a_function: "`$1` is not a function"
-#    type_error: "TypeError: "
-#    reference_error: "ReferenceError: "
-#    spelling_issues: "Look out for spelling issues: did you mean `$1` instead of `$2`?"
-#    capitalization_issues: "Look out for capitalization: `$1` should be `$2`."
-#    py_empty_block: "Empty $1. Put 4 spaces in front of statements inside the $2."
-#    fx_missing_paren: "If you want to call `$1` as function, you need `()`'s"
-#    unmatched_token: "Unmatched `$1`.  Every opening `$2` needs a closing `$3` to match it."
-#    unterminated_string: "Unterminated string. Add a matching `\"` at the end of your string."
+  esper:
+    line_no: "Zeile $1: "
+    x_not_a_function: "`$1` ist keine Funktion."
+    type_error: "TypFehler: "
+    reference_error: "BezugsFehler: "
+    spelling_issues: "Achte auf die Schreibweise: meintest du `$1` anstelle von `$2`?"
+    capitalization_issues: "Achte auf Groß-/Kleinschreibung: `$1` sollte `$2` heißen."
+    py_empty_block: "Leeres $1. Schreibe 4 Leerzeichen vor allen Anweisungen innerhalb des $2."
+    fx_missing_paren: "Wenn du `$1` als Funktion verwenden möchtest, dann brauchst du `()`"
+    unmatched_token: "`$1` ohne Gegenpart.  Jede öffnende `$2` benötigt eine zugehörige schließende `$3` ."
+    unterminated_string: "String ohne Abschluss. Füge ein ein `\"` am Ende deiner Zeichenkette hinzu."


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -677,10 +677,10 @@
     email: "Endereço de E-mail"
     school: "Nome da Escola"
     location: "Nome da Cidade"
-    age_students: "Que idade têm os teus alunos?"
+    age_students: "Que idade têm os teus estudantes?"
     under: "Menos de"
     other: "Outra:"
-    amount_students: "Quantos alunos ensinas?"
+    amount_students: "Quantos estudantes ensinas?"
     hear_about: "Como ouviste falar do CodeCombat?"
     fill_fields: "Por favor, preenche todos os campos."
     thanks: "Obrigado! Vamos enviar-te as instruções de configuração em breve."
@@ -843,89 +843,89 @@
     last_played: "Última vez jogado"
     leagues_explanation: "Joga numa liga contra outros membros do clã nestas instâncias de arenas multijogador."

-#  courses:
-#    course: "Course"
-#    courses: "courses"
-#    not_enrolled: "You are not enrolled in this course."
-#    visit_pref: "Please visit the"
-#    visit_suf: "page to enroll."
-#    select_class: "Select one of your classes"
-#    unnamed: "*unnamed*"
-#    select: "Select"
-#    unnamed_class: "Unnamed Class"
-#    edit_settings: "edit class settings"
-#    edit_settings1: "Edit Class Settings"
-#    progress: "Class Progress"
-#    add_students: "Add Students"
-#    stats: "Statistics"
-#    total_students: "Total students:"
-#    average_time: "Average level play time:"
-#    total_time: "Total play time:"
-#    average_levels: "Average levels completed:"
-#    total_levels: "Total levels completed:"
-#    furthest_level: "Furthest level completed:"
-#    concepts_covered: "Concepts Covered"
-#    students: "Students"
-#    students1: "students"
-#    expand_details: "Expand details"
-#    concepts: "Concepts"
-#    levels: "levels"
-#    played: "Played"
-#    play_time: "Play time:"
-#    completed: "Completed:"
-#    invite_students: "Invite students to join this class."
-#    enter_emails: "Enter student emails to invite, one per line"
-#    send_invites: "Send Invites"
-#    title: "Title"
-#    description: "Description"
-#    languages_available: "Select programming languages available to the class:"
-#    all_lang: "All Languages"
-#    show_progress: "Show student progress to everyone in the class"
-#    creating_class: "Creating class..."
-#    purchasing_course: "Purchasing course..."
-#    buy_course: "Buy Course"
-#    buy_course1: "Buy this course"
-#    create_class: "Create Class"
-#    select_all_courses: "Select 'All Courses' for a 50% discount!"
-#    all_courses: "All Courses"
-#    number_students: "Number of students"
-#    enter_number_students: "Enter the number of students you need for this class."
-#    name_class: "Name your class"
-#    displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
-#    buy: "Buy"
-#    purchasing_for: "You are purchasing a license for"
-#    creating_for: "You are creating a class for"
-#    for: "for" # Like in 'for 30 students'
+  courses:
+    course: "Curso"
+    courses: "cursos"
+    not_enrolled: "Não estás inscrito neste curso."
+    visit_pref: "Por favor visita a página dos"
+    visit_suf: "para te inscreveres."
+    select_class: "Seleciona uma das tuas turmas"
+    unnamed: "*sem nome*"
+    select: "Selecionar"
+    unnamed_class: "Turma Sem Nome"
+    edit_settings: "editar definições da turma"
+    edit_settings1: "Editar Definições da Turma"
+    progress: "Progresso da Turma"
+    add_students: "Adicionar Estudantes"
+    stats: "Estatísticas"
+    total_students: "Estudantes no total:"
+    average_time: "Média do tempo de jogo do nível:"
+    total_time: "Tempo de jogo total:"
+    average_levels: "Média de níveis completos:"
+    total_levels: "Total de níveis completos:"
+    furthest_level: "Nível mais avançado completo:"
+    concepts_covered: "Conceitos Abordados"
+    students: "Estudantes"
+    students1: "estudantes"
+    expand_details: "Expandir detalhes"
+    concepts: "Conceitos"
+    levels: "níveis"
+    played: "Jogados"
+    play_time: "Tempo de jogo:"
+    completed: "Completos:"
+    invite_students: "Convidar estudantes para se juntarem a esta turma."
+    enter_emails: "Introduz os e-mails dos estudantes a convidar, um por linha"
+    send_invites: "Enviar Convites"
+    title: "Título"
+    description: "Descrição"
+    languages_available: "Seleciona as linguagens de programação disponíveis para a turma:"
+    all_lang: "Todas as Linguagens"
+    show_progress: "Mostrar o progresso dos estudantes para todos da turma"
+    creating_class: "A criar turma..."
+    purchasing_course: "A comprar curso..."
+    buy_course: "Comprar Curso"
+    buy_course1: "Comprar este curso"
+    create_class: "Criar Turma"
+    select_all_courses: "Seleciona 'Todos os Cursos' para teres 50% de desconto!"
+    all_courses: "Todos os Cursos"
+    number_students: "Número de estudantes"
+    enter_number_students: "Introduz o número de estudantes que precisas para esta turma."
+    name_class: "Dá um nome à tua turma"
+    displayed_course_page: "Isto vai ser mostrado na página do curso para ti e para os teus estudantes. Pode ser mudado mais tarde."
+    buy: "Comprar"
+    purchasing_for: "Estás a comprar uma licença para"
+    creating_for: "Estás a criar uma turma para"
+    for: "para" # Like in 'for 30 students'
 #    receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
-#    free_trial: "Free trial for teachers!"
+    free_trial: "Avaliação gratuita para professores!"
 #    get_access: "to get individual access to all courses for evalutaion purposes."
-#    questions: "Questions?"
-#    faq: "Courses FAQ"
-#    question: "Q:" # Like in 'Question'
+    questions: "Perguntas?"
+    faq: "FAQ dos Cursos"
+    question: "P:" # Like in 'Question'
 #    question1: "What's the difference between these courses and the single player game?"
-#    answer: "A:" # Like in 'Answer'
+    answer: "R:" # Like in 'Answer'
 #    answer1: "The single player game is designed for individuals, while the courses are designed for classes."
 #    answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
-#    teachers_click: "Teachers Click Here"
-#    students_click: "Students Click Here"
-#    courses_on_coco: "Courses on CodeCombat"
+    teachers_click: "Professores Cliquem Aqui"
+    students_click: "Estudantes Cliquem Aqui"
+    courses_on_coco: "Cursos no CodeCombat"
 #    designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
-#    more_in_less: "Learn more in less time"
-#    no_experience: "No coding experience necesssary"
+    more_in_less: "Aprende mais em menos tempo"
+    no_experience: "Não é necessária experiência com programação"
 #    easy_monitor: "Easily monitor student progress"
 #    purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
-#    see_the: "See the"
-#    more_info: "for more information."
-#    choose_course: "Choose Your Course:"
+    see_the: "Vê o"
+    more_info: "para mais informações."
+    choose_course: "Escolhe o Teu Curso:"
 #    enter_code: "Enter an unlock code"
 #    enter_code1: "Enter unlock code"
-#    enroll: "Enroll"
+    enroll: "Inscrever"
 #    pick_from_classes: "Pick from your current classes"
-#    enter: "Enter"
-#    or: "Or"
-#    topics: "Topics"
-#    hours_content: "Hours of content:"
-#    get_free: "Get FREE course"
+    enter: "Introduzir"
+    or: "Ou"
+    topics: "Tópicos"
+    hours_content: "Horas de conteúdo:"
+    get_free: "Obtém um curso GRÁTIS"

   classes:
     archmage_title: "Arcomago"
@@ -1212,7 +1212,7 @@
     recently_played: "Jogados Recentemente"
     no_recent_games: "Sem jogos jogados nas passadas duas semanas."
     payments: "Pagamentos"
-#    prepaid: "Prepaid"
+    prepaid: "Pré-pago"
     purchased: "Adquirido"
     sale: "Promoção"
     subscription: "Subscrição"
@@ -1243,13 +1243,13 @@
     retrying: "Erro do servidor, a tentar novamente."
     success: "Pago com sucesso. Obrigado!"

-#  account_prepaid:
-#    purchase_code: "Purchase a Subscription Code"
-#    purchase_amount: "Amount"
-#    purchase_total: "Total"
-#    purchase_button: "Submit Purchase"
-#    your_codes: "Your Codes:"
-#    redeem_codes: "Redeem a Subscription Code"
+  account_prepaid:
+    purchase_code: "Comprar um Código de Subscrição"
+    purchase_amount: "Quantidade"
+    purchase_total: "Total"
+    purchase_button: "Submeter Compra"
+    your_codes: "Os Teus Códigos:"
+    redeem_codes: "Resgata um Código de Subscrição"

   loading_error:
     could_not_load: "Erro ao carregar do servidor"
@@ -1263,7 +1263,7 @@
     bad_input: "Má entrada."
     server_error: "Erro do servidor."
     unknown: "Erro desconhecido."
-#    error: "ERROR"
+    error: "ERRO"

   resources:
     sessions: "Sessões"


Remove unused variable

Remove unused variable

--- src/injections.coffee
+++ src/injections.coffee
@@ -13,7 +13,6 @@
       patterns = []
       for regex in values.patterns
         pattern = @grammar.createPattern(regex)
-        anchored = true if pattern.anchored
         patterns.push(pattern.getIncludedPatterns(grammar, patterns)...)
       @injections.push
         selector: new ScopeSelector(selector)


FinderController: update hide feature to work with machine.uid

--- client/Finder/filetree/controllers/findercontroller.coffee
+++ client/Finder/filetree/controllers/findercontroller.coffee
@@ -235,15 +235,13 @@
           @reloadPreviousState vmName
         , yes

-  isNodesHiddenFor:(vmName)->
+  isNodesHiddenFor:(uid)->
     return yes  if @getOption 'hideDotFiles'
-    pipedVm = @_pipedVmName vmName
-    return (@appStorage.getValue('vmsDotFileChoices') or {})[pipedVm]
-
-  setNodesHidden:(vmName, state)->
-    pipedVm = @_pipedVmName vmName
-    prefs   = @appStorage.getValue('vmsDotFileChoices') or {}
-    prefs[pipedVm] = state
+    return (@appStorage.getValue('machinesDotFileChoices') or {})[uid]
+
+  setNodesHidden:(uid, state)->
+    prefs = @appStorage.getValue('machinesDotFileChoices') or {}
+    prefs[uid] = state
     @appStorage.setValue 'vmsDotFileChoices', prefs

   getRecentFolders:->


NFinderController: fix `mountVm` method call in `updateVMRoot` method

--- client/Finder/filetree/controllers/findercontroller.coffee
+++ client/Finder/filetree/controllers/findercontroller.coffee
@@ -212,7 +212,10 @@
     vmRoots[pipedVm] = path
     @appStorage.setValue 'vmRoots', vmRoots  if @getOptions().useStorage

-    @mountVm "#{vmName}:#{path}"
+    KD.singleton("vmController").fetchVmsByName [vmName], (err, [vm]) =>
+      return KD.showError err  if err
+      vm.path = path
+      @mountVm vm

   cleanup:->
     @treeController.removeAllNodes()


activity: sort messages correctly when rendering PublicFeedPane

--- client/activity/lib/components/publicfeedpane/index.coffee
+++ client/activity/lib/components/publicfeedpane/index.coffee
@@ -11,11 +11,12 @@

   getMessages: ->
-    @props.messages
-      .toSeq()
-      .sortBy (m) -> m.get 'createdAt'
-      .reverse()
-      .toMap()
+    return immutable.Map()  unless @props.messages
+
+    @props.messages.sort (a, b) ->
+      if a.get 'createdAt' > b.get 'createdAt' then 1
+      else if b.get 'createdAt' < b.get 'createdAt' then return -1
+      else 0

   render: ->


Remove demo app

Remove demo app

--- client/app/Applications/Demos.kdapplication/AppView.coffee
+++ client/app/Applications/Demos.kdapplication/AppView.coffee
@@ -1,143 +1,3 @@
 class DemosMainView extends KDScrollView

   viewAppended:->
-
-    @addSubView scene = new EnvironmentScene
-
-    rulesContainer    = new EnvironmentContainer
-      title           : "Rules"
-      itemClass       : EnvironmentRuleItem
-    scene.addContainer rulesContainer
-
-    domainsContainer  = new EnvironmentContainer
-      title           : "Domains"
-      itemClass       : EnvironmentDomainItem
-    scene.addContainer domainsContainer, x: 300
-
-    machinesContainer = new EnvironmentContainer
-      title           : "Machines"
-      itemClass       : EnvironmentMachineItem
-    scene.addContainer machinesContainer, x: 580
-
-    rulesContainer.addItem
-      title       : 'Internal Access'
-      description : 'Deny: All, Allow: 127.0.0.1'
-      activated   : yes
-
-    rulesContainer.addItem
-      title       : 'No China'
-      description : 'Deny: All, Allow: Loc: [China]'
-      notes       : 'We need this for bla bla bla...'
-
-    domainsContainer.addItem
-      title       : 'gokmen.kd.io'
-      description : 'Main domain'
-      activated   : yes
-
-    domainsContainer.addItem
-      title       : 'git.gokmen.kd.io'
-      description : 'Koding GIT repository'
-      notes       : 'I keep all the source in here.'
-
-    domainsContainer.addItem
-      title       : 'svn.gokmen.kd.io'
-      description : 'Koding SVN repository'
-
-    machinesContainer.addItem
-      title : 'vm-0.gokmen.koding.kd.io'
-      usage : 20
-
-    machinesContainer.addItem
-      title     : 'vm-1.gokmen.koding.kd.io'
-      usage     : 45
-      activated : yes
-
-class EnvironmentScene extends KDDiaScene
-  constructor:->
-    super
-      cssClass  : 'environments-scene'
-      lineWidth : 1
-
-class EnvironmentContainer extends KDDiaContainer
-  constructor:(options={}, data)->
-    options.cssClass  = 'environments-container'
-    options.draggable = yes
-    super options, data
-
-    title = @getOption 'title'
-    @header = new KDHeaderView {type : "medium", title}
-
-  viewAppended:->
-    super
-    @addSubView @header
-
-  addDia:(diaObj, pos)->
-    pos = x: 20, y: 60 + @diaCount() * 50
-    super diaObj, pos
-    @updateHeight()
-
-  diaCount:-> Object.keys(@dias).length
-  updateHeight:-> @setHeight 80 + @diaCount() * 50
-
-class EnvironmentItem extends KDDiaObject
-  constructor:(options={}, data)->
-    options.cssClass = KD.utils.curry 'environments-item', options.cssClass
-    options.jointItemClass = EnvironmentItemJoint
-    options.draggable = no
-    super options, data
-
-  viewAppended:->
-    super
-    @setClass 'activated'  if @getData().activated?
-
-  pistachio:->
-    """
-

-        {h3{#(title)}}
-        {{#(description)}}
-

-    """
-
-class EnvironmentRuleItem extends EnvironmentItem
-  constructor:(options={}, data)->
-    options.joints             = ['right']
-    options.cssClass           = 'rule'
-    options.allowedConnections =
-      EnvironmentDomainItem : ['left']
-    super options, data
-
-class EnvironmentDomainItem extends EnvironmentItem
-  constructor:(options={}, data)->
-    options.joints = ['left', 'right']
-    options.cssClass = 'domain'
-    options.allowedConnections =
-      EnvironmentRuleItem    : ['right']
-      EnvironmentMachineItem : ['left']
-    super options, data
-
-class EnvironmentMachineItem extends EnvironmentItem
-  constructor:(options={}, data)->
-    options.joints = ['left']
-    options.cssClass = 'machine'
-    options.allowedConnections =
-      EnvironmentDomainItem : ['right']
-    super options, data
-    @usage = new KDProgressBarView
-
-  viewAppended:->
-    super
-    @usage.updateBar @getData().usage, '%', ''
-
-  pistachio:->
-    """
-

-        {h3{#(title)}}
-        {{> @usage}}
-

-    """
-
-class EnvironmentItemJoint extends KDDiaJoint
-  constructor:(options={}, data)->
-    options.cssClass = 'environments-joint'
-    options.size     = 4
-    super options, data


Like when likeLink clicked

Like when likeLink clicked

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

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

         {{> @avatar}}

-          {{> @deleteLink}}

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

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


feat(obj-constancy): transition for x axis

--- app/scripts/main.coffee
+++ app/scripts/main.coffee
@@ -19,13 +19,10 @@
 group3 = [7..10]

 data = [group1, group2, group3]
-
 dataMin = d3.min data, (d) -> d3.min d
 dataMax = d3.max data, (d) -> d3.max d

-
 xScale = d3.scale.linear()
-    .domain([0, dataMax])
     .range([0, svgWidth])

 yScale = d3.scale.ordinal()
@@ -63,10 +60,33 @@
         class: "domain"
         y2: svgHeight

-placeBars = (data) ->
+draw = (data) ->
+
+    dataMax = d3.max data
+    xScale.domain([0, dataMax])
+
+    svg
+        .select ".x.axis"
+        .transition()
+        .delay 1000
+        .duration 1000
+        .call xAxis

     bar = svg.selectAll "rect"
         .data data
+
+    barUpdate = bar
+        .transition()
+        .delay 1000
+        .duration 1000
+        .style
+            stroke: "gray"
+            fill: (d) -> colorScale(d)
+        .attr
+            x: barPadding
+            y: (d, i) -> yScale(i)
+            width: (d) -> xScale(d)
+            height: -> yScale.rangeBand()

     barEnter = bar.enter()
         .append "rect"
@@ -79,27 +99,22 @@
             width: (d) -> xScale(d)
             height: -> yScale.rangeBand()

-    barUpdate = bar
+    barExit = bar.exit()
+        .style "fill", "red"
+        .transition()
+        .delay 1000
+        .duration 1000
         .style
-            stroke: "gray"
-            fill: (d) -> colorScale(d)
-        .attr
-            x: barPadding
-            y: (d, i) -> yScale(i)
-            width: (d) -> xScale(d)
-            height: -> yScale.rangeBand()
-        .trasition
-
-    barExit = bar.exit()
+            color: "red"
+            "fill-opacity": 0
         .remove()

 $ ->
-    # start app
-    placeBars(data[0])
+    draw(data[0])

 $ document
     .on "change", "#group", ->
         console.log "changing. Data:"
         console.log (data[$(@).val()])
-        placeBars(data[$(@).val()])
+        draw(data[$(@).val()])


Add lb-services in assets

Add lb-services in assets

--- build/tasks/compile/lb-services.coffee
+++ build/tasks/compile/lb-services.coffee
@@ -9,4 +9,4 @@
   .pipe require('gulp-loopback-sdk-angular')
     apiUrl: "#{parameters.backend_route}"
   .pipe rename 'lb-services.js'
-  .pipe gulp.dest "#{parameters.web_path}/js"
+  .pipe gulp.dest "#{parameters.assets_path}/js"


remove home.coffee file for precompilation

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


Cleaned up js

Cleaned up js

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -1,15 +1,10 @@
 $ ->
   # Focus on click
-  $("#reps").on "click", ->
-    $(this).select()
-  $("#weight").on "click", ->
+  $(":input").on "click", ->
     $(this).select()
   # Change results on change
-  $("#reps").on "keyup", ->
+  $(":input").bind "keyup change", (e) ->
     CalculateScore()
-  $("#weight").on "keyup", ->
-    CalculateScore()
-  # The score
   CalculateScore = ->
     score = $("#weight").val()*($("#reps").val()-3.4)+$("#reps").val()*30 + 0.01*$("#weight").val()*$("#weight").val()
     score = Math.round(score)


swapped || order

swapped || order

--- app/assets/javascripts/user.coffee
+++ app/assets/javascripts/user.coffee
@@ -79,7 +79,7 @@
   HEADER_PARALLAX = ->
     header = $("#profile--header:not(.profile--no-header) img")[0]
     return unless header?
-    top = (document.body || document.documentElement).scrollTop * HEADER_PARALLAX_INERTIA
+    top = (document.documentElement || document.body).scrollTop * HEADER_PARALLAX_INERTIA
     header.style[PARALLAX_CSS] = "translate3d(0px, #{top}px, 0px)";

   $(window).on "scroll", (event) ->


Stateless component for WarningBanner Trying out the new syntax for stateless components in React 0.14.

--- app/classifier/warning-banner.cjsx
+++ app/classifier/warning-banner.cjsx
@@ -1,10 +1,7 @@
 React = require 'react'
 TriggeredModalForm = require 'modal-form/triggered'

-module.exports = React.createClass
-  displayName: 'WarningBanner'
-
-  render: ->
-    <TriggeredModalForm trigger={@props.label} triggerProps={className: 'warning-banner'}>
-      {@props.children}
+module.exports = (props) ->
+    <TriggeredModalForm trigger={props.label} triggerProps={className: 'warning-banner'}>
+      {props.children}
     </TriggeredModalForm>


Fixed #218 sherlock related invalid coupon code error

--- app/components/make-payment.coffee
+++ app/components/make-payment.coffee
@@ -80,7 +80,8 @@
           self.send "closeModal"
         , (error)->
           console.log error
-          Notify.error "Something went wrong when trying to process your card"
+          # Notify.error "Something went wrong when trying to process your card"
+          Notify.error error.jqXHR.responseJSON.message
       .catch @stripeErrorHandler

 `export default MakePaymentComponent`


A workflow isn't passed into the subject viewer for talk

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -51,10 +51,10 @@
         inFlipbookMode: allowFlipbook

   render: ->
-    rootClasses = classnames('subject-viewer', {
+    rootClasses = classnames('subject-viewer', {
       'default-root-style': @props.defaultStyle
-      'subject-viewer--flipbook': @state.inFlipbookMode
-      "subject-viewer--layout-#{@props.workflow.configuration?.multi_image_layout}": @props.workflow?.configuration?.multi_image_layout
+      'subject-viewer--flipbook': @state.inFlipbookMode
+      "subject-viewer--layout-#{@props.workflow?.configuration?.multi_image_layout}": @props.workflow?.configuration?.multi_image_layout
     })
     # Feature detect IE11 and apply flex prop. Revisit or remove when IE11 is no longer supported.
     rootStyle = flex: "1 1 auto" if "ActiveXObject" in window or window.ActiveXObject isnt undefined
@@ -64,7 +64,7 @@
       mainDisplay = @renderFrame @state.frame
     else
       mainDisplay = (@renderFrame frame, {key: "frame-#{frame}"} for frame of @props.subject.locations)
-
+

     tools = switch type
       when 'image'
@@ -128,7 +128,7 @@
       </div>
     </div>

-
+

   renderFrame: (frame, props = {}) ->
     <FrameViewer {...@props} {...props} frame={frame} />


added a todo

added a todo

--- app/helper/executableHelper.coffee
+++ app/helper/executableHelper.coffee
@@ -101,6 +101,8 @@
     collection.method = serviceInfo.service
     async.waterfall [
       (callback) ->
+        #TODO Add collection exist check to here
+
         if (req.body.images[0].type is 'collection')
           preprocessCollection(collection, req, serviceInfo, parameterHelper, callback)
         else if(req.body.images[0].type is 'iiif')


Rename coll to collection. Adds lots of TODO comments. Rename CAT_DATA_SCALES to CATEGORICAL_VALUES. Rename constructDCObject to createDCObject. Simplify the createDCObject arguments. Parenthesize CoffeeScript function arguments when used in an assign. Add multiple tumor warning. Rename dat to data. Rename data object variable from d to obj. Rename chart axes variable from charts to axes. Simplify ticks assignment. Name chart element with one-based id suffix rather than zero-based. Add table hyperlinks.

--- app/javascripts/correlation.coffee
+++ app/javascripts/correlation.coffee
@@ -14,7 +14,7 @@
         # properties:
         #
         # * label - Appears in the dropdown picklists and as chart axis labels.
-        # * coll - The collection(s) for which the data type is valid. May be
+        # * collection - The collection(s) for which the data type is valid. May be
         #   'all' or a list of specific collections.
         # * accessor - The data accessor.
         #
@@ -23,42 +23,43 @@
         # obtained and plotted on the charts.
         #
         # TODO - push the common config data into the respective services.
-        #   See the k-trans.coffee TODO item.
+        #   See the k-trans.coffee TODO item. See also the CATEGORICAL_VALUES
+        #   TODO below.
         #
         CHART_DATA_CONFIG =
           'fxlKTrans':
               label: 'FXL Ktrans'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (modelingResult) -> modelingResult.fxlKTrans.average
           'fxrKTrans':
               label: 'FXR Ktrans'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (modelingResult) -> modelingResult.fxrKTrans.average
           'deltaKTrans':
               label: 'delta Ktrans'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (modelingResult) -> modelingResult.deltaKTrans.average
           'vE':
               label: 'v_e'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (modelingResult) -> modelingResult.vE.average
           'tauI':
               label: 'tau_i'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (modelingResult) -> modelingResult.tauI.average
           'tumorLength':
               label: 'Tumor Length (mm)'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (tumor) ->
@@ -66,7 +67,7 @@
                 if tumor.extent.length? then tumor.extent.length else null
           'tumorWidth':
               label: 'Tumor Width (mm)'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (tumor) ->
@@ -74,7 +75,7 @@
                 if tumor.extent.width? then tumor.extent.width else null
           'tumorDepth':
               label: 'Tumor Depth (mm)'
-              coll: [
+              collection: [
                 'all'
               ]
               accessor: (tumor) ->
@@ -82,7 +83,7 @@
                 if tumor.extent.depth? then tumor.extent.depth else null
           'breastTNMStage':
               label: 'TNM Stage'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -91,7 +92,7 @@
                 stage.replace /^\d+/, roman.romanize
           'recurrenceScore':
               label: 'Recurrence Score'
-              coll: [
+              collection: [
                 'Breast'
               ]
               scale: null
@@ -100,7 +101,7 @@
                 Breast.recurrenceScore tumor.geneticExpression.normalizedAssay
           'ki67':
               label: 'Ki67 Expression'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -111,7 +112,7 @@
                   null
           'gstm1':
               label: 'GSTM1 Normalized Assay'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -122,7 +123,7 @@
                   null
           'cd68':
               label: 'CD68 Normalized Assay'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -133,7 +134,7 @@
                   null
           'bag1':
               label: 'BAG1 Normalized Assay'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -144,7 +145,7 @@
                   null
           'grb7':
               label: 'GRB7 Normalized Assay'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -155,7 +156,7 @@
                   null
           'her2':
               label: 'HER2 Normalized Assay'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -166,7 +167,7 @@
                   null
           'rcbIndex':
               label: 'RCB Index'
-              coll: [
+              collection: [
                 'Breast'
               ]
               accessor: (tumor) ->
@@ -175,7 +176,7 @@
                 rcb.index
           'sarcomaTNMStage':
               label: 'TNM Stage'
-              coll: [
+              collection: [
                 'Sarcoma'
               ]
               accessor: (tumor) ->
@@ -184,7 +185,7 @@
                 stage.replace /^\d+/, roman.romanize
           'necrosisPercent':
               label: 'Necrosis Percent'
-              coll: [
+              collection: [
                 'Sarcoma'
               ]
               accessor: (tumor) ->
@@ -208,10 +209,31 @@
         DATA_TYPES = _.keys CHART_DATA_CONFIG

         # The imaging data types.
-        IMAGING_DATA_TYPES = DATA_TYPES.slice 0, 5
-
-        # All possible values for categorical data types.
-        CAT_DATA_SCALES =
+        IMAGING_DATA_TYPES = DATA_TYPES.slice(0, 5)
+
+        # The categorical data type {data type: value extent array}
+        # associative look-up object.
+        #
+        # TODO - get this from the respective services:
+        #
+        #     breast: Breast.stageExtent().map(Roman.romanize)
+        #     sarcoma: Sarcoma.stageExtent().map(Roman.romanize)
+        #
+        #   Better still is to geet the stages on demand given the
+        #   current collection rather than list the known collections
+        #   here, e.g. make a service collection.coffee with:
+        #     factory 'Collection', ... ->
+        #       SERVICES = {breast: Breast, ...}
+        #       service: (key) -> SERVICES[key] or throw error
+        #       extent: (collection) -> service(key).stageExtent()
+        #   then replace use of CATEGORICAL_VALUES by:
+        #      Collection.extent(key)
+        #
+        #   The application tends to break if detailed domain knowledge
+        #   is redundantly dispersed throughout the source code. There
+        #   are other opportunities to consolidate domain knowledge
+        #   in services and delegate to those services in this file.
+        CATEGORICAL_VALUES =
           'breastTNMStage':
             [
              'IA'
@@ -244,11 +266,13 @@
           ticks: 4
           leftMargin: 6

-        # The default chart data types to be displayed, by collection. X axes
-        # may include any data types that are valid for the collection. Y axes
-        # may only include continuous data types (no categorical/ordinal data
-        # types).
-        DEFAULT_CHARTS:
+        # The default chart axes to be displayed, by collection. The X
+        # axes can include any data type that is valid for the collection.
+        # The Y axes can include only the continuous data types, i.e. no
+        # categorical/ordinal data types.
+        #
+        # TODO - where is the above statement enforced?
+        DEFAULT_AXES:
           'Breast':
             [
               {
@@ -294,17 +318,17 @@
         # from the Y axis choices because the DC charting does not currently
         # support such a configuration.
         #
-        # @param coll the collection
-        # @returns the valid X and Y axis data types and labels for the current
+        # @param collection the target collection
+        # @returns the valid X and Y axis data types and labels for the target
         #   collection
-        dataTypeChoices: (coll) ->
+        dataTypeChoices: (collection) ->
           xChoices = new Object
           yChoices = new Object
           for dt in DATA_TYPES
             config = CHART_DATA_CONFIG[dt]
-            if 'all' in config.coll or coll in config.coll
+            if 'all' in config.collection or collection in config.collection
               xChoices[dt] = LABELS[dt]
-              if dt not in Object.keys CAT_DATA_SCALES
+              if dt not in Object.keys(CATEGORICAL_VALUES)
                 yChoices[dt] = LABELS[dt]
           choices =
             x: xChoices
@@ -328,61 +352,108 @@
         # Each object needs to contain all of the same keys. If data
         # is not available for a data type, it must be assigned a null value.
         #
+        # TODO - The chart should collect whatever properties are available
+        #   rather than be constrained by choices. See the CHART_DATA_CONFIG
+        #   TODO above.
+        #
+        # TODO - Are there count(session) x max(1, count(tumors)) objects for
+        #   each subject?
+        #
         # @param charting the REST query result
         # @param choices the valid data types for the current collection
         # @returns the scatterplot data
         prepareScatterPlotData: (charting, choices) ->
-          # @param s the subject number
-          # @param v the visit number
-          # @param d the visit date
-          # @param modResult the modeling result
+          # @param modeling the modeling object
           # @param tumor the tumor pathology
           # @returns a complete scatterplot data object
-          constructDCObject = (s, v, d, modResult, tumor) ->
-            dateFormat = (date) ->
+          createDCObject = (modeling, tumor) ->
+            formatDate = (date) ->
+              # TODO - the date should already be a moment here and below.
               moment(date).format 'MM/DD/YYYY'
+
             # Create a new data object with core properties.
+            session = modeling.session
+            subject = session.subject
+            # TODO - the date should already be a moment here and above.
+            date = formatDate(DateHelper.asMoment(session.date))
+
+            # Make the session page hyperlink.
+            sbjRefUrl = "/quip/#{ subject.collection }/subject/#{ subject.number }"
+            sbjRef = "#{ sbjRefUrl }?project=#{ subject.project }"
+            sessRef = "#{ sbjRefUrl }/session/#{ session.number }?project=#{ subject.project }"
+
+            # Make the chart data object. The chart data object combines the
+            # subject, session and modeling objects.
+            #
+            # TODO - rename the visit variable to session here and elsewhere.
+            #   Visit has a web connotation that differs from the imaging connotation.
+            #   For clarity, 'visit' is confined to the presentation layer, i.e. it is
+            #   displayed to the user but is not used as a variable in the source code.
+            #   Thus, e.g., the test Page class has a function named 'visit' that
+            #   follows a link. It would be confusing to 'visit the visit page'.
+            #
+            # TODO - similarly, rename patient variables and element ids/classes to
+            #   'subject'.
+            #
             dcObject =
-              'subject': s
-              'visit': v + 1
-              'date': dateFormat DateHelper.asMoment(d)
+              subject: _.extend({href: sbjRef}, subject)
+              visit: _.extend({href: sessRef}, session)
+
             # Iterate over the valid data types and add data to the object.
             for key of choices
               config = CHART_DATA_CONFIG[key]
               if key in IMAGING_DATA_TYPES
-                dcObject[key] = config.accessor modResult
+                dcObject[key] = config.accessor(modeling.result)
               else if tumor?
-                dcObject[key] = config.accessor tumor
+                dcObject[key] = config.accessor(tumor)
               else
                 dcObject[key] = null
+
             # Return the data object.
             dcObject

           # Initialize the data object array.
           data = new Array
           # Iterate over the subjects.
-          for subj in charting
+          for sbj in charting
             # Obtain the tumor pathology data from the Surgery encounter.
+            # tumors will be null iff there is no pathology report.
+            # A path report is required for a biopsy, optional for a
+            # surgery. However, the biopsy tumors could be an empty
+            # array, althought that should be an error.
+            #
+            # For now, we allow for at most one path report and raise an error
+            # otherwise.
+            #
+            # TODO - Support both a biopsy and a surgery path report.
+            #   Then rework the tumors loop below.
+            #
+            # TODO - Support no path reports.
+            #
             tumors = null
-            for enc in subj.encounters
-              if _.endsWith(enc._cls, 'Surgery') and enc.pathology.tumors?
-                tumors = enc.pathology.tumors
-            # Iterate over the subject encounters. If an encounter is a
-            # modeling session, construct a data object with the modeling and,
-            # if available, tumor pathology data.
-            for enc, i in subj.encounters
-              if enc._cls == 'Session'
-                for mod in enc.modelings
-                  if not tumors
-                    dcObject = constructDCObject(
-                      subj.number, i, enc.date, mod.result, null
-                    )
-                  else
-                    for tumor in tumors
-                      dcObject = constructDCObject(
-                        subj.number, i, enc.date, mod.result, tumor
-                      )
-                  data.push dcObject
+            for enc in sbj.clinicalEncounters
+              if enc.pathology.tumors?
+                # More than one path report is not yet supported.
+                if tumors?
+                  console.warn("Only one pathology report per subject is" +
+                               " supported: #{ sbj.collection } Subject" +
+                               " #{ sbj.number }")
+                if enc.isSurgery()
+                  tumors = enc.pathology.tumors
+            # Make a chart data object for each tumor of each session. The
+            # data object consists of the modeling result properties and,
+            # if available, the tumor pathology data.
+            for session in sbj.sessions
+              for mdl in session.modelings
+                if tumors
+                  # TODO - how are tumors distinguished? Should we aggregate
+                  #   the size across tumors? What about other clinical values?
+                  #   Current handling of more than one tumor is probably a bug.
+                  for tumor in tumors
+                    dcObject = createDCObject(mdl, tumor)
+                else
+                  dcObject = createDCObject(mdl,  null)
+                data.push dcObject
           # Return the scatterplot data.
           data

@@ -390,33 +461,50 @@
         # the DC charts, the padding must be expressed in the same unit domains
         # as the data being charted.
         #
-        # @param dat the scatterplot data
+        # @param data the scatterplot data
         # @param choices the valid data types for the current collection
         # @returns the chart padding for each data type
-        calculatePadding: (dat, choices) ->
+        calculatePadding: (data, choices) ->
           padding = new Object
           # Iterate over the data types.
+          #
+          # TODO - What does 'data type' mean here?
+          #
+          # FIXME - this breaks if data is empty. Present an alert
+          #   in that case. Can this occur in practice?
+          #
+          # TODO - rename function to chartPadding. Then, make a function to
+          #   get the item padding and collect the iteration result into an
+          #   object using the functional programming reduce idiom, e.g.:
+          #     chartPadding: (data, choices) ->
+          #       dataTypePadding = (key) -> ...
+          #       addDataTypePadding = (obj, key) ->
+          #         obj[key] = dataTypePadding(key)
+          #         obj
+          #     # Return the {data type: padding} object
+          #     choices.reduce(addDataTypePadding, key, {})
           for key of choices
-            max = _.maxBy(dat, (o) -> o[key])[key]
-            min = _.minBy(dat, (o) -> o[key])[key]
+            values = _.map(data, key)
+            max = _.max(values)
+            min = _.min(values)
             diff = max - min
-            # If the values are not all the same, calculate a padding value
-            # based the chart layout parameter setting, e.g. a setting of .2
-            # will give the chart 20% padding.
-            #
-            # If the values are all the same, calculate a padding value of an
-            # appropriate resolution for that value. The initial result value
-            # reflects the number of digits or decimal places of the
-            # scatterplot values. Each chart tick mark above and below that
-            # value is then set to 10 to the power of the result reduced by 1.
-            if diff != 0
-              pad = diff * CHART_LAYOUT_PARAMS.corrChartPadding
-            else
+            # The padding is determined as follows:
+            # * If the values are all the same, then calculate a padding value of
+            #   an appropriate resolution for that value. The initial result value
+            #   reflects the number of digits or decimal places of the
+            #   scatterplot values. Each chart tick mark above and below that
+            #   value is then set to 10 to the power of the result reduced by 1.
+            # * Otherwise, calculate a padding value based the chart layout
+            #   parameter setting, e.g. a setting of .2 will give the chart 20%
+            #   padding.
+            if diff == 0
               max = Math.abs max
               result = Math.ceil(Math.log(max) / Math.log(10))
               if Math.abs(result) is Infinity then result = 0
               pad = CHART_LAYOUT_PARAMS.ticks / 2 * Math.pow(10, result - 1)
+            else
               # Add the padding for the data type to the object.
+              pad = diff * CHART_LAYOUT_PARAMS.corrChartPadding
             padding[key] = pad
           # Return the padding object.
           padding
@@ -425,43 +513,55 @@
         #
         # @param config the chart configuration
         renderCharts: (config) ->
+          # TODO - refactor this function body into app. 3 smaller functions.
+          #
           # Enables a specified D3 element to be moved to the front layer of
           # the visualization, which is necessary if that element is to be
           # bound to mouseover events (e.g. tooltips).
           d3.selection::moveToFront = ->
             @each ->
               @parentNode.appendChild this
+              # TODO - Why this return?
               return
-
-          dat = config.data
+
+          # Convenience temp config variables.
+          data = config.data
           padding = config.padding
-          chartsToDisplay = config.charts
+          axes = config.axes

           # Set up the crossfilter.
-          ndx = crossfilter dat
+          # TODO - what is ndx? Rename and describe.
+          #   'ndx' conventionally signifies an integer index.
+          #   That is not what ndx is here.
+          ndx = crossfilter(data)

           # The patient/visit dimension.
-          dim = ndx.dimension (d) ->
+          dim = ndx.dimension (obj) ->
             [
-              d.subject
-              d.visit
+              obj.subject.number
+              obj.visit.number
             ]

           # The patient/visit group.
           group = dim.group()

           # The patient/visit table configuration.
-          #
-          # TODO - Construct the hyperlinks.
           table = dc.dataTable '#qi-patient-table'
           table.dimension dim
-            .group (d) ->
-              '<a ui-sref="">Patient ' + d.subject + '</a>'
-            .sortBy (d) -> d.visit
+            .group (obj) -> "<a href=\"#{ obj.subject.href }\">Patient #{ obj.subject.number }</a>"
+            .sortBy (obj) -> obj.visit.href
             .columns [
-              (d) ->
-                '• <a ui-sref="">Visit ' + d.visit + '</a> <span class="qi-dc-date">' + d.date + '</span>'
+              # TODO - Should this be two columns per row?
+              (obj) ->
+                refElt = "• <a href=\"#{ obj.visit.href }\">Visit #{ obj.visit.number }</a>"
+                dateElt = "<span class='qi-dc-date'>#{ obj.visit.date.format('MM/DD/YYYY') }</span>"
+                "#{ refElt } #{ dateElt }"
             ]
+
+          # The largest subject number is the number of X tick marks.
+          maxSbjNbr = _.chain(data).map('subject.number').max().value()
+          # The largest session number is the number of Y tick marks.
+          maxSessNbr = _.chain(data).map('visit.number').max().value()

           # The patient/visit chart configuration.
           chart = dc.scatterPlot '#qi-patient-chart'
@@ -472,40 +572,60 @@
             .renderHorizontalGridLines true
             .symbolSize CHART_LAYOUT_PARAMS.symbolSize
             .symbol CHART_LAYOUT_PARAMS.patientChartSymbol
+            # TODO - What is category 10?
             .colors d3.scale.category10()
+            # TODO - What is d here? Why key 1?
             .colorAccessor (d) -> d.key[1]
             .xAxisLabel "Patient"
             .yAxisLabel "Visit"
+            .elasticX true
             .elasticY true
-            .elasticX true
             .x d3.scale.linear()
             .y d3.scale.linear()
             .xAxisPadding CHART_LAYOUT_PARAMS.patientChartPadding
             .yAxisPadding CHART_LAYOUT_PARAMS.patientChartPadding
-          chart.xAxis().ticks _.maxBy(dat, (o) -> o.subject).subject
-          chart.yAxis().ticks _.maxBy(dat, (o) -> o.visit).visit
+          # Set the axis tick counts.
+          chart.xAxis().ticks(maxSbjNbr)
+          chart.yAxis().ticks(maxSessNbr)
+          # Pad the left margins.
           chart.margins().left += CHART_LAYOUT_PARAMS.leftMargin

           # Supply the tooltips.
           chart.on 'renderlet', (chart) ->
             chart.selectAll '.symbol'
               .on 'mouseover', (d) ->
-                div.transition().duration(20).style 'opacity', .9
+                # TODO - What is div here?
+                # TODO - Remove the returns below if they are extraneous.
+                div.transition().duration(20).style('opacity', .9)
+                # TODO - make temp variables and interpolate the argument,
+                #   e.g.:
+                #     label = "<strong>Patient:</strong>#{ d.key[0] }"
+                #     ...
+                #     html = "#{ label }..."
+                #     div.html(html)
                 div.html('<strong>Patient:</strong> ' + d.key[0] + '<br/><strong>Visit:</strong> ' + d.key[1]).style('left', d3.event.pageX + 5 + 'px').style 'top', d3.event.pageY - 35 + 'px'
                 return
               .on 'mouseout', (d) ->
-                div.transition().duration(50).style 'opacity', 0
+                div.transition().duration(50).style('opacity', 0)
                 return

+          # The axes determine the number of charts to display.
+          chartCnt = axes.length
+          # The charts are numbered from 1 to n, where n is the number of
+          # axis pairs defined in the chart config axes variable.
+          chartNbrs = _.range(1, chartCnt + 1)
           # Set up the scatterplots.
-          charts = (dc.scatterPlot('#qi-correlation-chart-' + i) for i in [0...4])
+          charts = (dc.scatterPlot('#qi-correlation-chart-' + chartNbr) for chartNbr in chartNbrs)

           # Iterate over the charts.
-          for chart, index in charts
-            xAxis = chartsToDisplay[index].x
-            yAxis = chartsToDisplay[index].y
+          for chart, i in charts
+            # TODO - make this for body a function.
+            xAxis = axes[i].x
+            yAxis = axes[i].y

             # Set up the dimension based on the X and Y axis user selections.
+            # TODO - is d here the chart data object? If so, then comment and
+            # rename to obj here and below.
             dim = ndx.dimension (d) ->
               [
                 d[xAxis]
@@ -532,9 +652,9 @@
             chart.margins().left += CHART_LAYOUT_PARAMS.leftMargin

             # Special configuration for X-axis categorical data.
-            if xAxis in Object.keys CAT_DATA_SCALES
+            if xAxis in Object.keys(CATEGORICAL_VALUES)
               chart.elasticX false
-                .x d3.scale.ordinal().domain(CAT_DATA_SCALES[xAxis])
+                .x d3.scale.ordinal().domain(CATEGORICAL_VALUES[xAxis])
                 .xUnits dc.units.ordinal
                 ._rangeBandPadding 1
             else
@@ -554,10 +674,7 @@
                   div.transition().duration(50).style 'opacity', 0
                   return
                 .style 'opacity', (d) ->
-                  if d.key[0]? && d.key[1]?
-                    1
-                  else
-                    0
+                  if d.key[0]? and d.key[1]? then 1 else 0

           # Render the charts.
           dc.renderAll()


Fix ignored error messages

Fix ignored error messages

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -802,7 +802,7 @@
 									return
 				error:			(jqXHR, textStatus, errorThrown) =>
 									log arguments
-									@onScheduleError data, textStatus, errorThrown
+									@onScheduleError textStatus, data, errorThrown
 									return
 				complete:		(jqXHR, textStatus) ->
 									resetIcon?() unless data.archive
@@ -820,14 +820,17 @@

 		onScheduleSuccess: (data) =>
 			log 'Scheduling success', data
-			chrome.extension.sendMessage
-				action: 	'notification'
-				icon: 		"images/tie48x48.png"
-				title: 		__msg 'notificationScheduleSuccessTitle'
-				message: 	__msg 'notificationScheduleSuccess'
+			if data.success
+				chrome.extension.sendMessage
+					action: 	'notification'
+					icon: 		"images/tie48x48.png"
+					title: 		__msg 'notificationScheduleSuccessTitle'
+					message: 	__msg 'notificationScheduleSuccess'
+			else
+				@onScheduleError data.error, null, data.error
 			return

-		onScheduleError: (params, status, error) =>
+		onScheduleError: (status, params, error) =>
 			log 'There was an error', arguments
 			if status is 'parsererror'
 				params.action = 'setup'


Check for user id on feed redirect

Check for user id on feed redirect

--- apps/feed/routes.coffee
+++ apps/feed/routes.coffee
@@ -3,7 +3,7 @@
 ExploreBlocks = require '../../collections/explore_blocks'

 @index = (req, res, next) ->
-  return next() unless req.user
+  return next() unless req.user?.id

   res.locals.sd.CURRENT_PATH = "/"
   res.locals.sd.FEED_TYPE = 'primary'


os detector fix

os detector fix

--- app/scripts/views/main_view.coffee
+++ app/scripts/views/main_view.coffee
@@ -126,7 +126,7 @@

   _detectBrowser: ->
     if $.browser.mozilla
-      if @_osDetection() is 'MacOS'
+      if @_osDetection() isnt 'MacOS'
         @ui.onlyBody.addClass('mozilla')

   _osDetection: ->


Continued development, not yet working.

Continued development, not yet working.

--- src/lib/strata.coffee
+++ src/lib/strata.coffee
@@ -1,14 +1,32 @@
 class Mutation
-  constructor: (root, initial, subsequent, swap, penultimate) ->
+  constructor: (@strata, root, initial, subsequent, swap, penultimate) ->
     @levels = []
     @decisions = { root, initial, subsequent, swap, penultimate }

 class Level
-  constructor: (@exclusive) ->
+  constructor: (exclusive) ->
+    @lock = if exclusive then @lockExclusive else @lockShared
+  lockExclusive: (mutation, tier, callback) ->
+    strata = mutation.strata
+    locks = strata.locks
+    if locks.exclusive[tier.id] or locks.shared[tier.id]
+      @exclusiveQueue.push [ mutation, tier, callback ]
+    else
+      strata.locks.exclusive[tier.id]++
+      callback()
+  lockShared: (mutation, tier, callback) ->
+    strata = mutation.strata
+    if locks.exclusive[tier.id]
+      @sharedQueue.push [ mutation, tier, callback ]
+    else
+      strata.locks.shared[tier.id]++
+      callback()

-generalized = (mutation, decisions) ->
-  mutation.levels.push new Level(false)
-  root = @io.root @rootAddress
+class module.exports.MemoryIO
+  root: (map) -> map
+  fields: (object) -> object
+  compare: (left, right) ->
+    if left < right then -1 else if left > right then 1 else 0

 class module.exports.Strata
   constructor: (options) ->
@@ -16,18 +34,81 @@
     @branchSize = options.branchSize or 12
     throw new Error("I/O Strategy is required.") unless @io = options.io
     throw new Error("Root address is required.") unless @rootAddress = options.rootAddress
+    @locks = { shared: {}, exclusive: {} }

   add: (object, callback) ->
     fields = @io.fields object
-    mutation = { fields, object }
-    @_mutation new Mutation(shouldSplitRoot)
+    @_mutation new Mutation(this, @_shouldSplitRoot)

-  @_mutation: (mutation, decisions) ->
+  generalized: (mutation) ->
+    mutation.levels.push new Level(false)
+    mutation.parent = @io.root @rootAddress
+    mutation.parentLevel = new Level(false)
+    mutation.levels.push mutation.parentLevel
+    parentLevel.lock mutation, @_initialTest
+
+  _initialTest: (mutation) ->
+    mutation.childLevel = new Level(false)
+    mutation.levels.push mutation.childLevel
+    if mutation.initial.call this, mutation
+      mutation.parentLevel.operations.clear()
+      mutation.parentLevel.upgrade mutation.childLevel, @_initialTestAgain
+
+  _initialTestAgain: (mutation) ->
+    parentLevel = mutation.levels[0]
+    parent = @io.root @rootAddress
+    if not mutation.initial.call this, mutation
+      mutation.rewind 0
+    @_tierDecision mutation
+
+  _tierDecision: (mutation) ->
+    if mutation.parent.penultimate
+      @_testInnerTier mutation, mutation.penultimate, 1, @_operate
+    else
+      @_testInnerTier mutation, mutation.subsequent, 0, @_tierDescend
+
+  _tierDescend: (mutation) ->
+    branch = @_find(mutation.parent, mutation.sought)
+    @io.load mutation.parent, branch, (child) =>
+      mutation.parent = child
+      mutation.parentLevel = mutation.childLevel
+      mutation.childLevel = new Level(mutation.parentLevel.exclusive)
+      mutation.levels.push mutation.childLevel
+      mutation.shift @_tierDescend
+
+  _find: (tier, sought) ->
+
+  _testInnerTier: (mutation, decision, leaveExclusive, next) ->
+    tiers = decision.test.call mutation
+    keys = mutation.swap.test.call mutation
+    if tiers or keys
+      if not (mutation.parentLevel.exclusive and mutation.childLevel.exclusive)
+        mutation.parentLevel.upgrade mutation.childLevel, =>
+          mutation.parentLevel.operations.clear()
+          mutation.childLevel.operations.clear()
+          @_testInnerTier mutation, leaveExclusive, next
+      else if not tiers
+        mutation.rewind leaveExclusive
+        next.call mutation.strata, mutation
+      else
+        next.call mutation.strata, mutation
+    else
+      mutation.rewind leaveExclusive
+      next.call mutation.strata, mutation
+
+  _shouldSplitRoot: (mutation)
+    if @branchSize is mutation.parent.length
+      mutation.parentLevel.operations.add(@_splitRoot)
+      true
+    else
+      false
+
+  _mutation: (mutation) ->
     @_queue.push mutation
     @_queue.push []
     @_dequeue()

-  @_dequeue: ->
+  _dequeue: ->
     if queue.length % 2 is 1
       while queue[0].length isnt 0
         reader = queue[0].shift()


cleanup and don't fire did-move-to-item-area within movement in item-area

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -286,24 +286,22 @@
         (not cursor.selection.isEmpty()) or
         (oldBufferPosition.row is newBufferPosition.row)

-      if newBufferPosition.row is 0
+      newRow = newBufferPosition.row
+      oldRow = oldBufferPosition.row
+
+      if newRow is 0 # was at Item area
         @withLock => @moveToPrompt()
         return

-      if newBufferPosition.row > oldBufferPosition.row
-        direction = 'next'
-      else
-        direction = 'previous'
-      {row, column} = newBufferPosition
-
+      direction = if newRow > oldRow then 'next' else 'previous'
       @withLock =>
-        row = @findNormalItem(row, direction)
+        row = @findNormalItem(newRow, direction)
         if row? # row might be '0'
           @selectItemForRow(row)
-          cursor.setBufferPosition([row, column])
-          @emitDidMoveToItemArea()
-        else if direction is 'previous'
-          @moveToPrompt()
+          cursor.setBufferPosition([row, newBufferPosition.column])
+          @emitDidMoveToItemArea() if oldRow is 0 # was at prompt
+        else
+          @moveToPrompt() if direction is 'previous'

       @preview() if @isAutoPreview()


Get component via ComponentLoader

Get component via ComponentLoader

--- spec/ControlMirobot.coffee
+++ spec/ControlMirobot.coffee
@@ -1,20 +1,29 @@
 noflo = require 'noflo'
 unless noflo.isBrowser()
-  chai = require 'chai' unless chai
-  ControlMirobot = require '../components/ControlMirobot.coffee'
+  chai = require 'chai'
+  path = require 'path'
+  baseDir = path.resolve __dirname, '../'
 else
-  ControlMirobot = require 'noflo-mirobot/components/ControlMirobot.js'
+  baseDir = 'noflo-mirobot'

 describe 'ControlMirobot component', ->
   c = null
   ins = null
   out = null
+  before (done) ->
+    @timeout 4000
+    loader = new noflo.ComponentLoader baseDir
+    loader.load 'mirobot/ControlMirobot', (err, instance) ->
+      return done err if err
+      c = instance
+      ins = noflo.internalSocket.createSocket()
+      c.inPorts.lib.attach ins
+      done()
   beforeEach ->
-    c = ControlMirobot.getComponent()
-    ins = noflo.internalSocket.createSocket()
     out = noflo.internalSocket.createSocket()
-    c.inPorts.lib.attach ins
     c.outPorts.path.attach out
+  afterEach ->
+    c.outPorts.path.detach out

   describe 'when instantiated', ->
     it 'should have an lib port', ->


accelerometer cordova example test

accelerometer cordova example test

--- spec/Generator.spec.coffee
+++ spec/Generator.spec.coffee
@@ -17,6 +17,7 @@
   afterEach ->
     @testHelper.cleanUp()

+
   describe 'tutorials', ->

     beforeEach ->
@@ -89,6 +90,7 @@

         expect(fs.readFileSync(controllersStartHTMLPath).toString()).toMatch(/Good! You made it all/)

+
   describe 'examples', ->

     beforeEach ->
@@ -111,54 +113,66 @@
         expect( cmd.stdout ).toMatch(/Error: No such example neverGonnaExistExample, see 'steroids generate' for help./)

-    it "generates animation example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "animation"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/animationExample.html")
-
-    it "generates drawer example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "drawer"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/drawerExample.html")
-
-    it "generates layerStack example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "layerStack"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/layerStackExample.html")
-
-    it "generates drawer example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "drawer"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/drawerExample.html")
-
-    it "generates modal example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "modal"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/modalExample.html")
-
-    it "generates navigationBar example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "navigationBar"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/navigationBarExample.html")
-
-    it "generates preload example", ->
-      cmd = @testHelper.runInProjectSync "generate",
-        args: ["example", "preload"]
-
-      runs ->
-        expect( cmd.stdout ).toMatch("app/layouts/preloadExample.html")
+    describe "cordova examples", ->
+
+      it "generates accelerometer example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "accelerometer"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("www/accelerometerExample.html")
+
+
+    describe "steroids examples", ->
+
+      it "generates animation example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "animation"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/animationExample.html")
+
+      it "generates drawer example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "drawer"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/drawerExample.html")
+
+      it "generates layerStack example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "layerStack"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/layerStackExample.html")
+
+      it "generates drawer example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "drawer"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/drawerExample.html")
+
+      it "generates modal example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "modal"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/modalExample.html")
+
+      it "generates navigationBar example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "navigationBar"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/navigationBarExample.html")
+
+      it "generates preload example", ->
+        cmd = @testHelper.runInProjectSync "generate",
+          args: ["example", "preload"]
+
+        runs ->
+          expect( cmd.stdout ).toMatch("app/layouts/preloadExample.html")

   describe 'usage', ->


Polyomino example cleanup, use svgtiler.static

--- examples/anim/shapes.coffee
+++ examples/anim/shapes.coffee
@@ -25,7 +25,8 @@

 (key) ->
   [shape, classes...] = key.split /\s+/
-  attrs = class: classes.join ' '
-  # SVG parser complains about empty class attribute
-  delete attrs.class unless attrs.class
-  shapes[shape] attrs
+  attrs = class: classes.join(' ') or undefined
+    # `or undefined` needed or SVG parser complains about empty class attribute
+  ## `svgtiler.static` wrapper indicates that symbol depends only on key,
+  ## not any other context in the drawing.
+  svgtiler.static shapes[shape] attrs


Add failing spec

Add failing spec

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -877,6 +877,12 @@
       runs ->
         findView.findEditor.setText('items')
         findView.replaceEditor.setText('cats')
+
+    describe "when the find string is empty", ->
+      it "beeps", ->
+        findView.findEditor.setText('')
+        atom.commands.dispatch(findView.replaceEditor.element, 'core:confirm')
+        expect(atom.beep).toHaveBeenCalled()

     describe "when the replacement string contains an escaped char", ->
       describe "when the regex option is chosen", ->


Blur editor before testing that it gets focused

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -356,6 +356,8 @@
       atom.workspaceView.attachToDom()

     it "selects the file and opens it in the active editor on the first click, then changes focus to the active editor on the second", ->
+      runs -> treeView.focus()
+
       waitsForFileToOpen ->
         sampleJs.trigger clickEvent(originalEvent: { detail: 1 })