diff --git a/CHANGES.rst b/CHANGES.rst
index 4e536bc4de3914f42e63df4dfb7d94372dc2fccd..edf32db752c31b660ddbb9223a2d7dc3ce355020 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,12 @@
+Changes in synapse 0.3.1 (2014-09-18)
+=====================================
+This is a release to hotfix v0.3.0 to fix two regressions.
+
+Webclient:
+ * Fix a regression where we sometimes displayed duplicate events.
+ * Fix a regression where we didn't immediately remove rooms you were
+   banned in from the recents list.
+
 Changes in synapse 0.3.0 (2014-09-18)
 =====================================
 See UPGRADE for information about changes to the client server API, including
diff --git a/VERSION b/VERSION
index 0d91a54c7d439e84e3dd17d3594f1b2b6737f430..9e11b32fcaa96816319e5d0dcff9fb2873f04061 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.0
+0.3.1
diff --git a/synapse/__init__.py b/synapse/__init__.py
index 8ef176ea6f743d9639d2e0cc863e7b5dd51ea313..1b49cbb38e3ec12db00e0e7a81fa9b19c8fcde8e 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -16,4 +16,4 @@
 """ This is a reference implementation of a synapse home server.
 """
 
-__version__ = "0.3.0"
+__version__ = "0.3.1"
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
index ad69d297fae90a0dc86590f160e3e9fee63b9cbb..321054f90429efa70dab72479aae17e6a36645cc 100644
--- a/webclient/components/matrix/event-handler-service.js
+++ b/webclient/components/matrix/event-handler-service.js
@@ -232,20 +232,12 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
     };
     
     var handleRoomMember = function(event, isLiveEvent, isStateEvent) {
-        // if the server is stupidly re-relaying a no-op join, discard it.
-        if (event.prev_content && 
-            event.content.membership === "join" &&
-            event.content.membership === event.prev_content.membership)
-        {
-            return;
-        }
         
         // add membership changes as if they were a room message if something interesting changed
         // Exception: Do not do this if the event is a room state event because such events already come
         // as room messages events. Moreover, when they come as room messages events, they are relatively ordered
-        // with other other room messages XXX This is no longer true, you only get a single event, not a room message event.
-        // FIXME: This possibly reintroduces multiple join messages.
-        if (event.content.prev !== event.content.membership) { // && !isStateEvent
+        // with other other room messages
+        if (event.content.prev !== event.content.membership && !isStateEvent) {
             if (isLiveEvent) {
                 $rootScope.events.rooms[event.room_id].messages.push(event);
             }
@@ -376,7 +368,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
                         handleMessage(event, isLiveEvent);
                         break;
                     case "m.room.member":
-                        isStateEvent = true;
                         handleRoomMember(event, isLiveEvent, isStateEvent);
                         break;
                     case "m.presence":
@@ -406,8 +397,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
         // isLiveEvents determines whether notifications should be shown, whether
         // messages get appended to the start/end of lists, etc.
         handleEvents: function(events, isLiveEvents, isStateEvents) {
-            // XXX FIXME TODO: isStateEvents is being left as undefined sometimes. It makes no sense
-            // to have isStateEvents as an arg, since things like m.room.member are ALWAYS state events.
             for (var i=0; i<events.length; i++) {
                 this.handleEvent(events[i], isLiveEvents, isStateEvents);
             }
@@ -423,7 +412,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
             if (dir && 'b' === dir) {
                 // paginateBackMessages requests messages to be in reverse chronological order
                 for (var i=0; i<events.length; i++) {
-                    // FIXME: Being live != being state
                     this.handleEvent(events[i], isLiveEvents, isLiveEvents);
                 }
                 
@@ -433,7 +421,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
             else {
                 // InitialSync returns messages in chronological order
                 for (var i=events.length - 1; i>=0; i--) {
-                    // FIXME: Being live != being state
                     this.handleEvent(events[i], isLiveEvents, isLiveEvents);
                 }
                 // Store where to start pagination
diff --git a/webclient/recents/recents-filter.js b/webclient/recents/recents-filter.js
index d948205e191a6c7064368a5b95f764fa9a610caf..e8323f4a315190271aabee6a60c4601e9caa85d6 100644
--- a/webclient/recents/recents-filter.js
+++ b/webclient/recents/recents-filter.js
@@ -19,14 +19,13 @@
 angular.module('RecentsController')
 .filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) {
     return function(rooms) {
-
         var user_id = matrixService.config().user_id;
 
         // Transform the dict into an array
         // The key, room_id, is already in value objects
         var filtered = [];
         angular.forEach(rooms, function(room, room_id) {
-
+            
             // Show the room only if the user has joined it or has been invited
             // (ie, do not show it if he has been banned)
             var member = eventHandlerService.getMember(room_id, user_id);
@@ -35,8 +34,9 @@ angular.module('RecentsController')
                 // Count users here
                 // TODO: Compute it directly in eventHandlerService
                 room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
+
+                filtered.push(room);
             }
-            filtered.push(room);
         });
 
         // And time sort them
@@ -60,4 +60,4 @@ angular.module('RecentsController')
         });
         return filtered;
     };
-}]);
+}]);
\ No newline at end of file