diff --git a/config.sample.json b/config.sample.json
index b707ec77..9e69c1c3 100644
--- a/config.sample.json
+++ b/config.sample.json
@@ -6,8 +6,8 @@
     "integrations_rest_url": "https://scalar.vector.im/api",
     "bug_report_endpoint_url": "https://riot.im/bugreports/submit",
     "features": {
-        "feature_groups": "labs"
-        "feature_pinning": "labs",
+        "feature_groups": "labs",
+        "feature_pinning": "labs"
     },
     "default_federate": true,
     "welcomePageUrl": "home.html",
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index 1988dc5e..60f07a62 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -19,6 +19,7 @@ limitations under the License.
 import React from 'react';
 import { DragDropContext } from 'react-dnd';
 import HTML5Backend from 'react-dnd-html5-backend';
+import classNames from 'classnames';
 import KeyCode from 'matrix-react-sdk/lib/KeyCode';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
@@ -55,7 +56,7 @@ var LeftPanel = React.createClass({
         // We just need to update if any of these things change.
         if (
             this.props.collapsed !== nextProps.collapsed ||
-            this.props.opacity !== nextProps.opacity
+            this.props.disabled !== nextProps.disabled
         ) {
             return true;
         }
@@ -176,14 +177,16 @@ var LeftPanel = React.createClass({
             topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
         }
 
-        let classes = "mx_LeftPanel mx_fadable";
-        if (this.props.collapsed) {
-            classes += " collapsed";
-        }
+        let classes = classNames(
+            "mx_LeftPanel", "mx_fadable",
+            {
+                "collapsed": this.props.collapsed,
+                "mx_fadable_faded": this.props.disabled,
+            }
+        );
 
         return (
-            <aside className={classes} style={{ opacity: this.props.opacity }}
-                   onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
+            <aside className={classes} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
                 { topBox }
                 <CallPreview ConferenceHandler={VectorConferenceHandler} />
                 <RoomList
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 30142ad2..18567b57 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -18,14 +18,16 @@ limitations under the License.
 
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
-import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
+import MatrixClient from 'matrix-js-sdk';
 import Analytics from 'matrix-react-sdk/lib/Analytics';
 import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
 import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
 import { showGroupInviteDialog, showGroupAddRoomDialog } from 'matrix-react-sdk/lib/GroupAddressPicker';
+import GroupStoreCache from 'matrix-react-sdk/lib/stores/GroupStoreCache';
 
 class HeaderButton extends React.Component {
     constructor() {
@@ -80,6 +82,10 @@ module.exports = React.createClass({
         collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
     },
 
+    contextTypes: {
+        matrixClient: PropTypes.instanceOf(MatrixClient),
+    },
+
     Phase: {
         RoomMemberList: 'RoomMemberList',
         GroupMemberList: 'GroupMemberList',
@@ -92,29 +98,52 @@ module.exports = React.createClass({
 
     componentWillMount: function() {
         this.dispatcherRef = dis.register(this.onAction);
-        const cli = MatrixClientPeg.get();
+        const cli = this.context.matrixClient;
         cli.on("RoomState.members", this.onRoomStateMember);
+        this._initGroupStore(this.props.groupId);
     },
 
     componentWillUnmount: function() {
         dis.unregister(this.dispatcherRef);
-        if (MatrixClientPeg.get()) {
-            MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
+        if (this.context.matrixClient) {
+            this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
         }
+        this._unregisterGroupStore();
     },
 
     getInitialState: function() {
-        if (this.props.groupId) {
-            return {
-                phase: this.Phase.GroupMemberList,
-            };
-        } else {
-            return {
-                phase: this.Phase.RoomMemberList,
-            };
+        return {
+            phase: this.props.groupId ? this.Phase.GroupMemberList : this.Phase.RoomMemberList,
+            isUserPrivilegedInGroup: null,
         }
     },
 
+    componentWillReceiveProps(newProps) {
+        if (newProps.groupId !== this.props.groupId) {
+            this._unregisterGroupStore();
+            this._initGroupStore(newProps.groupId);
+        }
+    },
+
+    _initGroupStore(groupId) {
+        this._groupStore = GroupStoreCache.getGroupStore(
+            this.context.matrixClient, this.props.groupId,
+        );
+        this._groupStore.registerListener(this.onGroupStoreUpdated);
+    },
+
+    _unregisterGroupStore() {
+        if (this._groupStore) {
+            this._groupStore.unregisterListener(this.onGroupStoreUpdated);
+        }
+    },
+
+    onGroupStoreUpdated: function(){
+        this.setState({
+            isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
+        });
+    },
+
     onCollapseClick: function() {
         dis.dispatch({
             action: 'hide_right_panel',
@@ -122,7 +151,7 @@ module.exports = React.createClass({
     },
 
     onInviteButtonClick: function() {
-        if (MatrixClientPeg.get().isGuest()) {
+        if (this.context.matrixClient.isGuest()) {
             dis.dispatch({action: 'view_set_mxid'});
             return;
         }
@@ -222,13 +251,13 @@ module.exports = React.createClass({
         if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo)
             && this.props.roomId
         ) {
-            const cli = MatrixClientPeg.get();
+            const cli = this.context.matrixClient;
             const room = cli.getRoom(this.props.roomId);
             let userIsInRoom;
             if (room) {
                 membersBadge = room.getJoinedMembers().length;
                 userIsInRoom = room.hasMembershipState(
-                    MatrixClientPeg.get().credentials.userId, 'join',
+                    this.context.matrixClient.credentials.userId, 'join',
                 );
             }
 
@@ -243,6 +272,11 @@ module.exports = React.createClass({
             }
         }
 
+        const isPhaseGroup = [
+            this.Phase.GroupMemberInfo,
+            this.Phase.GroupMemberList
+        ].includes(this.state.phase);
+
         let headerButtons = [];
         if (this.props.roomId) {
             headerButtons = [
@@ -266,7 +300,7 @@ module.exports = React.createClass({
         } else if (this.props.groupId) {
             headerButtons = [
                 <HeaderButton key="_groupMembersButton" title={_t('Members')} iconSrc="img/icons-people.svg"
-                    isHighlighted={this.state.phase === this.Phase.GroupMemberList}
+                    isHighlighted={isPhaseGroup}
                     clickPhase={this.Phase.GroupMemberList}
                     analytics={['Right Panel', 'Group Member List Button', 'click']}
                 />,
@@ -317,8 +351,8 @@ module.exports = React.createClass({
             panel = <div className="mx_RightPanel_blank"></div>;
         }
 
-        if (this.props.groupId) {
-            inviteGroup = this.state.phase === this.Phase.GroupMemberList ? (
+        if (this.props.groupId && this.state.isUserPrivilegedInGroup) {
+            inviteGroup = isPhaseGroup ? (
                 <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
                     <div className="mx_RightPanel_icon" >
                         <TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
@@ -335,13 +369,16 @@ module.exports = React.createClass({
             );
         }
 
-        let classes = "mx_RightPanel mx_fadable";
-        if (this.props.collapsed) {
-            classes += " collapsed";
-        }
+        let classes = classNames(
+            "mx_RightPanel", "mx_fadable",
+            {
+                "collapsed": this.props.collapsed,
+                "mx_fadable_faded": this.props.disabled,
+            }
+        );
 
         return (
-            <aside className={classes} style={{ opacity: this.props.opacity }}>
+            <aside className={classes}>
                 <div className="mx_RightPanel_header">
                     <div className="mx_RightPanel_headerButtonGroup">
                         {headerButtons}
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index cd9ac565..323af86c 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -89,17 +89,17 @@ module.exports = React.createClass({
         });
 
         // dis.dispatch({
-        //     action: 'ui_opacity',
-        //     sideOpacity: 0.3,
-        //     middleOpacity: 0.3,
+        //     action: 'panel_disable',
+        //     sideDisabled: true,
+        //     middleDisabled: true,
         // });
     },
 
     componentWillUnmount: function() {
         // dis.dispatch({
-        //     action: 'ui_opacity',
-        //     sideOpacity: 1.0,
-        //     middleOpacity: 1.0,
+        //     action: 'panel_disable',
+        //     sideDisabled: false,
+        //     middleDisabled: false,
         // });
         if (this.filterTimeout) {
             clearTimeout(this.filterTimeout);
diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 9c5948b4..7760ea84 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -57,7 +57,7 @@ class SendCustomEvent extends React.Component {
     _buttons() {
         return <div className="mx_Dialog_buttons">
             <button onClick={this.onBack}>{ _t('Back') }</button>
-            {!this.state.message && <button onClick={this._send}>{ _t('Send') }</button>}
+            { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
         </div>;
     }
 
@@ -83,7 +83,7 @@ class SendCustomEvent extends React.Component {
     }
 
     _additionalFields() {
-        return <div/>;
+        return <div />;
     }
 
     _onChange(e) {
@@ -94,15 +94,15 @@ class SendCustomEvent extends React.Component {
         if (this.state.message) {
             return <div>
                 <div className="mx_Dialog_content">
-                    {this.state.message}
+                    { this.state.message }
                 </div>
-                {this._buttons()}
+                { this._buttons() }
             </div>;
         }
 
         return <div>
             <div className="mx_Dialog_content">
-                {this._additionalFields()}
+                { this._additionalFields() }
                 <div className="mx_TextInputDialog_label">
                     <label htmlFor="eventType"> { _t('Event Type') } </label>
                 </div>
@@ -117,7 +117,7 @@ class SendCustomEvent extends React.Component {
                     <textarea id="evContent" onChange={this._onChange} value={this.state.input_evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
                 </div>
             </div>
-            {this._buttons()}
+            { this._buttons() }
         </div>;
     }
 }
@@ -223,7 +223,7 @@ class RoomStateExplorer extends React.Component {
         if (this.state.event) {
             return <div className="mx_ViewSource">
                 <div className="mx_Dialog_content">
-                    <pre>{JSON.stringify(this.state.event.event, null, 2)}</pre>
+                    <pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
                 </div>
                 <div className="mx_Dialog_buttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
@@ -237,7 +237,7 @@ class RoomStateExplorer extends React.Component {
         if (this.state.eventType === null) {
             Object.keys(this.roomStateEvents).forEach((evType) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !evType.includes(this.state.query)) return;
+                if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLowerCase())) return;
 
                 const stateGroup = this.roomStateEvents[evType];
                 const stateKeys = Object.keys(stateGroup);
@@ -258,7 +258,7 @@ class RoomStateExplorer extends React.Component {
             const stateGroup = this.roomStateEvents[evType];
             Object.keys(stateGroup).forEach((stateKey) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !stateKey.includes(this.state.query)) return;
+                if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLowerCase())) return;
 
                 const ev = stateGroup[stateKey];
                 rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}
@@ -271,7 +271,7 @@ class RoomStateExplorer extends React.Component {
         return <div>
             <div className="mx_Dialog_content">
                 <input onChange={this.onQuery} placeholder={_t('Filter results')} size="64" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" value={this.state.query} />
-                {rows}
+                { rows }
             </div>
             <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index 6e497d62..fa3f48c1 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -14,16 +14,19 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-'use strict';
-var React = require('react');
-import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
+import React from 'react';
 import Promise from 'bluebird';
-var sdk = require('matrix-react-sdk');
-var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
-var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
-var Modal = require('matrix-react-sdk/lib/Modal');
-
-var notifications = require('../../../notifications');
+import sdk from 'matrix-react-sdk';
+import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
+import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
+import UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
+import Modal from 'matrix-react-sdk/lib/Modal';
+import {
+    NotificationUtils, 
+    VectorPushRulesDefinitions, 
+    PushRuleVectorState, 
+    ContentRules
+} from '../../../notifications';
 
 // TODO: this "view" component still has far too much application logic in it,
 // which should be factored out to other files.
@@ -31,17 +34,13 @@ var notifications = require('../../../notifications');
 // TODO: this component also does a lot of direct poking into this.state, which
 // is VERY NAUGHTY.
 
-var NotificationUtils = notifications.NotificationUtils;
-var VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
-var PushRuleVectorState = notifications.PushRuleVectorState;
-var ContentRules = notifications.ContentRules;
 
 /**
  * Rules that Vector used to set in order to override the actions of default rules.
  * These are used to port peoples existing overrides to match the current API.
  * These can be removed and forgotten once everyone has moved to the new client.
  */
-var LEGACY_RULES = {
+const LEGACY_RULES = {
     "im.vector.rule.contains_display_name": ".m.rule.contains_display_name",
     "im.vector.rule.room_one_to_one": ".m.rule.room_one_to_one",
     "im.vector.rule.room_message": ".m.rule.message",
@@ -51,7 +50,7 @@ var LEGACY_RULES = {
 };
 
 function portLegacyActions(actions) {
-    var decoded = NotificationUtils.decodeActions(actions);
+    const decoded = NotificationUtils.decodeActions(actions);
     if (decoded !== null) {
         return NotificationUtils.encodeActions(decoded);
     } else {
@@ -62,7 +61,7 @@ function portLegacyActions(actions) {
 }
 
 module.exports = React.createClass({
-    displayName: 'Notififications',
+    displayName: 'Notifications',
 
     phases: {
         LOADING: "LOADING", // The component is loading or sending data to the hs
@@ -102,7 +101,7 @@ module.exports = React.createClass({
     },
 
     onEnableNotificationsChange: function(event) {
-        var self = this;
+        const self = this;
         this.setState({
             phase: this.phases.LOADING
         });
@@ -122,20 +121,20 @@ module.exports = React.createClass({
     },
 
     onEnableEmailNotificationsChange: function(address, event) {
-        var emailPusherPromise;
+        let emailPusherPromise;
         if (event.target.checked) {
-            var data = {}
+            const data = {}
             data['brand'] = this.props.brand || 'Riot';
             emailPusherPromise = UserSettingsStore.addEmailPusher(address, data);
         } else {
-            var emailPusher = UserSettingsStore.getEmailPusher(this.state.pushers, address);
+            const emailPusher = UserSettingsStore.getEmailPusher(this.state.pushers, address);
             emailPusher.kind = null;
             emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
         }
         emailPusherPromise.done(() => {
             this._refreshFromServer();
         }, (error) => {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             Modal.createTrackedDialog('Error saving email notification preferences', '', ErrorDialog, {
                 title: _t('Error saving email notification preferences'),
                 description: _t('An error occurred whilst saving your email notification preferences.'),
@@ -145,14 +144,14 @@ module.exports = React.createClass({
 
     onNotifStateButtonClicked: function(event) {
         // FIXME: use .bind() rather than className metadata here surely
-        var vectorRuleId = event.target.className.split("-")[0];
-        var newPushRuleVectorState = event.target.className.split("-")[1];
+        const vectorRuleId = event.target.className.split("-")[0];
+        const newPushRuleVectorState = event.target.className.split("-")[1];
 
         if ("_keywords" === vectorRuleId) {
             this._setKeywordsPushRuleVectorState(newPushRuleVectorState)
         }
         else {
-            var rule = this.getRule(vectorRuleId);
+            const rule = this.getRule(vectorRuleId);
             if (rule) {
                 this._setPushRuleVectorState(rule, newPushRuleVectorState);
             }
@@ -160,12 +159,12 @@ module.exports = React.createClass({
     },
 
     onKeywordsClicked: function(event) {
-        var self = this;
+        const self = this;
 
         // Compute the keywords list to display
-        var keywords = [];
-        for (var i in this.state.vectorContentRules.rules) {
-            var rule = this.state.vectorContentRules.rules[i];
+        let keywords = [];
+        for (let i in this.state.vectorContentRules.rules) {
+            const rule = this.state.vectorContentRules.rules[i];
             keywords.push(rule.pattern);
         }
         if (keywords.length) {
@@ -179,7 +178,7 @@ module.exports = React.createClass({
             keywords = "";
         }
 
-        var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
+        const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
         Modal.createTrackedDialog('Keywords Dialog', '', TextInputDialog, {
             title: _t('Keywords'),
             description: _t('Enter keywords separated by a comma:'),
@@ -188,8 +187,8 @@ module.exports = React.createClass({
             onFinished: function onFinished(should_leave, newValue) {
 
                 if (should_leave && newValue !== keywords) {
-                    var newKeywords = newValue.split(',');
-                    for (var i in newKeywords) {
+                    let newKeywords = newValue.split(',');
+                    for (let i in newKeywords) {
                         newKeywords[i] = newKeywords[i].trim();
                     }
 
@@ -208,8 +207,8 @@ module.exports = React.createClass({
     },
 
     getRule: function(vectorRuleId) {
-        for (var i in this.state.vectorPushRules) {
-            var rule = this.state.vectorPushRules[i];
+        for (let i in this.state.vectorPushRules) {
+            const rule = this.state.vectorPushRules[i];
             if (rule.vectorRuleId === vectorRuleId) {
                 return rule;
             }
@@ -223,13 +222,13 @@ module.exports = React.createClass({
                 phase: this.phases.LOADING
             });
 
-            var self = this;
-            var cli = MatrixClientPeg.get();
-            var deferreds = [];
-            var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
+            const self = this;
+            const cli = MatrixClientPeg.get();
+            const deferreds = [];
+            const ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
 
             if (rule.rule) {
-                var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
+                const actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
 
                 if (!actions) {
                     // The new state corresponds to disabling the rule.
@@ -244,7 +243,7 @@ module.exports = React.createClass({
             Promise.all(deferreds).done(function() {
                 self._refreshFromServer();
             }, function(error) {
-                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                 console.error("Failed to change settings: " + error);
                 Modal.createTrackedDialog('Failed to change settings', '', ErrorDialog, {
                     title: _t('Failed to change settings'),
@@ -262,19 +261,19 @@ module.exports = React.createClass({
             return;
         }
 
-        var self = this;
-        var cli = MatrixClientPeg.get();
+        const self = this;
+        const cli = MatrixClientPeg.get();
 
         this.setState({
             phase: this.phases.LOADING
         });
 
         // Update all rules in self.state.vectorContentRules
-        var deferreds = [];
-        for (var i in this.state.vectorContentRules.rules) {
-            var rule = this.state.vectorContentRules.rules[i];
+        const deferreds = [];
+        for (let i in this.state.vectorContentRules.rules) {
+            const rule = this.state.vectorContentRules.rules[i];
 
-            var enabled, actions;
+            let enabled, actions;
             switch (newPushRuleVectorState) {
                 case PushRuleVectorState.ON:
                     if (rule.actions.length !== 1) {
@@ -314,7 +313,7 @@ module.exports = React.createClass({
         Promise.all(deferreds).done(function(resps) {
             self._refreshFromServer();
         }, function(error) {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             console.error("Can't update user notification settings: " + error);
             Modal.createTrackedDialog('Can\'t update user notifcation settings', '', ErrorDialog, {
                 title: _t('Can\'t update user notification settings'),
@@ -329,14 +328,14 @@ module.exports = React.createClass({
             phase: this.phases.LOADING
         });
 
-        var self = this;
-        var cli = MatrixClientPeg.get();
-        var removeDeferreds = [];
+        const self = this;
+        const cli = MatrixClientPeg.get();
+        const removeDeferreds = [];
 
         // Remove per-word push rules of keywords that are no more in the list
-        var vectorContentRulesPatterns = [];
-        for (var i in self.state.vectorContentRules.rules) {
-            var rule = self.state.vectorContentRules.rules[i];
+        const vectorContentRulesPatterns = [];
+        for (let i in self.state.vectorContentRules.rules) {
+            const rule = self.state.vectorContentRules.rules[i];
 
             vectorContentRulesPatterns.push(rule.pattern);
 
@@ -347,16 +346,16 @@ module.exports = React.createClass({
 
         // If the keyword is part of `externalContentRules`, remove the rule
         // before recreating it in the right Vector path
-        for (var i in self.state.externalContentRules) {
-            var rule = self.state.externalContentRules[i];
+        for (let i in self.state.externalContentRules) {
+            const rule = self.state.externalContentRules[i];
 
             if (newKeywords.indexOf(rule.pattern) >= 0) {
                 removeDeferreds.push(cli.deletePushRule('global', rule.kind, rule.rule_id));
             }
         }
 
-        var onError = function(error) {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+        const onError = function(error) {
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             console.error("Failed to update keywords: " + error);
             Modal.createTrackedDialog('Failed to update keywords', '', ErrorDialog, {
                 title: _t('Failed to update keywords'),
@@ -367,9 +366,9 @@ module.exports = React.createClass({
 
         // Then, add the new ones
         Promise.all(removeDeferreds).done(function(resps) {
-            var deferreds = [];
+            const deferreds = [];
 
-            var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
+            let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
             if (pushRuleVectorStateKind === PushRuleVectorState.OFF) {
                 // When the current global keywords rule is OFF, we need to look at
                 // the flavor of rules in 'vectorContentRules' to apply the same actions
@@ -384,8 +383,8 @@ module.exports = React.createClass({
                 }
             }
 
-            for (var i in newKeywords) {
-                var keyword = newKeywords[i];
+            for (let i in newKeywords) {
+                const keyword = newKeywords[i];
 
                 if (vectorContentRulesPatterns.indexOf(keyword) < 0) {
                     if (self.state.vectorContentRules.vectorState !== PushRuleVectorState.OFF) {
@@ -412,31 +411,31 @@ module.exports = React.createClass({
 
     // Create a push rule but disabled
     _addDisabledPushRule: function(scope, kind, ruleId, body) {
-        var cli = MatrixClientPeg.get();
-        return cli.addPushRule(scope, kind, ruleId, body).then(function() {
-            return cli.setPushRuleEnabled(scope, kind, ruleId, false);
-        });
+        const cli = MatrixClientPeg.get();
+        return cli.addPushRule(scope, kind, ruleId, body).then(() =>
+            cli.setPushRuleEnabled(scope, kind, ruleId, false)
+        );
     },
 
     // Check if any legacy im.vector rules need to be ported to the new API
     // for overriding the actions of default rules.
     _portRulesToNewAPI: function(rulesets) {
-        var self = this;
-        var needsUpdate = [];
-        var cli = MatrixClientPeg.get();
+        const self = this;
+        const needsUpdate = [];
+        const cli = MatrixClientPeg.get();
 
-        for (var kind in rulesets.global) {
-            var ruleset = rulesets.global[kind];
-            for (var i = 0; i < ruleset.length; ++i) {
-                var rule = ruleset[i];
+        for (let kind in rulesets.global) {
+            const ruleset = rulesets.global[kind];
+            for (let i = 0; i < ruleset.length; ++i) {
+                const rule = ruleset[i];
                 if (rule.rule_id in LEGACY_RULES) {
                     console.log("Porting legacy rule", rule);
                     needsUpdate.push( function(kind, rule) {
                         return cli.setPushRuleActions(
                             'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions)
-                        ).then( function() {
-                            return cli.deletePushRule('global', kind, rule.rule_id);
-                        }).catch( (e) => {
+                        ).then(() => 
+                            cli.deletePushRule('global', kind, rule.rule_id)
+                        ).catch( (e) => {
                             console.warn(`Error when porting legacy rule: ${e}`);
                         });
                     }(kind, rule));
@@ -447,9 +446,9 @@ module.exports = React.createClass({
         if (needsUpdate.length > 0) {
             // If some of the rules need to be ported then wait for the porting
             // to happen and then fetch the rules again.
-            return Promise.all(needsUpdate).then( function() {
-                return cli.getPushRules();
-            });
+            return Promise.all(needsUpdate).then(() => 
+                cli.getPushRules()
+            );
         } else {
             // Otherwise return the rules that we already have.
             return rulesets;
@@ -457,15 +456,14 @@ module.exports = React.createClass({
     },
 
     _refreshFromServer: function() {
-        var self = this;
-        var pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
-            //console.log("resolving pushRulesPromise");
+        const self = this;
+        const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
 
             /// XXX seriously? wtf is this?
             MatrixClientPeg.get().pushRules = rulesets;
 
             // Get homeserver default rules and triage them by categories
-            var rule_categories = {
+            const rule_categories = {
                 // The master rule (all notifications disabling)
                 '.m.rule.master': 'master',
 
@@ -483,12 +481,12 @@ module.exports = React.createClass({
             };
 
             // HS default rules
-            var defaultRules = {master: [], vector: {}, others: []};
+            const defaultRules = {master: [], vector: {}, others: []};
 
-            for (var kind in rulesets.global) {
-                for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
-                    var r = rulesets.global[kind][i];
-                    var cat = rule_categories[r.rule_id];
+            for (let kind in rulesets.global) {
+                for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
+                    const r = rulesets.global[kind][i];
+                    const cat = rule_categories[r.rule_id];
                     r.kind = kind;
 
                     if (r.rule_id[0] === '.') {
@@ -511,7 +509,7 @@ module.exports = React.createClass({
             }
 
             // parse the keyword rules into our state
-            var contentRules = ContentRules.parseContentRules(rulesets);
+            const contentRules = ContentRules.parseContentRules(rulesets);
             self.state.vectorContentRules = {
                 vectorState: contentRules.vectorState,
                 rules: contentRules.rules,
@@ -522,7 +520,7 @@ module.exports = React.createClass({
             self.state.vectorPushRules = [];
             self.state.externalPushRules = [];
 
-            var vectorRuleIds = [
+            const vectorRuleIds = [
                 '.m.rule.contains_display_name',
                 '.m.rule.contains_user_name',
                 '_keywords',
@@ -533,8 +531,8 @@ module.exports = React.createClass({
                 '.m.rule.call',
                 '.m.rule.suppress_notices'
             ];
-            for (var i in vectorRuleIds) {
-                var vectorRuleId = vectorRuleIds[i];
+            for (let i in vectorRuleIds) {
+                const vectorRuleId = vectorRuleIds[i];
 
                 if (vectorRuleId === '_keywords') {
                     // keywords needs a special handling
@@ -546,9 +544,8 @@ module.exports = React.createClass({
                             <span>
                             { _tJsx('Messages containing <span>keywords</span>',
                                 /<span>(.*?)<\/span>/,
-                                (sub) => {
-                                    return <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>;
-                                }
+                                (sub) =>
+                                    <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>
                             )}
                             </span>
                         ),
@@ -556,10 +553,10 @@ module.exports = React.createClass({
                     });
                 }
                 else {
-                    var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
-                    var rule = defaultRules.vector[vectorRuleId];
+                    const ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
+                    const rule = defaultRules.vector[vectorRuleId];
 
-                    var vectorState = ruleDefinition.ruleToVectorState(rule);
+                    const vectorState = ruleDefinition.ruleToVectorState(rule);
 
                     //console.log("Refreshing vectorPushRules for " + vectorRuleId +", "+ ruleDefinition.description +", " + rule +", " + vectorState);
 
@@ -579,14 +576,14 @@ module.exports = React.createClass({
             }
 
             // Build the rules not managed by Vector UI
-            var otherRulesDescriptions = {
+            const otherRulesDescriptions = {
                 '.m.rule.message': _t('Notify for all other messages/rooms'),
                 '.m.rule.fallback': _t('Notify me for anything else'),
             };
 
-            for (var i in defaultRules.others) {
-                var rule = defaultRules.others[i];
-                var ruleDescription = otherRulesDescriptions[rule.rule_id];
+            for (let i in defaultRules.others) {
+                const rule = defaultRules.others[i];
+                const ruleDescription = otherRulesDescriptions[rule.rule_id];
 
                 // Show enabled default rules that was modified by the user
                 if (ruleDescription && rule.enabled && !rule.default) {
@@ -596,8 +593,7 @@ module.exports = React.createClass({
             }
         });
 
-        var pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
-            //console.log("resolving pushersPromise");
+        const pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
             self.setState({pushers: resp.pushers});
         });
 
@@ -623,7 +619,7 @@ module.exports = React.createClass({
     },
 
     _updatePushRuleActions: function(rule, actions, enabled) {
-        var cli = MatrixClientPeg.get();
+        const cli = MatrixClientPeg.get();
 
         return cli.setPushRuleActions(
             'global', rule.kind, rule.rule_id, actions
@@ -669,9 +665,9 @@ module.exports = React.createClass({
     },
 
     renderNotifRulesTableRows: function() {
-        var rows = [];
-        for (var i in this.state.vectorPushRules) {
-            var rule = this.state.vectorPushRules[i];
+        const rows = [];
+        for (let i in this.state.vectorPushRules) {
+            const rule = this.state.vectorPushRules[i];
             //console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState);
             rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
         }
@@ -697,30 +693,32 @@ module.exports = React.createClass({
     },
 
     render: function() {
-        var self = this;
-
-        var spinner;
+        const self = this;
+        
+        let spinner;
         if (this.state.phase === this.phases.LOADING) {
-            var Loader = sdk.getComponent("elements.Spinner");
+            const Loader = sdk.getComponent("elements.Spinner");
             spinner = <Loader />;
         }
-
+        
+        let masterPushRuleDiv;
         if (this.state.masterPushRule) {
-            var masterPushRuleDiv = (
+            masterPushRuleDiv = (
                 <div className="mx_UserNotifSettings_tableRow">
-                        <div className="mx_UserNotifSettings_inputCell">
-                            <input id="enableNotifications"
-                                ref="enableNotifications"
-                                type="checkbox"
-                                checked={ !this.state.masterPushRule.enabled }
-                                onChange={ this.onEnableNotificationsChange } />
-                        </div>
-                        <div className="mx_UserNotifSettings_labelCell">
-                            <label htmlFor="enableNotifications">
-                                { _t('Enable notifications for this account') }
-                            </label>
-                        </div>
+                    <div className="mx_UserNotifSettings_inputCell">
+                        <input id="enableNotifications"
+                            ref="enableNotifications"
+                            type="checkbox"
+                            checked={ !this.state.masterPushRule.enabled }
+                            onChange={ this.onEnableNotificationsChange } 
+                        />
                     </div>
+                    <div className="mx_UserNotifSettings_labelCell">
+                        <label htmlFor="enableNotifications">
+                            { _t('Enable notifications for this account') }
+                        </label>
+                    </div>
+                </div>
             );
         }
 
@@ -748,29 +746,29 @@ module.exports = React.createClass({
             // This only supports the first email address in your profile for now
             emailNotificationsRow = this.emailNotificationsRow(
                 emailThreepids[0].address,
-                _t('Enable email notifications') + ' (' + emailThreepids[0].address + ')'
+                `${_t('Enable email notifications')} (${emailThreepids[0].address})`
             );
         }
 
         // Build external push rules
-        var externalRules = [];
-        for (var i in this.state.externalPushRules) {
-            var rule = this.state.externalPushRules[i];
+        const externalRules = [];
+        for (let i in this.state.externalPushRules) {
+            const rule = this.state.externalPushRules[i];
             externalRules.push(<li>{ _t(rule.description) }</li>);
         }
 
         // Show keywords not displayed by the vector UI as a single external push rule
-        var externalKeyWords = [];
-        for (var i in this.state.externalContentRules) {
-            var rule = this.state.externalContentRules[i];
-            externalKeyWords.push(rule.pattern);
+        let externalKeywords = [];
+        for (let i in this.state.externalContentRules) {
+            const rule = this.state.externalContentRules[i];
+            externalKeywords.push(rule.pattern);
         }
-        if (externalKeyWords.length) {
-            externalKeyWords = externalKeyWords.join(", ");
-            externalRules.push(<li>{ _t('Notifications on the following keywords follow rules which can’t be displayed here:') } { externalKeyWords }</li>);
+        if (externalKeywords.length) {
+            externalKeywords = externalKeywords.join(", ");
+            externalRules.push(<li>{ _t('Notifications on the following keywords follow rules which can’t be displayed here:') } { externalKeywords }</li>);
         }
 
-        var devicesSection;
+        let devicesSection;
         if (this.state.pushers === undefined) {
             devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>
         } else if (this.state.pushers.length == 0) {
@@ -778,8 +776,8 @@ module.exports = React.createClass({
         } else {
             // TODO: It would be great to be able to delete pushers from here too,
             // and this wouldn't be hard to add.
-            var rows = [];
-            for (var i = 0; i < this.state.pushers.length; ++i) {
+            const rows = [];
+            for (let i = 0; i < this.state.pushers.length; ++i) {
                 rows.push(<tr key={ i }>
                     <td>{this.state.pushers[i].app_display_name}</td>
                     <td>{this.state.pushers[i].device_display_name}</td>
@@ -798,7 +796,7 @@ module.exports = React.createClass({
             </div>);
         }
 
-        var advancedSettings;
+        let advancedSettings;
         if (externalRules.length) {
             advancedSettings = (
                 <div>
diff --git a/src/skins/vector/css/_common.scss b/src/skins/vector/css/_common.scss
index 876c5b97..c1eb8fab 100644
--- a/src/skins/vector/css/_common.scss
+++ b/src/skins/vector/css/_common.scss
@@ -82,6 +82,11 @@ textarea {
     transition: opacity 0.2s ease-in-out;
 }
 
+.mx_fadable.mx_fadable_faded {
+    opacity: 0.3;
+    pointer-events: none;
+}
+
 /* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
    Stop the scrollbar view from pushing out the container's overall sizing, which causes
    flexbox to adapt to the new size and cause the view to keep growing.
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 35cb5acc..1a92fc10 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -33,10 +33,12 @@ limitations under the License.
     min-height: 70px;
     align-items: center;
     display: flex;
+    padding-bottom: 10px;
 }
 
 .mx_GroupView_header_view {
     border-bottom: 1px solid $primary-hairline-color;
+    padding-bottom: 0px;
 }
 
 .mx_GroupView_header_avatar, .mx_GroupView_header_info {
@@ -159,8 +161,12 @@ limitations under the License.
     user-select: none;
 }
 
-.mx_GroupView_rooms_header h3 {
-    margin-bottom: 10px
+.mx_GroupView h3 {
+    text-transform: uppercase;
+    color: $h3-color;
+    font-weight: 600;
+    font-size: 13px;
+    margin-bottom: 10px;
 }
 
 .mx_GroupView_rooms_header .mx_AccessibleButton {
@@ -169,15 +175,24 @@ limitations under the License.
     height: 24px;
 }
 
-.mx_GroupView_rooms_header_addButton {
-    display: inline-block;
+.mx_GroupView_group {
+    border-top: 1px solid $primary-hairline-color;
 }
 
-.mx_GroupView_rooms_header_addButton object {
+.mx_GroupView_group_disabled {
+    opacity: 0.3;
     pointer-events: none;
 }
 
-.mx_GroupView_rooms_header_addButton_label {
+.mx_GroupView_rooms_header_addRow_button {
+    display: inline-block;
+}
+
+.mx_GroupView_rooms_header_addRow_button object {
+    pointer-events: none;
+}
+
+.mx_GroupView_rooms_header_addRow_label {
     display: inline-block;
     vertical-align: top;
     line-height: 24px;
@@ -222,14 +237,6 @@ limitations under the License.
     user-select: none;
 }
 
-.mx_GroupView_memberSettings h3 {
-    text-transform: uppercase;
-    color: $h3-color;
-    font-weight: 600;
-    font-size: 13px;
-    margin-bottom: 10px;
-}
-
 .mx_GroupView_memberSettings input {
     margin-right: 6px;
 }
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 faed5b8b..7944d01d 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
@@ -68,7 +68,7 @@ limitations under the License.
     min-width: 0px;
     max-width: 960px;
     width: 100%;
-    margin: auto;
+    margin: 0px auto;
 
     overflow: auto;
     border-bottom: 1px solid $primary-hairline-color;
@@ -80,17 +80,32 @@ limitations under the License.
     max-width: 1920px ! important;
 }
 
-.mx_RoomView_topUnreadMessagesBar {
+
+.mx_RoomView_body {
+    order: 3;
+    flex: 1 1 0;
+    flex-direction: column;
+    display: flex;
+}
+
+.mx_RoomView_body .mx_RoomView_topUnreadMessagesBar {
+    order: 1;
+}
+
+.mx_RoomView_body .mx_RoomView_messagePanel {
+    order: 2;
+}
+
+.mx_RoomView_body .mx_RoomView_statusArea {
     order: 3;
 }
 
-.mx_RoomView_messagePanel {
+.mx_RoomView_body .mx_MessageComposer {
     order: 4;
+}
 
-    flex: 1 1 0;
-
+.mx_RoomView_messagePanel {
     width: 100%;
-
     overflow-y: auto;
 }
 
@@ -131,18 +146,6 @@ limitations under the License.
     clear: both;
 }
 
-.mx_RoomView_invitePrompt {
-    order: 2;
-
-    min-width: 0px;
-    max-width: 960px;
-    width: 100%;
-    margin: auto;
-
-    margin-top: 12px;
-    margin-bottom: 12px;
-}
-
 li.mx_RoomView_myReadMarker_container {
     height: 0px;
     margin: 0px;
@@ -160,8 +163,6 @@ hr.mx_RoomView_myReadMarker {
 }
 
 .mx_RoomView_statusArea {
-    order: 5;
-
     width: 100%;
     flex: 0 0 auto;
 
@@ -236,8 +237,6 @@ hr.mx_RoomView_myReadMarker {
 }
 
 .mx_RoomView .mx_MessageComposer {
-    order: 6;
-
     width: 100%;
     flex: 0 0 auto;
     margin-right: 2px;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
index 8f23ef6b..feb60f47 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
@@ -42,4 +42,8 @@ limitations under the License.
 .mx_VideoView_localVideoFeed video {
     width: auto;
     height: 100%;
-}
\ No newline at end of file
+}
+
+.mx_VideoView_localVideoFeed.mx_VideoView_localVideoFeed_flipped video {
+    transform: scale(-1, 1);
+}
diff --git a/src/skins/vector/css/vector-web/structures/_RightPanel.scss b/src/skins/vector/css/vector-web/structures/_RightPanel.scss
index 91caed7b..85057410 100644
--- a/src/skins/vector/css/vector-web/structures/_RightPanel.scss
+++ b/src/skins/vector/css/vector-web/structures/_RightPanel.scss
@@ -94,30 +94,26 @@ limitations under the License.
 }
 
 .mx_RightPanel_footer .mx_RightPanel_invite {
-    line-height: 35px;
     font-size: 14px;
 	color: $primary-fg-color;
     padding-top: 13px;
     padding-left: 5px;
     cursor: pointer;
+    display: flex;
+    align-items: center;
 }
 
 .collapsed .mx_RightPanel_footer .mx_RightPanel_invite {
     display: none;
 }
 
-.mx_RightPanel_invite .mx_RightPanel_icon {
-    display: inline-block;
-}
-
 .mx_RightPanel_invite .mx_RightPanel_icon object {
     pointer-events: none;
 }
 
 .mx_RightPanel_invite .mx_RightPanel_message {
-    display: inline-block;
-    vertical-align: top;
-    padding-left: 10px
+    padding-left: 10px;
+    line-height: 18px;
 }
 
 .mx_MatrixChat_useCompactLayout {
diff --git a/src/skins/vector/img/icon-delete-pink.svg b/src/skins/vector/img/icon-delete-pink.svg
new file mode 100644
index 00000000..9d9907d8
--- /dev/null
+++ b/src/skins/vector/img/icon-delete-pink.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<path fill="#FF0064" d="M85.633,454.889c0,31.168,25.553,56.661,56.79,56.661h227.156c31.234,0,56.787-25.493,56.787-56.661
+		V128.225H85.633V454.889z M468.958,43.042H362.479L326.828,0.45H185.173l-35.652,42.591H43.042v42.592h425.916V43.042z"/>
+</g>
+</svg>