diff --git a/skins/base/img/edit.png b/skins/base/img/edit.png
new file mode 100644
index 00000000..2686885f
Binary files /dev/null and b/skins/base/img/edit.png differ
diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js
index 6e3cb859..7c66616b 100644
--- a/skins/base/views/molecules/MemberTile.js
+++ b/skins/base/views/molecules/MemberTile.js
@@ -34,11 +34,6 @@ module.exports = React.createClass({
     displayName: 'MemberTile',
     mixins: [MemberTileController],
 
-    // XXX: should these be in the controller?
-    getInitialState: function() {
-        return { 'hover': false };
-    },
-
     mouseEnter: function(e) {
         this.setState({ 'hover': true });
     },
@@ -47,6 +42,11 @@ module.exports = React.createClass({
         this.setState({ 'hover': false });
     },
 
+    onClick: function(e) {
+        this.setState({ 'menu': true });
+        this.setState(this._calculateOpsPermissions());        
+    },
+
     getDuration: function(time) {
         if (!time) return;
         var t = parseInt(time / 1000);
@@ -78,6 +78,14 @@ module.exports = React.createClass({
         return "Unknown";
     },
 
+    getPowerLabel: function() {
+        var label = this.props.member.userId;
+        if (this.state.isTargetMod) {
+            label += " - Mod (" + this.props.member.powerLevelNorm + "%)";
+        }
+        return label;
+    },
+
     render: function() {
         var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId;
 
@@ -102,7 +110,7 @@ module.exports = React.createClass({
         }
 
         var name = this.props.member.name;
-        if (isMyUser) name += " (me)";
+        // if (isMyUser) name += " (me)"; // this does nothing other than introduce line wrapping and pain
         var leave = isMyUser ? <img className="mx_MemberTile_leave" src="img/delete.png" width="10" height="10" onClick={this.onLeaveClick}/> : null;
 
         var nameClass = "mx_MemberTile_name";
@@ -110,6 +118,41 @@ module.exports = React.createClass({
             nameClass += " mx_MemberTile_zalgo";
         }
 
+        var menu;
+        if (this.state.menu) {
+            var kickButton, banButton, muteButton, giveModButton;
+            if (this.state.can.kick) {
+                kickButton = <div className="mx_MemberTile_menuItem" onClick={this.onKick}>
+                    Kick
+                </div>;
+            }
+            if (this.state.can.ban) {
+                banButton = <div className="mx_MemberTile_menuItem" onClick={this.onBan}>
+                    Ban
+                </div>;
+            }
+            if (this.state.can.mute) {
+                var muteLabel = this.state.muted ? "Unmute" : "Mute";
+                muteButton = <div className="mx_MemberTile_menuItem" onClick={this.onMuteToggle}>
+                    {muteLabel}
+                </div>;
+            }
+            if (this.state.can.modifyLevel) {
+                var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
+                giveModButton = <div className="mx_MemberTile_menuItem" onClick={this.onModToggle}>
+                    {giveOpLabel}
+                </div>
+            }
+            menu = <div className="mx_MemberTile_menu">
+                        <img className="mx_MemberTile_chevron" src="img/chevron-right.png" width="9" height="16" />
+                        <div className="mx_MemberTile_menuItem" onClick={this.onChatClick}>Chat</div>
+                        {muteButton}
+                        {kickButton}
+                        {banButton}
+                        {giveModButton}
+                   </div>;
+        }
+
         var nameEl;
         if (this.state.hover) {
             var presence;
@@ -122,11 +165,10 @@ module.exports = React.createClass({
                 presence = <div className="mx_MemberTile_presence">{ this.getPrettyPresence(this.props.member.user) }</div>;
             }
 
-            // <MemberInfo member={this.props.member} />
             nameEl =
                 <div className="mx_MemberTile_details">
                     { leave }
-                    <div className="mx_MemberTile_userId" title={ this.props.member.userId }>{ this.props.member.userId }</div>
+                    <div className="mx_MemberTile_userId">{ this.props.member.userId }</div>
                     { presence }
                 </div>
         }
@@ -138,7 +180,8 @@ module.exports = React.createClass({
         }
 
         return (
-            <div className={mainClassName} onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
+            <div className={mainClassName} title={ this.getPowerLabel } onClick={ this.onClick } onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }>
+                { menu }
                 <div className="mx_MemberTile_avatar">
                     <MemberAvatar member={this.props.member} />
                      { power }
diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js
index b6bfedf1..eff25d96 100644
--- a/skins/base/views/organisms/RoomView.js
+++ b/skins/base/views/organisms/RoomView.js
@@ -72,7 +72,7 @@ module.exports = React.createClass({
         if (!this.state.numUnreadMessages) {
             return "";
         }
-        return this.state.numUnreadMessages + " new messages";
+        return this.state.numUnreadMessages + " new message" + (this.state.numUnreadMessages > 1 ? "s" : "");
     },
 
     scrollToBottom: function() {
diff --git a/src/controllers/molecules/MemberTile.js b/src/controllers/molecules/MemberTile.js
index ae468284..19c08731 100644
--- a/src/controllers/molecules/MemberTile.js
+++ b/src/controllers/molecules/MemberTile.js
@@ -25,13 +25,169 @@ var Loader = require("react-loader");
 var MatrixClientPeg = require("../../MatrixClientPeg");
 
 module.exports = {
-    onClick: function() {
-        dis.dispatch({
-            action: 'view_user',
-            user_id: this.props.member.userId
+    // onClick: function() {
+    //     dis.dispatch({
+    //         action: 'view_user',
+    //         user_id: this.props.member.userId
+    //     });
+    // },
+
+    onKick: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        MatrixClientPeg.get().kick(roomId, target).done(function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Kick success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Kick error",
+                description: err.message
+            });
         });
     },
 
+    onBan: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        MatrixClientPeg.get().ban(roomId, target).done(function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Ban success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Ban error",
+                description: err.message
+            });
+        });
+    },
+
+    onMuteToggle: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        var room = MatrixClientPeg.get().getRoom(roomId);
+        if (!room) {
+            return;
+        }
+        var powerLevelEvent = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevelEvent) {
+            return;
+        }
+        var isMuted = this.state.muted;
+        var powerLevels = powerLevelEvent.getContent();
+        var levelToSend = (
+            (powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
+            powerLevels.events_default
+        );
+        var level;
+        if (isMuted) { // unmute
+            level = levelToSend;
+        }
+        else { // mute
+            level = levelToSend - 1;
+        }
+
+        MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done(
+        function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Mute toggle success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Mute error",
+                description: err.message
+            });
+        });
+    },
+
+    onModToggle: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var room = MatrixClientPeg.get().getRoom(roomId);
+        if (!room) {
+            return;
+        }
+        var powerLevelEvent = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevelEvent) {
+            return;
+        }
+        var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+        if (!me) {
+            return;
+        }
+        var defaultLevel = powerLevelEvent.getContent().users_default;
+        var modLevel = me.powerLevel - 1;
+        // toggle the level
+        var newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
+        MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done(
+        function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Mod toggle success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Mod error",
+                description: err.message
+            });
+        });
+    },
+
+    onChatClick: function() {
+        // check if there are any existing rooms with just us and them (1:1)
+        // If so, just view that room. If not, create a private room with them.
+        var rooms = MatrixClientPeg.get().getRooms();
+        var userIds = [
+            this.props.member.userId,
+            MatrixClientPeg.get().credentials.userId
+        ];
+        var existingRoomId = null;
+        for (var i = 0; i < rooms.length; i++) {
+            var members = rooms[i].getJoinedMembers();
+            if (members.length === 2) {
+                var hasTargetUsers = true;
+                for (var j = 0; j < members.length; j++) {
+                    if (userIds.indexOf(members[j].userId) === -1) {
+                        hasTargetUsers = false;
+                        break;
+                    }
+                }
+                if (hasTargetUsers) {
+                    existingRoomId = rooms[i].roomId;
+                    break;
+                }
+            }
+        }
+
+        if (existingRoomId) {
+            dis.dispatch({
+                action: 'view_room',
+                room_id: existingRoomId
+            });
+        }
+        else {
+            MatrixClientPeg.get().createRoom({
+                invite: [this.props.member.userId],
+                preset: "private_chat"
+            }).done(function(res) {
+                dis.dispatch({
+                    action: 'view_room',
+                    room_id: res.room_id
+                });
+            }, function(err) {
+                console.error(
+                    "Failed to create room: %s", JSON.stringify(err)
+                );
+            });
+        }
+    },
+
     onLeaveClick: function() {
         var roomId = this.props.member.roomId;
         Modal.createDialog(QuestionDialog, {
@@ -56,5 +212,84 @@ module.exports = {
                 }
             }
         });
