diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json
index 8baf56684a..c384817ff6 100644
--- a/interface/resources/controllers/keyboardMouse.json
+++ b/interface/resources/controllers/keyboardMouse.json
@@ -81,7 +81,11 @@
 
         { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
           "when": "Keyboard.RightMouseButton",
-          "to": "Actions.Yaw"
+          "to": "Actions.Yaw",
+          "filters":
+            [
+                { "type": "scale", "scale": 0.6 }
+            ]
         },
 
         { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
@@ -102,8 +106,19 @@
         { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
         { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
 
-        { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP" },
-        { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN" },
+        { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP",
+          "filters":
+            [
+                { "type": "scale", "scale": 0.6 }
+            ]
+
+        },
+        { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN",
+          "filters":
+            [
+                { "type": "scale", "scale": 0.6 }
+            ]
+        },
 
         { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" },
         { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" },
diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml
index e4a20a0316..e94325f399 100644
--- a/interface/resources/qml/hifi/tablet/EditTabView.qml
+++ b/interface/resources/qml/hifi/tablet/EditTabView.qml
@@ -21,7 +21,7 @@ TabView {
         enabled: true
         property string originalUrl: ""
 
-        Rectangle { 
+        Rectangle {
             color: "#404040"
 
             Text {
@@ -180,7 +180,7 @@ TabView {
 
         WebView {
             id: entityListToolWebView
-            url: "../../../../../scripts/system/html/entityList.html"
+            url: Paths.defaultScripts + "/system/html/entityList.html"
             anchors.fill: parent
             enabled: true
         }
@@ -194,7 +194,7 @@ TabView {
 
         WebView {
             id: entityPropertiesWebView
-            url: "../../../../../scripts/system/html/entityProperties.html"
+            url: Paths.defaultScripts + "/system/html/entityProperties.html"
             anchors.fill: parent
             enabled: true
         }
@@ -208,7 +208,7 @@ TabView {
 
         WebView {
             id: gridControlsWebView
-            url: "../../../../../scripts/system/html/gridControls.html"
+            url: Paths.defaultScripts + "/system/html/gridControls.html"
             anchors.fill: parent
             enabled: true
         }
@@ -222,7 +222,7 @@ TabView {
 
         WebView {
             id: particleExplorerWebView
-            url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
+            url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
             anchors.fill: parent
             enabled: true
         }
@@ -293,16 +293,16 @@ TabView {
                     break;
                 case 'list':
                     editTabView.currentIndex = 1;
-                    break; 
+                    break;
                 case 'properties':
                     editTabView.currentIndex = 2;
-                    break; 
+                    break;
                 case 'grid':
                     editTabView.currentIndex = 3;
-                    break; 
+                    break;
                 case 'particle':
                     editTabView.currentIndex = 4;
-                    break; 
+                    break;
                 default:
                     console.warn('Attempt to switch to invalid tab:', id);
             }
@@ -310,4 +310,4 @@ TabView {
             console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index f9a4d491c8..b32ef4024e 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -69,8 +69,8 @@ const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // action motor gets add
 const float MIN_AVATAR_SPEED = 0.05f;
 const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this
 
-const float YAW_SPEED_DEFAULT = 120.0f;   // degrees/sec
-const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec
+const float YAW_SPEED_DEFAULT = 100.0f;   // degrees/sec
+const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec
 
 // TODO: normalize avatar speed for standard avatar size, then scale all motion logic
 // to properly follow avatar size.
diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp
index a284e38dac..d02f4d8fcf 100644
--- a/interface/src/scripting/AudioDevices.cpp
+++ b/interface/src/scripting/AudioDevices.cpp
@@ -36,6 +36,21 @@ Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
     }
 }
 
+static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
+    QString deviceName;
+    auto& setting = getSetting(hmd, mode);
+    if (setting.isSet()) {
+        deviceName = setting.get();
+    } else if (hmd) {
+        if (mode == QAudio::AudioInput) {
+            deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
+        } else { // if (_mode == QAudio::AudioOutput)
+            deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
+        }
+    }
+    return deviceName;
+}
+
 QHash<int, QByteArray> AudioDeviceList::_roles {
     { Qt::DisplayRole, "display" },
     { Qt::CheckStateRole, "selected" },
@@ -59,10 +74,15 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
     }
 }
 
-
-void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) {
+void AudioDeviceList::resetDevice(bool contextIsHMD) {
     auto client = DependencyManager::get<AudioClient>().data();
-    auto deviceName = getSetting(contextIsHMD, _mode).get();
+    QString deviceName = getTargetDevice(contextIsHMD, _mode);
+    // FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not
+    // We need to have the AudioClient emit signals on switch success / failure
+    QMetaObject::invokeMethod(client, "switchAudioDevice", 
+        Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName));
+
+#if 0
     bool switchResult = false;
     QMetaObject::invokeMethod(client, "switchAudioDevice", Qt::BlockingQueuedConnection,
         Q_RETURN_ARG(bool, switchResult),
@@ -85,6 +105,7 @@ void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) {
             QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode));
         }
     }
+#endif
 }
 
 void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) {
@@ -137,11 +158,8 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
 }
 
 void AudioDevices::onContextChanged(const QString& context) {
-    auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get();
-    auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get();
-
-    _inputs.resetDevice(_contextIsHMD, input);
-    _outputs.resetDevice(_contextIsHMD, output);
+    _inputs.resetDevice(_contextIsHMD);
+    _outputs.resetDevice(_contextIsHMD);
 }
 
 void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
@@ -182,8 +200,16 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& d
 
 void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) {
     if (mode == QAudio::AudioInput) {
+        if (_requestedInputDevice == device) {
+            onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice);
+            _requestedInputDevice = QAudioDeviceInfo();
+        }
         _inputs.onDeviceChanged(device);
     } else { // if (mode == QAudio::AudioOutput)
+        if (_requestedOutputDevice == device) {
+            onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice);
+            _requestedOutputDevice = QAudioDeviceInfo();
+        }
         _outputs.onDeviceChanged(device);
     }
 }
@@ -201,28 +227,16 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceI
 
 void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device) {
     auto client = DependencyManager::get<AudioClient>();
-    bool success = false;
+    _requestedInputDevice = device;
     QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
-        Qt::BlockingQueuedConnection,
-        Q_RETURN_ARG(bool, success),
         Q_ARG(QAudio::Mode, QAudio::AudioInput),
         Q_ARG(const QAudioDeviceInfo&, device));
-
-    if (success) {
-        onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice);
-    }
 }
 
 void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device) {
     auto client = DependencyManager::get<AudioClient>();
-    bool success = false;
+    _requestedOutputDevice = device;
     QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
-        Qt::BlockingQueuedConnection,
-        Q_RETURN_ARG(bool, success),
         Q_ARG(QAudio::Mode, QAudio::AudioOutput),
         Q_ARG(const QAudioDeviceInfo&, device));
