diff --git a/.gitignore b/.gitignore
index c5826b78..cba50a69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
 /key.pem
 /lib
 /node_modules
+/electron/node_modules
 /packages/
 /webapp
 /.npmrc
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2ed3149..ea63ee66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+Changes in [0.9.9](https://github.com/vector-im/riot-web/releases/tag/v0.9.9) (2017-04-25)
+==========================================================================================
+[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.9-rc.2...v0.9.9)
+
+ * No changes
+
+
+Changes in [0.9.9-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.9-rc.2) (2017-04-24)
+====================================================================================================
+[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.9-rc.1...v0.9.9-rc.2)
+
+ * Fix bug where links to Riot would fail to open.
+
+
+Changes in [0.9.9-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.9-rc.1) (2017-04-21)
+====================================================================================================
+[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8...v0.9.9-rc.1)
+
+ * Update js-sdk and matrix-react-sdk to fix registration without a captcha (https://github.com/vector-im/riot-web/issues/3621)
+
+
 Changes in [0.9.8](https://github.com/vector-im/riot-web/releases/tag/v0.9.8) (2017-04-12)
 ==========================================================================================
 [Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.3...v0.9.8)
diff --git a/README.md b/README.md
index 2d7ab81b..55463a37 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ To run as a desktop app:
 
    ```
    npm install electron
-   node_modules/.bin/electron .
+   npm run electron
    ```
 
 To build packages, use electron-builder. This is configured to output:
diff --git a/config.sample.json b/config.sample.json
index a65646ac..3c513f7a 100644
--- a/config.sample.json
+++ b/config.sample.json
@@ -4,7 +4,7 @@
     "brand": "Riot",
     "integrations_ui_url": "https://scalar.vector.im/",
     "integrations_rest_url": "https://scalar.vector.im/api",
-    "bug_report_endpoint_url": "https://vector.im/bugs",
+    "bug_report_endpoint_url": "https://riot.im/bugreports/submit",
     "enableLabs": true,
     "roomDirectory": {
         "servers": [
diff --git a/electron/img/riot.ico b/electron/img/riot.ico
deleted file mode 100644
index 8b681ffb..00000000
Binary files a/electron/img/riot.ico and /dev/null differ
diff --git a/electron/img/riot.png b/electron/img/riot.png
deleted file mode 100644
index fe13aa99..00000000
Binary files a/electron/img/riot.png and /dev/null differ
diff --git a/electron/build/icon.icns b/electron_app/build/icon.icns
similarity index 100%
rename from electron/build/icon.icns
rename to electron_app/build/icon.icns
diff --git a/electron/build/icon.ico b/electron_app/build/icon.ico
similarity index 100%
rename from electron/build/icon.ico
rename to electron_app/build/icon.ico
diff --git a/electron/build/icons/128x128.png b/electron_app/build/icons/128x128.png
similarity index 100%
rename from electron/build/icons/128x128.png
rename to electron_app/build/icons/128x128.png
diff --git a/electron/build/icons/16x16.png b/electron_app/build/icons/16x16.png
similarity index 100%
rename from electron/build/icons/16x16.png
rename to electron_app/build/icons/16x16.png
diff --git a/electron/build/icons/24x24.png b/electron_app/build/icons/24x24.png
similarity index 100%
rename from electron/build/icons/24x24.png
rename to electron_app/build/icons/24x24.png
diff --git a/electron/build/icons/256x256.png b/electron_app/build/icons/256x256.png
similarity index 100%
rename from electron/build/icons/256x256.png
rename to electron_app/build/icons/256x256.png
diff --git a/electron/build/icons/48x48.png b/electron_app/build/icons/48x48.png
similarity index 100%
rename from electron/build/icons/48x48.png
rename to electron_app/build/icons/48x48.png
diff --git a/electron/build/icons/512x512.png b/electron_app/build/icons/512x512.png
similarity index 100%
rename from electron/build/icons/512x512.png
rename to electron_app/build/icons/512x512.png
diff --git a/electron/build/icons/64x64.png b/electron_app/build/icons/64x64.png
similarity index 100%
rename from electron/build/icons/64x64.png
rename to electron_app/build/icons/64x64.png
diff --git a/electron/build/icons/96x96.png b/electron_app/build/icons/96x96.png
similarity index 100%
rename from electron/build/icons/96x96.png
rename to electron_app/build/icons/96x96.png
diff --git a/electron/build/install-spinner.gif b/electron_app/build/install-spinner.gif
similarity index 100%
rename from electron/build/install-spinner.gif
rename to electron_app/build/install-spinner.gif
diff --git a/electron_app/img/riot.ico b/electron_app/img/riot.ico
new file mode 100644
index 00000000..8f8ff94e
Binary files /dev/null and b/electron_app/img/riot.ico differ
diff --git a/electron_app/img/riot.png b/electron_app/img/riot.png
new file mode 100644
index 00000000..85e9f8ca
Binary files /dev/null and b/electron_app/img/riot.png differ
diff --git a/electron_app/package.json b/electron_app/package.json
new file mode 100644
index 00000000..99651cc1
--- /dev/null
+++ b/electron_app/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "riot-web",
+  "productName": "Riot",
+  "main": "src/electron-main.js",
+  "version": "0.9.9",
+  "description": "A feature-rich client for Matrix.org",
+  "author": "Vector Creations Ltd.",
+  "dependencies": {
+    "electron-window-state": "^4.1.0"
+  }
+}
diff --git a/electron/riot.im/README b/electron_app/riot.im/README
similarity index 100%
rename from electron/riot.im/README
rename to electron_app/riot.im/README
diff --git a/electron/riot.im/config.json b/electron_app/riot.im/config.json
similarity index 100%
rename from electron/riot.im/config.json
rename to electron_app/riot.im/config.json
diff --git a/electron/src/electron-main.js b/electron_app/src/electron-main.js
similarity index 95%
rename from electron/src/electron-main.js
rename to electron_app/src/electron-main.js
index a1fc9c1a..32e305d8 100644
--- a/electron/src/electron-main.js
+++ b/electron_app/src/electron-main.js
@@ -30,6 +30,8 @@ const tray = require('./tray');
 
 const VectorMenu = require('./vectormenu');
 
+const windowStateKeeper = require('electron-window-state');
+
 let vectorConfig = {};
 try {
     vectorConfig = require('../../webapp/config.json');
@@ -187,11 +189,21 @@ electron.app.on('ready', () => {
         process.platform == 'win32' ? 'ico' : 'png'
     );
 
+    // Load the previous window state with fallback to defaults
+    let mainWindowState = windowStateKeeper({
+        defaultWidth: 1024,
+        defaultHeight: 768,
+    });
+
     mainWindow = new electron.BrowserWindow({
         icon: icon_path,
-        width: 1024, height: 768,
         show: false,
         autoHideMenuBar: true,
+
+        x: mainWindowState.x,
+        y: mainWindowState.y,
+        width: mainWindowState.width,
+        height: mainWindowState.height,
     });
     mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
     electron.Menu.setApplicationMenu(VectorMenu);
@@ -230,6 +242,8 @@ electron.app.on('ready', () => {
             onLinkContextMenu(ev, params);
         }
     });
+
+    mainWindowState.manage(mainWindow);
 });
 
 electron.app.on('window-all-closed', () => {
diff --git a/electron/src/squirrelhooks.js b/electron_app/src/squirrelhooks.js
similarity index 100%
rename from electron/src/squirrelhooks.js
rename to electron_app/src/squirrelhooks.js
diff --git a/electron/src/tray.js b/electron_app/src/tray.js
similarity index 100%
rename from electron/src/tray.js
rename to electron_app/src/tray.js
diff --git a/electron/src/vectormenu.js b/electron_app/src/vectormenu.js
similarity index 100%
rename from electron/src/vectormenu.js
rename to electron_app/src/vectormenu.js
diff --git a/package.json b/package.json
index a25e1795..884811af 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
   "name": "riot-web",
   "productName": "Riot",
-  "main": "electron/src/electron-main.js",
-  "version": "0.9.8",
+  "main": "electron_app/src/electron-main.js",
+  "version": "0.9.9",
   "description": "A feature-rich client for Matrix.org",
   "author": "Vector Creations Ltd.",
   "repository": {
@@ -33,13 +33,15 @@
     "build:bundle": "cross-env NODE_ENV=production webpack -p --progress",
     "build:bundle:dev": "webpack --optimize-occurence-order --progress",
     "build:electron": "npm run clean && npm run build && build -wml --ia32 --x64",
-    "build": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle",
-    "build:dev": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle:dev",
+    "build": "npm run build:res && npm run build:bundle",
+    "build:dev": "npm run build:res && npm run build:bundle:dev",
     "dist": "scripts/package.sh",
+    "install:electron": "install-app-deps",
+    "electron": "npm run install:electron && electron .",
     "start:res": "node scripts/copy-res.js -w",
     "start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-file=bundles/_dev_/[name].js -w --progress",
     "start:js:prod": "cross-env NODE_ENV=production webpack-dev-server -w --progress",
-    "start": "node scripts/babelcheck.js && parallelshell \"npm run start:res\" \"npm run start:js\"",
+    "start": "parallelshell \"npm run start:res\" \"npm run start:js\"",
     "start:prod": "parallelshell \"npm run start:res\" \"npm run start:js:prod\"",
     "lint": "eslint src/",
     "lintall": "eslint src/ test/",
@@ -145,10 +147,12 @@
     "dereference": true,
     "//files": "We bundle everything, so we only need to include webapp/",
     "files": [
-      "electron/src/**",
-      "electron/img/**",
-      "webapp/**",
-      "package.json"
+      "node_modules/**",
+      "src/**",
+      "img/**"
+    ],
+    "extraResources": [
+      "webapp/**/*"
     ],
     "linux": {
       "target": "deb",
@@ -159,10 +163,11 @@
     },
     "win": {
       "target": "squirrel"
+    },
+    "directories": {
+      "buildResources": "electron_app/build",
+      "output": "electron_app/dist",
+      "app": "electron_app"
     }
-  },
-  "directories": {
-    "buildResources": "electron/build",
-    "output": "electron/dist"
   }
 }
diff --git a/release.sh b/release.sh
index e8c68b90..c2454560 100755
--- a/release.sh
+++ b/release.sh
@@ -1,12 +1,25 @@
-#!/bin/sh
+#!/bin/bash
 #
 # Script to perform a release of vector-web.
 #
-# Requires github-changelog-generator; to install, do 
+# Requires github-changelog-generator; to install, do
 #   pip install git+https://github.com/matrix-org/github-changelog-generator.git
 
 set -e
 
 cd `dirname $0`
 
+
+# bump Electron's package.json first
+release="${1#v}"
+tag="v${release}"
+echo "electron npm version"
+
+cd electron_app
+npm version --no-git-tag-version "$release"
+git commit package.json -m "$tag"
+
+
+cd ..
+
 exec ./node_modules/matrix-js-sdk/release.sh -z "$@"
diff --git a/scripts/babelcheck.js b/scripts/babelcheck.js
deleted file mode 100644
index 89f5cac5..00000000
--- a/scripts/babelcheck.js
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-
-var exec = require('child_process').exec;
-
-// Makes sure the babel executable in the path is babel 6 (or greater), not
-// babel 5, which it is if you upgrade from an older version of react-sdk and
-// run 'npm install' since the package has changed to babel-cli, so 'babel'
-// remains installed and the executable in node_modules/.bin remains as babel
-// 5.
-
-// This script is duplicated from matrix-react-sdk because it can't reliably
-// be pulled in from react-sdk while npm install is failing, as it will do
-// if the environment is in the erroneous state this script checks for.
-
-exec("babel -V", function (error, stdout, stderr) {
-    if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
-        console.log("\033[31m\033[1m"+
-            '*****************************************\n'+
-            '* vector-web has moved to babel 6       *\n'+
-            '* Please "rm -rf node_modules && npm i" *\n'+
-            '* then restore links as appropriate     *\n'+
-            '*****************************************\n'+
-        "\033[91m");
-        process.exit(1);
-    }
-});
diff --git a/scripts/electron-package.sh b/scripts/electron-package.sh
index 87e353f7..a5718df8 100755
--- a/scripts/electron-package.sh
+++ b/scripts/electron-package.sh
@@ -90,8 +90,8 @@ npm run build:electron
 
 popd
 
-distdir="$builddir/electron/dist"
-pubdir="$projdir/electron/pub"
+distdir="$builddir/electron_app/dist"
+pubdir="$projdir/electron_app/pub"
 rm -r "$pubdir" || true
 mkdir -p "$pubdir"
 
@@ -120,11 +120,11 @@ cp $distdir/win/*.nupkg "$pubdir/update/win32/x64/"
 cp $distdir/win/RELEASES "$pubdir/update/win32/x64/"
 
 # Move the debs to the main project dir's dist folder
-rm -r "$projdir/electron/dist" || true
-mkdir -p "$projdir/electron/dist"
-cp $distdir/*.deb "$projdir/electron/dist/"
+rm -r "$projdir/electron_app/dist" || true
+mkdir -p "$projdir/electron_app/dist"
+cp $distdir/*.deb "$projdir/electron_app/dist/"
 
 rm -rf "$builddir"
 
 echo "Riot Desktop is ready to go in $pubdir: this directory can be hosted on your web server."
-echo "deb archives are in electron/dist/ - these should be added into your debian repository"
+echo "deb archives are in electron_app/dist/ - these should be added into your debian repository"
diff --git a/scripts/issues-burndown.pl b/scripts/issues-burndown.pl
old mode 100644
new mode 100755
index 7d0f3409..67c05673
--- a/scripts/issues-burndown.pl
+++ b/scripts/issues-burndown.pl
@@ -4,16 +4,24 @@ use warnings;
 use strict;
 
 use Net::GitHub;
-use DateTime;
-use DateTime::Format::ISO8601;
+use Time::Moment;
+use Term::ReadPassword;
+
+# This version of the script emits the cumulative number of bugs, split into open & closed
+# suitable for drawing the 'top' and 'bottom' of a burndown graph.
+#
+# N.B. this doesn't take into account issues changing priority over time, but only their most recent priority.
+#
+# If you want instead the number of open issues on a given day, then look at issues-no-state.pl
 
 my $gh = Net::GitHub->new(
-    login => 'ara4n', pass => 'secret'
+    login => 'ara4n', pass => read_password("github password: "),
 );
 
 $gh->set_default_user_repo('vector-im', 'vector-web'); 
 
-my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
+#my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
+my @issues = $gh->issue->repos_issues({ state => 'all' });
 while ($gh->issue->has_next_page) {
     push @issues, $gh->issue->next_page;
 }
@@ -30,11 +38,11 @@ while ($gh->issue->has_next_page) {
 
 my $days = {};
 my $schema = {};
-my $now = DateTime->now();
+my $now = Time::Moment->now;
 
 foreach my $issue (@issues) {
     next if ($issue->{pull_request});
-    
+
     # use Data::Dumper;
     # print STDERR Dumper($issue);
 
@@ -56,10 +64,10 @@ foreach my $issue (@issues) {
     my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
     my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
 
-    my $start = DateTime::Format::ISO8601->parse_datetime($issue->{created_at});
+    my $start = Time::Moment->from_string($issue->{created_at});
 
     do {
-        my $ymd = $start->ymd();
+        my $ymd = $start->strftime('%F');
 
         $days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
         $schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
@@ -68,13 +76,14 @@ foreach my $issue (@issues) {
             $schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
         }
 
-        $start = $start->add(days => 1);
-    } while (DateTime->compare($start, $now) < 0);
+        $start = $start->plus_days(1);
+        # print STDERR "^";
+    } while ($start->compare($now) < 0);
 
     if ($state eq 'closed') {
-        my $end = DateTime::Format::ISO8601->parse_datetime($issue->{closed_at});
+        my $end = Time::Moment->from_string($issue->{closed_at});
         do {
-            my $ymd = $end->ymd();
+            my $ymd = $end->strftime('%F');
 
             $days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
             $schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
@@ -83,9 +92,12 @@ foreach my $issue (@issues) {
                 $schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
             }
 
-            $end = $end->add(days => 1);
-        } while (DateTime->compare($end, $now) < 0);
+            $end = $end->plus_days(1);
+        } while ($end->compare($now) < 0);
+        # print STDERR "v";
     }
+
+    # print STDERR "\n";
 }
 
 print "day,";
diff --git a/scripts/issues-no-state.pl b/scripts/issues-no-state.pl
index da12fb22..9b07ed27 100755
--- a/scripts/issues-no-state.pl
+++ b/scripts/issues-no-state.pl
@@ -6,14 +6,22 @@ use strict;
 use Net::GitHub;
 use DateTime;
 use DateTime::Format::ISO8601;
+use Term::ReadPassword;
+
+# This version of the script emits the total number of bugs open on a given day,
+# split by various tags.
+#
+# If you want instead the cumulative number of open & closed issues on a given day,
+# then look at issues-burndown.pl
 
 my $gh = Net::GitHub->new(
-    login => 'ara4n', pass => 'secret'
+    login => 'ara4n', pass => read_password("github password: "),
 );
 
 $gh->set_default_user_repo('vector-im', 'vector-web'); 
 
-my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
+#my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
+my @issues = $gh->issue->repos_issues({ state => 'all' });
 while ($gh->issue->has_next_page) {
     push @issues, $gh->issue->next_page;
 }
diff --git a/scripts/make-icons.sh b/scripts/make-icons.sh
index c77064ab..19e48895 100755
--- a/scripts/make-icons.sh
+++ b/scripts/make-icons.sh
@@ -52,7 +52,7 @@ cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_256x256.png"
 cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_256x256@2x.png"
 cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_512x512.png"
 cp "$tmpdir/1024.png" "$tmpdir/Riot.iconset/icon_512x512@2x.png"
-iconutil -c icns -o electron/build/icon.icns "$tmpdir/Riot.iconset"
+iconutil -c icns -o electron_app/build/icon.icns "$tmpdir/Riot.iconset"
 
 cp "$tmpdir/36.png" "res/vector-icons/android-chrome-36x36.png"
 cp "$tmpdir/48.png" "res/vector-icons/android-chrome-48x48.png"
@@ -79,15 +79,17 @@ cp "$tmpdir/144.png" "res/vector-icons/mstile-144x144.png"
 cp "$tmpdir/150.png" "res/vector-icons/mstile-150x150.png"
 cp "$tmpdir/310.png" "res/vector-icons/mstile-310x310.png"
 cp "$tmpdir/310x150.png" "res/vector-icons/mstile-310x150.png"
+cp "$tmpdir/180.png" "electron_app/img/riot.png"
 
 convert "$tmpdir/16.png" "$tmpdir/32.png" "$tmpdir/64.png" "$tmpdir/128.png"  "$tmpdir/256.png" "res/vector-icons/favicon.ico"
 
-cp "res/vector-icons/favicon.ico" "electron/build/icon.ico"
+cp "res/vector-icons/favicon.ico" "electron_app/build/icon.ico"
+cp "res/vector-icons/favicon.ico" "electron_app/img/riot.ico"
 
 # https://github.com/electron-userland/electron-builder/blob/3f97b86993d4ea5172e562b182230a194de0f621/src/targets/LinuxTargetHelper.ts#L127
 for i in 24 96 16 48 64 128 256 512
 do
-    cp "$tmpdir/$i.png" "electron/build/icons/${i}x${i}.png"
+    cp "$tmpdir/$i.png" "electron_app/build/icons/${i}x${i}.png"
 done
 
 rm -r "$tmpdir"
diff --git a/scripts/package.sh b/scripts/package.sh
index 5c1fdd5e..b3bc00bf 100755
--- a/scripts/package.sh
+++ b/scripts/package.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 set -e
 
@@ -22,7 +22,14 @@ cp config.sample.json webapp/
 
 mkdir -p dist
 cp -r webapp vector-$version
-echo $version > vector-$version/version
+
+# if $version looks like semver with leading v, strip it before writing to file
+if [[ ${version} =~ ^v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(-.+)?$ ]]; then
+    echo ${version:1} > vector-$version/version
+else
+    echo ${version} > vector-$version/version
+fi
+
 tar chvzf dist/vector-$version.tar.gz vector-$version
 rm -r vector-$version
 
diff --git a/src/components/structures/BottomLeftMenu.js b/src/components/structures/BottomLeftMenu.js
index f378cac6..63dfac60 100644
--- a/src/components/structures/BottomLeftMenu.js
+++ b/src/components/structures/BottomLeftMenu.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,13 +15,8 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-'use strict';
-
-var React = require('react');
-var ReactDOM = require('react-dom');
-var sdk = require('matrix-react-sdk')
-var dis = require('matrix-react-sdk/lib/dispatcher');
-var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
+import React from 'react';
+import sdk from 'matrix-react-sdk';
 
 module.exports = React.createClass({
     displayName: 'BottomLeftMenu',
@@ -30,121 +26,28 @@ module.exports = React.createClass({
         teamToken: React.PropTypes.string,
     },
 
-    getInitialState: function() {
-        return({
-            directoryHover : false,
-            roomsHover : false,
-            homeHover: false,
-            peopleHover : false,
-            settingsHover : false,
-        });
-    },
-
-    // Room events
-    onDirectoryClick: function() {
-        dis.dispatch({ action: 'view_room_directory' });
-    },
-
-    onDirectoryMouseEnter: function() {
-        this.setState({ directoryHover: true });
-    },
-
-    onDirectoryMouseLeave: function() {
-        this.setState({ directoryHover: false });
-    },
-
-    onRoomsClick: function() {
-        dis.dispatch({ action: 'view_create_room' });
-    },
-
-    onRoomsMouseEnter: function() {
-        this.setState({ roomsHover: true });
-    },
-
-    onRoomsMouseLeave: function() {
-        this.setState({ roomsHover: false });
-    },
-
-    // Home button events
-    onHomeClick: function() {
-        dis.dispatch({ action: 'view_home_page' });
-    },
-
-    onHomeMouseEnter: function() {
-        this.setState({ homeHover: true });
-    },
-
-    onHomeMouseLeave: function() {
-        this.setState({ homeHover: false });
-    },
-
-    // People events
-    onPeopleClick: function() {
-        dis.dispatch({ action: 'view_create_chat' });
-    },
-
-    onPeopleMouseEnter: function() {
-        this.setState({ peopleHover: true });
-    },
-
-    onPeopleMouseLeave: function() {
-        this.setState({ peopleHover: false });
-    },
-
-    // Settings events
-    onSettingsClick: function() {
-        dis.dispatch({ action: 'view_user_settings' });
-    },
-
-    onSettingsMouseEnter: function() {
-        this.setState({ settingsHover: true });
-    },
-
-    onSettingsMouseLeave: function() {
-        this.setState({ settingsHover: false });
-    },
-
-    // Get the label/tooltip to show
-    getLabel: function(label, show) {
-        if (show) {
-            var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
-            return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
-        }
-    },
-
     render: function() {
-        var TintableSvg = sdk.getComponent('elements.TintableSvg');
+        const HomeButton = sdk.getComponent('elements.HomeButton');
+        const StartChatButton = sdk.getComponent('elements.StartChatButton');
+        const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
+        const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
+        const SettingsButton = sdk.getComponent('elements.SettingsButton');
 
         var homeButton;
         if (this.props.teamToken) {
-            homeButton = (
-                <AccessibleButton className="mx_BottomLeftMenu_homePage" onClick={ this.onHomeClick } onMouseEnter={ this.onHomeMouseEnter } onMouseLeave={ this.onHomeMouseLeave } >
-                    <TintableSvg src="img/icons-home.svg" width="25" height="25" />
-                    { this.getLabel("Welcome page", this.state.homeHover) }
-                </AccessibleButton>
-            );
+            homeButton = <HomeButton tooltip={true} />;
         }
 
         return (
             <div className="mx_BottomLeftMenu">
                 <div className="mx_BottomLeftMenu_options">
                     { homeButton }
-                    <AccessibleButton className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
-                        <TintableSvg src="img/icons-people.svg" width="25" height="25" />
-                        { this.getLabel("Start chat", this.state.peopleHover) }
-                    </AccessibleButton>
-                    <AccessibleButton className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
-                        <TintableSvg src="img/icons-directory.svg" width="25" height="25"/>
-                        { this.getLabel("Room directory", this.state.directoryHover) }
-                    </AccessibleButton>
-                    <AccessibleButton className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
-                        <TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
-                        { this.getLabel("Create new room", this.state.roomsHover) }
-                    </AccessibleButton>
-                    <AccessibleButton className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
-                        <TintableSvg src="img/icons-settings.svg" width="25" height="25" />
-                        { this.getLabel("Settings", this.state.settingsHover) }
-                    </AccessibleButton>
+                    <StartChatButton tooltip={true} />
+                    <RoomDirectoryButton tooltip={true} />
+                    <CreateRoomButton tooltip={true} />
+                    <span className="mx_BottomLeftMenu_settings">
+                        <SettingsButton tooltip={true} />
+                    </span>
                 </div>
             </div>
         );
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index cdf8d19e..91046959 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -204,7 +204,7 @@ module.exports = React.createClass({
                 }).done(() => {
                     modal.close();
                     this.refreshRoomList();
-                }, function(err) {
+                }, (err) => {
                     modal.close();
                     this.refreshRoomList();
                     console.error("Failed to " + step + ": " + err);
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 577dac9c..ab6c4422 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -1,4 +1,5 @@
 /*
+Copyright 2017 Vector Creations Ltd
 Copyright 2015, 2016 OpenMarket Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,6 +30,7 @@ var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
 var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
 var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
 var RoomSubListHeader = require('./RoomSubListHeader.js');
+import Modal from 'matrix-react-sdk/lib/Modal';
 
 // turn this on for drag & drop console debugging galore
 var debug = false;
@@ -82,6 +84,8 @@ var RoomSubList = React.createClass({
         incomingCall: React.PropTypes.object,
         onShowMoreRooms: React.PropTypes.func,
         searchFilter: React.PropTypes.string,
+        emptyContent: React.PropTypes.node, // content shown if the list is empty
+        headerItems: React.PropTypes.node, // content shown in the sublist header
     },
 
     getInitialState: function() {
@@ -468,16 +472,15 @@ var RoomSubList = React.createClass({
 
     render: function() {
         var connectDropTarget = this.props.connectDropTarget;
-        var RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
         var TruncatedList = sdk.getComponent('elements.TruncatedList');
 
         var label = this.props.collapsed ? null : this.props.label;
 
-        //console.log("render: " + JSON.stringify(this.state.sortedList));
-
-        var target;
-        if (this.state.sortedList.length == 0 && this.props.editable) {
-            target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
+        let content;
+        if (this.state.sortedList.length == 0) {
+            content = this.props.emptyContent;
+        } else {
+            content = this.makeRoomTiles();
         }
 
         var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
@@ -497,8 +500,7 @@ var RoomSubList = React.createClass({
             if (!this.state.hidden) {
                 subList = <TruncatedList className={ classes } truncateAt={this.state.truncateAt}
                                          createOverflowElement={this._createOverflowTile} >
-                                { target }
-                                { this.makeRoomTiles() }
+                                { content }
                           </TruncatedList>;
             }
             else {
@@ -520,6 +522,7 @@ var RoomSubList = React.createClass({
                         roomNotificationCount={ this.roomNotificationCount() }
                         onClick={ this.onClick }
                         onHeaderClick={ this.props.onHeaderClick }
+                        headerItems={this.props.headerItems}
                     />
                     { subList }
                 </div>
@@ -541,6 +544,7 @@ var RoomSubList = React.createClass({
                             roomNotificationCount={ this.roomNotificationCount() }
                             onClick={ this.onClick }
                             onHeaderClick={ this.props.onHeaderClick }
+                            headerItems={this.props.headerItems}
                         />
                      : undefined }
                     { (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined }
diff --git a/src/components/structures/RoomSubListHeader.js b/src/components/structures/RoomSubListHeader.js
index ad9aff5f..74094ae0 100644
--- a/src/components/structures/RoomSubListHeader.js
+++ b/src/components/structures/RoomSubListHeader.js
@@ -14,16 +14,11 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-'use strict';
-
-var React = require('react');
-var ReactDOM = require('react-dom');
-var classNames = require('classnames');
-var sdk = require('matrix-react-sdk')
-var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
-var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
-var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
-var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
+import React from 'react';
+import classNames from 'classnames';
+import sdk from 'matrix-react-sdk';
+import { formatCount } from 'matrix-react-sdk/lib/utils/FormattingUtils';
+import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
 
 module.exports = React.createClass({
     displayName: 'RoomSubListHeader',
@@ -42,6 +37,7 @@ module.exports = React.createClass({
         hidden: React.PropTypes.bool,
         onClick: React.PropTypes.func,
         onHeaderClick: React.PropTypes.func,
+        headerItems: React.PropTypes.node, // content shown in the sublist header
     },
 
     getDefaultProps: function() {
@@ -63,35 +59,34 @@ module.exports = React.createClass({
     // },
 
     render: function() {
-        var TintableSvg = sdk.getComponent("elements.TintableSvg");
+        const TintableSvg = sdk.getComponent("elements.TintableSvg");
 
-        var subListNotifications = this.props.roomNotificationCount;
-        var subListNotifCount = subListNotifications[0];
-        var subListNotifHighlight = subListNotifications[1];
+        const subListNotifications = this.props.roomNotificationCount;
+        const subListNotifCount = subListNotifications[0];
+        const subListNotifHighlight = subListNotifications[1];
 
-        var chevronClasses = classNames({
+        const chevronClasses = classNames({
             'mx_RoomSubList_chevron': true,
             'mx_RoomSubList_chevronRight': this.props.hidden,
             'mx_RoomSubList_chevronDown': !this.props.hidden,
         });
 
-        var badgeClasses = classNames({
+        const badgeClasses = classNames({
             'mx_RoomSubList_badge': true,
             'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
         });
 
-        var badge;
+        let badge;
         if (subListNotifCount > 0) {
-            badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
-        }
-        else if (subListNotifHighlight) {
+            badge = <div className={badgeClasses}>{ formatCount(subListNotifCount) }</div>;
+        } else if (subListNotifHighlight) {
             badge = <div className={badgeClasses}>!</div>;   
         }
 
         // When collapsed, allow a long hover on the header to show user
         // the full tag name and room count
-        var title;
-        var roomCount = this.props.roomCount;
+        let title;
+        const roomCount = this.props.roomCount;
         if (this.props.collapsed) {
             title = this.props.label;
             if (roomCount !== '') {
@@ -99,9 +94,9 @@ module.exports = React.createClass({
             }
         }
 
-        var incomingCall;
+        let incomingCall;
         if (this.props.isIncomingCallRoom) {
-            var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
+            const IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
             incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
         }
 
@@ -109,6 +104,7 @@ module.exports = React.createClass({
             <div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
                 <AccessibleButton onClick={ this.props.onClick } className="mx_RoomSubList_label" tabIndex="0">
                     { this.props.collapsed ? '' : this.props.label }
+                    {this.props.headerItems}
                     <div className="mx_RoomSubList_roomCount">{ roomCount }</div>
                     <div className={chevronClasses}></div>
                     { badge }
diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 54f6c795..a0864817 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -27,6 +27,7 @@
 @import "./matrix-react-sdk/views/elements/_MemberEventListSummary.scss";
 @import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
 @import "./matrix-react-sdk/views/elements/_RichText.scss";
+@import "./matrix-react-sdk/views/elements/_RoleButton.scss";
 @import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
 @import "./matrix-react-sdk/views/login/_ServerConfig.scss";
 @import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
index 12861939..e251ecd1 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
@@ -160,7 +160,8 @@ hr.mx_RoomView_myReadMarker {
     border-bottom: solid 1px $accent-color;
     margin-top: 0px;
     position: relative;
-    top: 5px;
+    top: -1px;
+    z-index: 1;
 }
 
 .mx_RoomView_statusArea {
diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_MemberEventListSummary.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_MemberEventListSummary.scss
index 18588659..1969bc2d 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/elements/_MemberEventListSummary.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_MemberEventListSummary.scss
@@ -20,6 +20,7 @@ limitations under the License.
 
 .mx_TextualEvent.mx_MemberEventListSummary_summary {
     font-size: 14px;
+    display: inline-flex;
 }
 
 .mx_MemberEventListSummary_avatars {
diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_RoleButton.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_RoleButton.scss
new file mode 100644
index 00000000..094e0b9b
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_RoleButton.scss
@@ -0,0 +1,33 @@
+/*
+Copyright 2107 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_RoleButton {
+    margin-left: 4px;
+    margin-right: 4px;
+    cursor: pointer;
+    display: inline-block;
+}
+
+.mx_RoleButton object {
+    pointer-events: none;
+}
+
+.mx_RoleButton_tooltip {
+    display: inline-block;
+    position: relative;
+    top: -25px;
+    left: 6px;
+}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomList.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomList.scss
index 110dcd5b..35787ca0 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomList.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomList.scss
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2107 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -37,3 +38,25 @@ limitations under the License.
 .mx_RoomList_scrollbar .gm-scrollbar.-vertical {
     z-index: 6;
 }
+
+.mx_RoomList_emptySubListTip {
+    font-size: 13px;
+    margin-left: 18px;
+    margin-right: 18px;
+    margin-top: 8px;
+    margin-bottom: 7px;
+    padding: 5px;
+    border: 1px dashed $accent-color;
+    color: $primary-fg-color;
+    background-color: $droptarget-bg-color;
+    border-radius: 4px;
+}
+
+.mx_RoomList_emptySubListTip .mx_RoleButton {
+    vertical-align: -3px;
+}
+
+.mx_RoomList_headerButtons {
+    position: absolute;
+    right: 60px;
+}
diff --git a/src/skins/vector/css/vector-web/structures/_LeftPanel.scss b/src/skins/vector/css/vector-web/structures/_LeftPanel.scss
index d3bbce1b..f171591c 100644
--- a/src/skins/vector/css/vector-web/structures/_LeftPanel.scss
+++ b/src/skins/vector/css/vector-web/structures/_LeftPanel.scss
@@ -64,43 +64,25 @@ limitations under the License.
     pointer-events: none;
 }
 
-.mx_LeftPanel .mx_BottomLeftMenu_homePage,
-.mx_LeftPanel .mx_BottomLeftMenu_directory,
-.mx_LeftPanel .mx_BottomLeftMenu_createRoom,
-.mx_LeftPanel .mx_BottomLeftMenu_people,
-.mx_LeftPanel .mx_BottomLeftMenu_settings {
-    display: inline-block;
-    cursor: pointer;
-}
-
-.collapsed .mx_BottomLeftMenu_homePage,
-.collapsed .mx_BottomLeftMenu_directory,
-.collapsed .mx_BottomLeftMenu_createRoom,
-.collapsed .mx_BottomLeftMenu_people,
-.collapsed .mx_BottomLeftMenu_settings {
+.collapsed .mx_RoleButton {
     margin-right: 0px ! important;
     padding-top: 3px ! important;
     padding-bottom: 3px ! important;
 }
 
-.mx_LeftPanel .mx_BottomLeftMenu_homePage,
-.mx_LeftPanel .mx_BottomLeftMenu_directory,
-.mx_LeftPanel .mx_BottomLeftMenu_createRoom,
-.mx_LeftPanel .mx_BottomLeftMenu_people {
+.mx_BottomLeftMenu_options .mx_RoleButton {
+    margin-left: 0px;
     margin-right: 10px;
 }
 
-.mx_LeftPanel .mx_BottomLeftMenu_settings {
+.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings {
     float: right;
 }
 
+.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings .mx_RoleButton {
+    margin-right: 0px;
+}
+
 .mx_LeftPanel.collapsed .mx_BottomLeftMenu_settings {
     float: none;
 }
-
-.mx_LeftPanel .mx_BottomLeftMenu_tooltip {
-    display: inline-block;
-    position: relative;
-    top: -25px;
-    left: 6px;
-}
diff --git a/src/vector/platform/ElectronPlatform.js b/src/vector/platform/ElectronPlatform.js
index 9c857e35..82ef0b51 100644
--- a/src/vector/platform/ElectronPlatform.js
+++ b/src/vector/platform/ElectronPlatform.js
@@ -20,13 +20,11 @@ limitations under the License.
 import VectorBasePlatform from './VectorBasePlatform';
 import dis from 'matrix-react-sdk/lib/dispatcher';
 import q from 'q';
-
-const electron = require('electron');
-const remote = electron.remote;
+import electron, {remote} from 'electron';
 
 remote.autoUpdater.on('update-downloaded', onUpdateDownloaded);
 
-function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) {
+function onUpdateDownloaded(ev: Event, releaseNotes: string, ver: string, date: Date, updateURL: string) {
     dis.dispatch({
         action: 'new_version',
         currentVersion: remote.app.getVersion(),
@@ -35,7 +33,7 @@ function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) {
     });
 }
 
-function platformFriendlyName() {
+function platformFriendlyName(): string {
     console.log(window.process);
     switch (window.process.platform) {
         case 'darwin':
@@ -72,11 +70,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
         }
     }
 
-    supportsNotifications() : boolean {
+    supportsNotifications(): boolean {
         return true;
     }
 
-    maySendNotifications() : boolean {
+    maySendNotifications(): boolean {
         return true;
     }
 
@@ -100,7 +98,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
                 icon: avatarUrl,
                 tag: 'vector',
                 silent: true, // we play our own sounds
-            }
+            },
         );
 
         notification.onclick = function() {
@@ -123,7 +121,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
         notif.close();
     }
 
-    getAppVersion() {
+    getAppVersion(): Promise<string> {
         return q(remote.app.getVersion());
     }
 
@@ -140,15 +138,15 @@ export default class ElectronPlatform extends VectorBasePlatform {
         electron.ipcRenderer.send('install_update');
     }
 
-    getDefaultDeviceDisplayName() {
+    getDefaultDeviceDisplayName(): string {
         return 'Riot Desktop on ' + platformFriendlyName();
     }
 
-    screenCaptureErrorString() {
+    screenCaptureErrorString(): ?string {
         return null;
     }
 
-    requestNotificationPermission() : Promise {
+    requestNotificationPermission(): Promise<string> {
         return q('granted');
     }
 
diff --git a/src/vector/platform/VectorBasePlatform.js b/src/vector/platform/VectorBasePlatform.js
index 5240f3f5..1466b76a 100644
--- a/src/vector/platform/VectorBasePlatform.js
+++ b/src/vector/platform/VectorBasePlatform.js
@@ -44,7 +44,7 @@ export default class VectorBasePlatform extends BasePlatform {
      * Get a sensible default display name for the
      * device Vector is running on
      */
-    getDefaultDeviceDisplayName() {
+    getDefaultDeviceDisplayName(): string {
         return "Unknown device";
     }
 }
diff --git a/src/vector/platform/WebPlatform.js b/src/vector/platform/WebPlatform.js
index 5dc55052..72ca19f0 100644
--- a/src/vector/platform/WebPlatform.js
+++ b/src/vector/platform/WebPlatform.js
@@ -52,7 +52,7 @@ export default class WebPlatform extends VectorBasePlatform {
             }
 
             this.favicon.badge(notif, {
-                bgColor: bgColor
+                bgColor: bgColor,
             });
         } catch (e) {
             console.warn(`Failed to set badge count: ${e.message}`);
@@ -75,7 +75,7 @@ export default class WebPlatform extends VectorBasePlatform {
      * Returns true if the platform supports displaying
      * notifications, otherwise false.
      */
-    supportsNotifications() : boolean {
+    supportsNotifications(): boolean {
         return Boolean(global.Notification);
     }
 
@@ -83,8 +83,8 @@ export default class WebPlatform extends VectorBasePlatform {
      * Returns true if the application currently has permission
      * to display notifications. Otherwise false.
      */
-    maySendNotifications() : boolean {
-        return global.Notification.permission == 'granted';
+    maySendNotifications(): boolean {
+        return global.Notification.permission === 'granted';
     }
 
     /**
@@ -94,7 +94,7 @@ export default class WebPlatform extends VectorBasePlatform {
      * that is 'granted' if the user allowed the request or
      * 'denied' otherwise.
      */
-    requestNotificationPermission() : Promise {
+    requestNotificationPermission(): Promise<string> {
         // annoyingly, the latest spec says this returns a
         // promise, but this is only supported in Chrome 46
         // and Firefox 47, so adapt the callback API.
@@ -113,13 +113,13 @@ export default class WebPlatform extends VectorBasePlatform {
                 icon: avatarUrl,
                 tag: "vector",
                 silent: true, // we play our own sounds
-            }
+            },
         );
 
         notification.onclick = function() {
             dis.dispatch({
                 action: 'view_room',
-                room_id: room.roomId
+                room_id: room.roomId,
             });
             global.focus();
             notification.close();
@@ -132,7 +132,7 @@ export default class WebPlatform extends VectorBasePlatform {
         }, 5 * 1000);
     }
 
-    _getVersion() {
+    _getVersion(): Promise<string> {
         const deferred = q.defer();
 
         // We add a cachebuster to the request to make sure that we know about
@@ -148,19 +148,19 @@ export default class WebPlatform extends VectorBasePlatform {
             },
             (err, response, body) => {
                 if (err || response.status < 200 || response.status >= 300) {
-                    if (err == null) err = { status: response.status };
+                    if (err === null) err = { status: response.status };
                     deferred.reject(err);
                     return;
                 }
 
                 const ver = body.trim();
                 deferred.resolve(ver);
-            }
+            },
         );
         return deferred.promise;
     }
 
-    getAppVersion() {
+    getAppVersion(): Promise<string> {
         if (this.runningVersion !== null) {
             return q(this.runningVersion);
         }
@@ -169,9 +169,9 @@ export default class WebPlatform extends VectorBasePlatform {
 
     pollForUpdate() {
         this._getVersion().done((ver) => {
-            if (this.runningVersion == null) {
+            if (this.runningVersion === null) {
                 this.runningVersion = ver;
-            } else if (this.runningVersion != ver) {
+            } else if (this.runningVersion !== ver) {
                 dis.dispatch({
                     action: 'new_version',
                     currentVersion: this.runningVersion,
@@ -187,19 +187,18 @@ export default class WebPlatform extends VectorBasePlatform {
         window.location.reload();
     }
 
-    getDefaultDeviceDisplayName() {
+    getDefaultDeviceDisplayName(): string {
         // strip query-string and fragment from uri
-        let u = url.parse(window.location.href);
+        const u = url.parse(window.location.href);
         u.search = "";
         u.hash = "";
-        let app_name = u.format();
+        const appName = u.format();
 
-        let ua = new UAParser();
-        return app_name + " via " + ua.getBrowser().name +
-            " on " + ua.getOS().name;
+        const ua = new UAParser();
+        return `${appName} via ${ua.getBrowser().name} on ${ua.getOS().name}`;
     }
 
-    screenCaptureErrorString() {
+    screenCaptureErrorString(): ?string {
         // it won't work at all if you're not on HTTPS so whine whine whine
         if (!global.window || global.window.location.protocol !== "https:") {
             return "You need to be using HTTPS to place a screen-sharing call.";
diff --git a/src/vector/submit-rageshake.js b/src/vector/submit-rageshake.js
index ef6fbabe..45b427e8 100644
--- a/src/vector/submit-rageshake.js
+++ b/src/vector/submit-rageshake.js
@@ -17,6 +17,7 @@ limitations under the License.
 import pako from 'pako';
 import q from "q";
 
+import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
 import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
 
 import rageshake from './rageshake'
@@ -64,6 +65,8 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
         userAgent = window.navigator.userAgent;
     }
 
+    const client = MatrixClientPeg.get();
+
     console.log("Sending bug report.");
 
     const body = new FormData();
@@ -72,6 +75,11 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
     body.append('version', version);
     body.append('user_agent', userAgent);
 
+    if (client) {
+        body.append('user_id', client.credentials.userId);
+        body.append('device_id', client.deviceId);
+    }
+
     if (opts.sendLogs) {
         progressCallback("Collecting logs");
         const logs = await rageshake.getLogsForReport();