-    }
+    },
+
+    getInitialState: function() {
+        return {
+            hover: false,
+            menu: false,
+
+            // presence: "offline",
+            // active: -1,
+            can: {
+                kick: false,
+                ban: false,
+                mute: false,
+                modifyLevel: false
+            },
+            muted: false,
+            isTargetMod: false,
+        }
+    },
+
+    _calculateOpsPermissions: function() {
+        var defaultPerms = {
+            can: {},
+            muted: false,
+            modifyLevel: false
+        };
+        var room = MatrixClientPeg.get().getRoom(this.props.member.roomId);
+        if (!room) {
+            return defaultPerms;
+        }
+        var powerLevels = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevels) {
+            return defaultPerms;
+        }
+        var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+        var them = this.props.member;
+        return {
+            can: this._calculateCanPermissions(
+                me, them, powerLevels.getContent()
+            ),
+            muted: this._isMuted(them, powerLevels.getContent()),
+            isTargetMod: them.powerLevel > powerLevels.getContent().users_default
+        };
+    },
+
+    _calculateCanPermissions: function(me, them, powerLevels) {
+        var can = {
+            kick: false,
+            ban: false,
+            mute: false,
+            modifyLevel: false
+        };
+        var canAffectUser = them.powerLevel < me.powerLevel;
+        if (!canAffectUser) {
+            //console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
+            return can;
+        }
+        var editPowerLevel = (
+            (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
+            powerLevels.state_default
+        );
+        can.kick = me.powerLevel >= powerLevels.kick;
+        can.ban = me.powerLevel >= powerLevels.ban;
+        can.mute = me.powerLevel >= editPowerLevel;
+        can.modifyLevel = me.powerLevel > them.powerLevel;
+        return can;
+    },
+
+    _isMuted: function(member, powerLevelContent) {
+        if (!powerLevelContent || !member) {
+            return false;
+        }
+        var levelToSend = (
+            (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
+            powerLevelContent.events_default
+        );
+        return member.powerLevel < levelToSend;
+    },
 };
diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js
index 912b142a..3eef007e 100644
--- a/src/controllers/organisms/MemberList.js
+++ b/src/controllers/organisms/MemberList.js
@@ -61,7 +61,9 @@ module.exports = {
         function updateUserState(event, user) {
             var tile = self.refs[user.userId];
             if (tile) {
-                tile.forceUpdate();
+                // update the whole list to get the order right, not just this cell...
+                self.forceUpdate();
+                // tile.forceUpdate();
             }
         }
         MatrixClientPeg.get().on("User.presence", updateUserState);