-
-    if (success) {
-        onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice);
-    }
 }
diff --git a/interface/src/scripting/AudioDevices.h b/interface/src/scripting/AudioDevices.h
index a17c577535..3278a53374 100644
--- a/interface/src/scripting/AudioDevices.h
+++ b/interface/src/scripting/AudioDevices.h
@@ -39,7 +39,7 @@ public:
     QVariant data(const QModelIndex& index, int role) const override;
 
     // reset device to the last selected device in this context, or the default
-    void resetDevice(bool contextIsHMD, const QString& device);
+    void resetDevice(bool contextIsHMD);
 
 signals:
     void deviceChanged(const QAudioDeviceInfo& device);
@@ -87,8 +87,10 @@ private:
 
     AudioDeviceList _inputs { QAudio::AudioInput };
     AudioDeviceList _outputs { QAudio::AudioOutput };
+    QAudioDeviceInfo _requestedOutputDevice;
+    QAudioDeviceInfo _requestedInputDevice;
 
-    bool& _contextIsHMD;
+    const bool& _contextIsHMD;
 };
 
 };
diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp
index 443f11fb23..a62fb2270b 100644
--- a/interface/src/ui/JSConsole.cpp
+++ b/interface/src/ui/JSConsole.cpp
@@ -24,13 +24,14 @@
 #include "ScriptHighlighting.h"
 
 const int NO_CURRENT_HISTORY_COMMAND = -1;
-const int MAX_HISTORY_SIZE = 64;
+const int MAX_HISTORY_SIZE = 256;
+const QString HISTORY_FILENAME = "JSConsole.history.json";
 
 const QString COMMAND_STYLE = "color: #266a9b;";
 
 const QString RESULT_SUCCESS_STYLE = "color: #677373;";
 const QString RESULT_INFO_STYLE = "color: #223bd1;";
-const QString RESULT_WARNING_STYLE = "color: #d13b22;";
+const QString RESULT_WARNING_STYLE = "color: #999922;";
 const QString RESULT_ERROR_STYLE = "color: #d13b22;";
 
 const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>";
