diff --git a/.flowconfig b/.flowconfig
new file mode 100644
index 0000000000..81770c6585
--- /dev/null
+++ b/.flowconfig
@@ -0,0 +1,6 @@
+[include]
+src/**/*.js
+test/**/*.js
+
+[ignore]
+node_modules/
diff --git a/src/autocomplete/FuzzyMatcher.js b/src/autocomplete/FuzzyMatcher.js
index bd19fc53e8..c22e2a1101 100644
--- a/src/autocomplete/FuzzyMatcher.js
+++ b/src/autocomplete/FuzzyMatcher.js
@@ -14,7 +14,12 @@ class KeyMap {
 const DEFAULT_RESULT_COUNT = 10;
 const DEFAULT_DISTANCE = 5;
 
-export default class FuzzyMatcher {
+// FIXME Until Fuzzy matching works better, we use prefix matching.
+
+import PrefixMatcher from './QueryMatcher';
+export default PrefixMatcher;
+
+class FuzzyMatcher {
     /**
      * Given an array of objects and keys, returns a KeyMap
      * Keys can refer to object properties by name and as in JavaScript (for nested properties)
diff --git a/src/autocomplete/QueryMatcher.js b/src/autocomplete/QueryMatcher.js
new file mode 100644
index 0000000000..b4c27a7179
--- /dev/null
+++ b/src/autocomplete/QueryMatcher.js
@@ -0,0 +1,62 @@
+//@flow
+
+import _at from 'lodash/at';
+import _flatMap from 'lodash/flatMap';
+import _sortBy from 'lodash/sortBy';
+import _sortedUniq from 'lodash/sortedUniq';
+import _keys from 'lodash/keys';
+
+class KeyMap {
+    keys: Array<String>;
+    objectMap: {[String]: Array<Object>};
+    priorityMap = new Map();
+}
+
+export default class QueryMatcher {
+    /**
+     * Given an array of objects and keys, returns a KeyMap
+     * Keys can refer to object properties by name and as in JavaScript (for nested properties)
+     *
+     * To use, simply presort objects by required criteria, run through this function and create a QueryMatcher with the
+     * resulting KeyMap.
+     *
+     * TODO: Handle arrays and objects (Fuse did this, RoomProvider uses it)
+     */
+    static valuesToKeyMap(objects: Array<Object>, keys: Array<String>): KeyMap {
+        const keyMap = new KeyMap();
+        const map = {};
+
+        objects.forEach((object, i) => {
+            const keyValues = _at(object, keys);
+            for (const keyValue of keyValues) {
+                if (!map.hasOwnProperty(keyValue)) {
+                    map[keyValue] = [];
+                }
+                map[keyValue].push(object);
+            }
+            keyMap.priorityMap.set(object, i);
+        });
+
+        keyMap.objectMap = map;
+        keyMap.keys = _keys(map);
+        return keyMap;
+    }
+
+    constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
+        this.options = options;
+        this.keys = options.keys;
+        this.setObjects(objects);
+    }
+
+    setObjects(objects: Array<Object>) {
+        this.keyMap = QueryMatcher.valuesToKeyMap(objects, this.keys);
+    }
+
+    match(query: String): Array<Object> {
+        query = query.toLowerCase().replace(/[^\w]/g, '');
+        const results = _sortedUniq(_sortBy(_flatMap(this.keyMap.keys, (key) => {
+            return key.toLowerCase().replace(/[^\w]/g, '').indexOf(query) >= 0 ? this.keyMap.objectMap[key] : [];
+        }), (candidate) => this.keyMap.priorityMap.get(candidate)));
+        return results;
+    }
+}
diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js
index b65439181c..589dfec9fa 100644
--- a/src/autocomplete/UserProvider.js
+++ b/src/autocomplete/UserProvider.js
@@ -1,20 +1,27 @@
+//@flow
 import React from 'react';
 import AutocompleteProvider from './AutocompleteProvider';
 import Q from 'q';
 import {PillCompletion} from './Components';
 import sdk from '../index';
 import FuzzyMatcher from './FuzzyMatcher';
+import _pull from 'lodash/pull';
+import _sortBy from 'lodash/sortBy';
+import MatrixClientPeg from '../MatrixClientPeg';
+
+import type {Room, RoomMember} from 'matrix-js-sdk';
 
 const USER_REGEX = /@\S*/g;
 
 let instance = null;
 
 export default class UserProvider extends AutocompleteProvider {
+    users: Array<RoomMember> = [];
+
     constructor() {
         super(USER_REGEX, {
             keys: ['name', 'userId'],
         });
-        this.users = [];
         this.matcher = new FuzzyMatcher([], {
             keys: ['name', 'userId'],
         });
@@ -53,8 +60,30 @@ export default class UserProvider extends AutocompleteProvider {
         return '👥 Users';
     }
 
-    setUserList(users) {
-        this.users = users;
+    setUserListFromRoom(room: Room) {
+        const events = room.getLiveTimeline().getEvents();
+        const lastSpoken = {};
+
+        for(const event of events) {
+            lastSpoken[event.getSender()] = event.getTs();
+        }
+
+        const currentUserId = MatrixClientPeg.get().credentials.userId;
+        this.users = room.getJoinedMembers().filter((member) => {
+            if (member.userId !== currentUserId) return true;
+        });
+
+        this.users = _sortBy(this.users, (user) => 1E20 - lastSpoken[user.userId] || 1E20);
+
+        this.matcher.setObjects(this.users);
+    }
+
+    onUserSpoke(user: RoomMember) {
+        if(user.userId === MatrixClientPeg.get().credentials.userId) return;
+
+        // Probably unsafe to compare by reference here?
+        _pull(this.users, user);
+        this.users.splice(0, 0, user);
         this.matcher.setObjects(this.users);
     }
 
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 696d15f84a..936d88c0ee 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -225,7 +225,7 @@ module.exports = React.createClass({
                 MatrixClientPeg.get().credentials.userId, 'join'
             );
 
-            this._updateAutoComplete();
+            UserProvider.getInstance().setUserListFromRoom(this.state.room);
             this.tabComplete.loadEntries(this.state.room);
         }
 
@@ -479,8 +479,7 @@ module.exports = React.createClass({
         // and that has probably just changed
         if (ev.sender) {
             this.tabComplete.onMemberSpoke(ev.sender);
-            // nb. we don't need to update the new autocomplete here since
-            // its results are currently ordered purely by search score.
+            UserProvider.getInstance().onUserSpoke(ev.sender);
         }
     },
 
@@ -658,7 +657,7 @@ module.exports = React.createClass({
 
         // refresh the tab complete list
         this.tabComplete.loadEntries(this.state.room);
-        this._updateAutoComplete();
+        UserProvider.getInstance().setUserListFromRoom(this.state.room);
 
         // if we are now a member of the room, where we were not before, that
         // means we have finished joining a room we were previously peeking
@@ -1437,14 +1436,6 @@ module.exports = React.createClass({
         }
     },
 
-    _updateAutoComplete: function() {
-        const myUserId = MatrixClientPeg.get().credentials.userId;
-        const members = this.state.room.getJoinedMembers().filter(function(member) {
-            if (member.userId !== myUserId) return true;
-        });
-        UserProvider.getInstance().setUserList(members);
-    },
-
     render: function() {
         var RoomHeader = sdk.getComponent('rooms.RoomHeader');
         var MessageComposer = sdk.getComponent('rooms.MessageComposer');