@@ -38,14 +39,35 @@ const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
 
 const QString JSConsole::_consoleFileName { "about:console" };
 
+const QString JSON_KEY = "entries";
+QList<QString> _readLines(const QString& filename) {
+    QFile file(filename);
+    file.open(QFile::ReadOnly);
+    auto json = QTextStream(&file).readAll().toUtf8();
+    auto root = QJsonDocument::fromJson(json).object();
+    // TODO: check root["version"]
+    return root[JSON_KEY].toVariant().toStringList();
+}
+
+void _writeLines(const QString& filename, const QList<QString>& lines) {
+    QFile file(filename);
+    file.open(QFile::WriteOnly);
+    auto root = QJsonObject();
+    root["version"] = 1.0;
+    root["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
+    root[JSON_KEY] = QJsonArray::fromStringList(lines);
+    auto json = QJsonDocument(root).toJson();
+    QTextStream(&file) << json;
+}
+
 JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
     QWidget(parent),
     _ui(new Ui::Console),
     _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
-    _commandHistory(),
+    _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME),
+    _commandHistory(_readLines(_savedHistoryFilename)),
     _ownScriptEngine(scriptEngine == NULL),
     _scriptEngine(NULL) {
-
     _ui->setupUi(this);
     _ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
     _ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap);
@@ -101,9 +123,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
 }
 
 void JSConsole::executeCommand(const QString& command) {
-    _commandHistory.prepend(command);
-    if (_commandHistory.length() > MAX_HISTORY_SIZE) {
-        _commandHistory.removeLast();
+    if (_commandHistory.isEmpty() || _commandHistory.constFirst() != command) {
+        _commandHistory.prepend(command);
+        if (_commandHistory.length() > MAX_HISTORY_SIZE) {
+            _commandHistory.removeLast();
+        }
+        _writeLines(_savedHistoryFilename, _commandHistory);
     }
 
     _ui->promptTextEdit->setDisabled(true);
@@ -182,7 +207,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) {
                     // a new QTextBlock isn't created.
                     keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
                 } else {
-                    QString command = _ui->promptTextEdit->toPlainText().trimmed();
+                    QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed();
 
                     if (!command.isEmpty()) {
                         QTextCursor cursor = _ui->promptTextEdit->textCursor();
diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h
index 864f847071..59280f65aa 100644
--- a/interface/src/ui/JSConsole.h
+++ b/interface/src/ui/JSConsole.h
@@ -63,6 +63,7 @@ private:
     QFutureWatcher<QScriptValue> _executeWatcher;
     Ui::Console* _ui;
     int _currentCommandInHistory;
+    QString _savedHistoryFilename;
     QList<QString> _commandHistory;
     // Keeps track if the script engine is created inside the JSConsole
     bool _ownScriptEngine;
diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h
index 14eb81dd9a..577c215c9e 100644
--- a/libraries/shared/src/PathUtils.h
+++ b/libraries/shared/src/PathUtils.h
@@ -13,6 +13,8 @@
 #define hifi_PathUtils_h
 
 #include <QtCore/QObject>
+#include <QtCore/QUrl>
+
 #include "DependencyManager.h"
 
 /**jsdoc
@@ -24,6 +26,7 @@ class PathUtils : public QObject, public Dependency {
     Q_OBJECT
     SINGLETON_DEPENDENCY
     Q_PROPERTY(QString resources READ resourcesPath)
+    Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation)
 public:
     static const QString& resourcesPath();
 
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js
index 777ef54085..70594d8f1e 100644
--- a/scripts/system/html/js/entityProperties.js
+++ b/scripts/system/html/js/entityProperties.js
@@ -1074,7 +1074,7 @@ function loaded() {
         elDimensionsZ.addEventListener('change', dimensionsChangeFunction);
 
         elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID'));
-        elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex'));
+        elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex', 0));
 
         var registrationChangeFunction = createEmitVec3PropertyUpdateFunction(
             'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ);
diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js
index 725803f824..2d1853fae2 100644
--- a/scripts/system/libraries/entitySelectionTool.js
+++ b/scripts/system/libraries/entitySelectionTool.js
@@ -1560,7 +1560,6 @@ SelectionDisplay = (function() {
             visible: rotationOverlaysVisible
         });
 
-        // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden
         Overlays.editOverlay(yawHandle, {
             visible: rotateHandlesVisible,
             position: yawCorner,
@@ -3615,24 +3614,21 @@ SelectionDisplay = (function() {
         onMove: function(event) {
             var pickRay = generalComputePickRay(event.x, event.y);
             Overlays.editOverlay(selectionBox, {
-                ignoreRayIntersection: true,
                 visible: false
             });
             Overlays.editOverlay(baseOfEntityProjectionOverlay, {
-                ignoreRayIntersection: true,
                 visible: false
             });
-            Overlays.editOverlay(rotateOverlayTarget, {
-                ignoreRayIntersection: false
-            });
 
-            var result = Overlays.findRayIntersection(pickRay);
+            var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
 
             if (result.intersects) {
                 var center = yawCenter;
                 var zero = yawZero;
+                // TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle)
                 var centerToZero = Vec3.subtract(center, zero);
                 var centerToIntersect = Vec3.subtract(center, result.intersection);
+                // TODO: orientedAngle wants normalized centerToZero and centerToIntersect
                 var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
                 var distanceFromCenter = Vec3.distance(center, result.intersection);
                 var snapToInner = distanceFromCenter < innerRadius;
@@ -3785,17 +3781,12 @@ SelectionDisplay = (function() {
         onMove: function(event) {
             var pickRay = generalComputePickRay(event.x, event.y);
             Overlays.editOverlay(selectionBox, {
-                ignoreRayIntersection: true,
                 visible: false
             });
             Overlays.editOverlay(baseOfEntityProjectionOverlay, {
-                ignoreRayIntersection: true,
                 visible: false
             });
-            Overlays.editOverlay(rotateOverlayTarget, {
-                ignoreRayIntersection: false
-            });
-            var result = Overlays.findRayIntersection(pickRay);
+            var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
 
             if (result.intersects) {
                 var properties = Entities.getEntityProperties(selectionManager.selections[0]);
@@ -3947,17 +3938,12 @@ SelectionDisplay = (function() {
         onMove: function(event) {
             var pickRay = generalComputePickRay(event.x, event.y);
             Overlays.editOverlay(selectionBox, {
-                ignoreRayIntersection: true,
                 visible: false
             });
             Overlays.editOverlay(baseOfEntityProjectionOverlay, {
-                ignoreRayIntersection: true,
                 visible: false
             });
-            Overlays.editOverlay(rotateOverlayTarget, {
-                ignoreRayIntersection: false
-            });
-            var result = Overlays.findRayIntersection(pickRay);
+            var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]);
 
             if (result.intersects) {
                 var properties = Entities.getEntityProperties(selectionManager.selections[0]);
@@ -4074,21 +4060,8 @@ SelectionDisplay = (function() {
             return false;
         }
 
-        // before we do a ray test for grabbers, disable the ray intersection for our selection box
-        Overlays.editOverlay(selectionBox, {
-            ignoreRayIntersection: true
-        });
-        Overlays.editOverlay(yawHandle, {
-            ignoreRayIntersection: true
-        });
-        Overlays.editOverlay(pitchHandle, {
-            ignoreRayIntersection: true
-        });
-        Overlays.editOverlay(rollHandle, {
-            ignoreRayIntersection: true
-        });
-
-        result = Overlays.findRayIntersection(pickRay);
+        // ignore ray intersection for our selection box and yaw/pitch/roll
+        result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] );
         if (result.intersects) {
             if (wantDebug) {
                 print("something intersects... ");
@@ -4191,17 +4164,8 @@ SelectionDisplay = (function() {
             }
 
 
-            // After testing our stretch handles, then check out rotate handles
-            Overlays.editOverlay(yawHandle, {
-                ignoreRayIntersection: false
-            });
-            Overlays.editOverlay(pitchHandle, {
-                ignoreRayIntersection: false
-            });
-            Overlays.editOverlay(rollHandle, {
-                ignoreRayIntersection: false
-            });
-            var result = Overlays.findRayIntersection(pickRay);
+            // Only intersect versus yaw/pitch/roll.
+            var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] );
 
             var overlayOrientation;
             var overlayCenter;
@@ -4306,6 +4270,7 @@ SelectionDisplay = (function() {
                 });
 
 
+                // TODO: these three duplicate prior three, remove them.
                 Overlays.editOverlay(yawHandle, {
                     visible: false
                 });
@@ -4402,10 +4367,8 @@ SelectionDisplay = (function() {
         }
 
         if (!somethingClicked) {
-            Overlays.editOverlay(selectionBox, {
-                ignoreRayIntersection: false
-            });
-            var result = Overlays.findRayIntersection(pickRay);
+            // Only intersect versus selectionBox.
+            var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]);
             if (result.intersects) {
                 switch (result.overlayID) {
                     case selectionBox: