diff --git a/background.html b/background.html
index a843df384..fcbb5b208 100644
--- a/background.html
+++ b/background.html
@@ -768,6 +768,7 @@
   <script type='text/javascript' src='js/views/identity_key_send_error_view.js'></script>
   <script type='text/javascript' src='js/views/migration_view.js'></script>
   <script type="text/javascript" src="js/views/phone-input-view.js"></script>
+  <script type='text/javascript' src='js/views/app_view.js'></script>
 
   <script type='text/javascript' src='js/wall_clock_listener.js'></script>
   <script type='text/javascript' src='js/rotate_signed_prekey_listener.js'></script>
diff --git a/js/background.js b/js/background.js
index fdf5ccccf..0303b21b7 100644
--- a/js/background.js
+++ b/js/background.js
@@ -58,18 +58,11 @@
         return accountManager;
     };
 
-
     storage.fetch();
     storage.onready(function() {
         ConversationController.load();
 
         window.dispatchEvent(new Event('storage_ready'));
-        setUnreadCount(storage.get("unreadCount", 0));
-
-        if (Whisper.Registration.isDone()) {
-            extension.keepAwake();
-            init();
-        }
 
         console.log("listening for registration events");
         Whisper.events.on('registration_done', function() {
@@ -78,8 +71,12 @@
             init(true);
         });
 
+        var appView = window.owsDesktopApp.appView = new Whisper.AppView({el: $('body'), events: Whisper.events});
+
         if (open) {
-            openInbox();
+            openInbox({
+                initialLoadComplete: initialLoadComplete
+            });
         }
 
         Whisper.WallClockListener.init(Whisper.events);
@@ -87,11 +84,21 @@
         Whisper.ExpiringMessagesListener.init(Whisper.events);
 
         if (Whisper.Registration.everDone()) {
-            openInbox();
-        }
-        if (!Whisper.Registration.isDone()) {
-            extension.install();
+            init();
+            appView.openInbox({
+                initialLoadComplete: initialLoadComplete
+            });
+        } else {
+            appView.openInstaller();
         }
+
+        Whisper.events.on('unauthorized', function() {
+            appView.inboxView.networkStatusView.update();
+        });
+        Whisper.events.on('reconnectTimer', function() {
+            appView.inboxView.networkStatusView.setSocketReconnectInterval(60000);
+        });
+
     });
 
     window.getSyncRequest = function() {
@@ -111,6 +118,7 @@
 
     function init(firstRun) {
         window.removeEventListener('online', init);
+
         if (!Whisper.Registration.isDone()) { return; }
         if (Whisper.Migration.inProgress()) { return; }
 
@@ -173,7 +181,7 @@
         initialLoadComplete = true;
 
         var interval = setInterval(function() {
-            var view = window.owsDesktopApp.inboxView;
+            var view = window.owsDesktopApp.appView;
             if (view) {
                 clearInterval(interval);
                 interval = null;
@@ -184,7 +192,7 @@
     function onProgress(ev) {
         var count = ev.count;
 
-        var view = window.owsDesktopApp.inboxView;
+        var view = window.owsDesktopApp.appView;
         if (view) {
             view.onProgress(count);
         }
@@ -373,7 +381,6 @@
         if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
             Whisper.Registration.remove();
             Whisper.events.trigger('unauthorized');
-            extension.install();
             return;
         }
 
@@ -523,50 +530,4 @@
         // Calling this directly so we can wait for completion
         return Whisper.DeliveryReceipts.onReceipt(receipt);
     }
-
-    window.owsDesktopApp = {
-        getAppView: function(destWindow) {
-            var self = this;
-
-            return ConversationController.loadPromise().then(function() {
-                try {
-                    if (self.inboxView) { self.inboxView.remove(); }
-                    self.inboxView = new Whisper.InboxView({
-                        model: self,
-                        window: destWindow,
-                        initialLoadComplete: initialLoadComplete
-                    });
-                    self.openConversation(getOpenConversation());
-
-                    return self.inboxView;
-
-                } catch (e) {
-                    console.log(e);
-                }
-            });
-        },
-        openConversation: function(conversation) {
-            if (this.inboxView && conversation) {
-                this.inboxView.openConversation(null, conversation);
-            }
-        }
-    };
-
-    Whisper.events.on('unauthorized', function() {
-        if (owsDesktopApp.inboxView) {
-            owsDesktopApp.inboxView.networkStatusView.update();
-        }
-    });
-    Whisper.events.on('reconnectTimer', function() {
-        if (owsDesktopApp.inboxView) {
-            owsDesktopApp.inboxView.networkStatusView.setSocketReconnectInterval(60000);
-        }
-    });
-
-    chrome.commands.onCommand.addListener(function(command) {
-        if (command === 'show_signal') {
-            openInbox();
-        }
-    });
-
 })();
diff --git a/js/chromium.js b/js/chromium.js
index ff61b8519..588e9a354 100644
--- a/js/chromium.js
+++ b/js/chromium.js
@@ -167,22 +167,8 @@
       };
     }
 
-    extension.install = function(mode) {
-        if (mode === 'standalone') {
-            return; // TODO
-        }
-        var installView = new Whisper.InstallView({
-              el: $('body').empty()
-        });
-        if (Whisper.Registration.everDone()) {
-            installView.selectStep(3);
-            installView.hideDots();
-        }
-        installView.$el.show();
-        Whisper.events.once('contactsync', function() {
-            openInbox();
-            installView.remove();
-        });
+    extension.install = function() {
+        Whisper.events.trigger('openInstaller');
     };
 
     var notification_pending = Promise.resolve();
diff --git a/js/panel_controller.js b/js/panel_controller.js
index 1a73677c9..532c2401f 100644
--- a/js/panel_controller.js
+++ b/js/panel_controller.js
@@ -1,4 +1,4 @@
-/*global $, Whisper, Backbone, textsecure, extension*/
+/*global $, Whisper, Backbone, extension*/
 /*
  * vim: ts=4:sw=4:expandtab
  */
@@ -10,11 +10,11 @@
     window.Whisper = window.Whisper || {};
 
     window.isOpen = function() {
-        return inboxOpened;
+        return true;
     };
 
     window.drawAttention = function() {
-        if (inboxOpened && !isFocused()) {
+        if (isOpen() && !isFocused()) {
             if (window.keepClear) {
                 clearInterval(window.keepClear);
                 delete window.keepClear;
@@ -33,29 +33,10 @@
             extension.windows.clearAttention(inboxWindowId);
         }, 2000);
     };
-
-    /* Inbox window controller */
-    var inboxOpened = false;
     var inboxWindowId = 'inbox';
-    var appWindow = null;
-    window.openInbox = function() {
-        console.log('open inbox');
-        if (inboxOpened === false) {
-            inboxOpened = true;
-            owsDesktopApp.getAppView(window).then(function(appView) {
-                var bodyEl = $('body');
-                bodyEl.innerHTML = "";
-                bodyEl.append(appView.el);
-            });
-            owsDesktopApp.openConversation(getOpenConversation());
-        } else if (inboxOpened === true) {
-            extension.windows.focus(inboxWindowId, function (error) {
-                if (error) {
-                    inboxOpened = false;
-                    openInbox();
-                }
-            });
-        }
+
+    window.openInbox = function(options) {
+        Whisper.events.trigger('openInbox', options);
     };
 
     window.setUnreadCount = function(count) {
@@ -68,18 +49,8 @@
         }
     };
 
-    var open;
     window.openConversation = function(conversation) {
-        if (inboxOpened === true) {
-            owsDesktopApp.openConversation(conversation);
-        } else {
-            open = conversation;
-        }
-        openInbox();
-    };
-    window.getOpenConversation = function() {
-        var o = open;
-        open = null;
-        return o;
+        Whisper.events.trigger('openConversation', conversation);
     };
+
 })();
diff --git a/js/views/app_view.js b/js/views/app_view.js
new file mode 100644
index 000000000..e628203b2
--- /dev/null
+++ b/js/views/app_view.js
@@ -0,0 +1,73 @@
+(function () {
+    'use strict';
+
+    window.Whisper = window.Whisper || {};
+
+    Whisper.AppView = Backbone.View.extend({
+        initialize: function(options) {
+          this.inboxView = null;
+          this.installView = null;
+          this.events = options.events;
+          this.events.on('openConversation', this.openConversation, this);
+          this.events.on('openInstaller', this.openInstaller, this);
+          this.events.on('openInbox', this.openInbox, this);
+        },
+        openInstaller: function() {
+          this.installView = new Whisper.InstallView();
+          if (Whisper.Registration.everDone()) {
+              this.installView.selectStep(3);
+              this.installView.hideDots();
+          }
+          this.el.innerHTML = "";
+          this.el.append(this.installView.el);
+        },
+        openInbox: function(options) {
+          options = options || {};
+          _.defaults(options, {initialLoadComplete: false});
+
+          console.log('open inbox');
+          if (this.installView) {
+            this.installView.remove();
+            this.installView = null;
+          }
+
+          if (!this.inboxView) {
+            return ConversationController.loadPromise().then(function() {
+                this.inboxView = new Whisper.InboxView({
+                  model: self,
+                  window: window,
+                  initialLoadComplete: initialLoadComplete
+                });
+                this.el.innerHTML = "";
+                this.el.append(this.inboxView.el);
+            }.bind(this));
+          } else {
+            if (!$.contains(this.$el, this.inboxView.$el)) {
+                this.el.innerHTML = "";
+                this.el.append(this.inboxView.el);
+            }
+            window.focus(); // FIXME
+            return Promise.resolve();
+          }
+        },
+        onEmpty: function() {
+          var view = this.inboxView;
+          if (view) {
+            view.onEmpty();
+          }
+        },
+        onProgress: function(count) {
+          var view = this.inboxView;
+          if (view) {
+            view.onProgress(count);
+          }
+        },
+        openConversation: function(conversation) {
+          if (conversation) {
+            this.openInbox().then(function() {
+              this.inboxView.openConversation(conversation);
+            }.bind(this));
+          }
+        },
+    });
+})();
diff --git a/js/views/install_view.js b/js/views/install_view.js
index b8d7e2a92..78e65fefd 100644
--- a/js/views/install_view.js
+++ b/js/views/install_view.js
@@ -73,6 +73,7 @@
         },
         close: function() {
             this.remove();
+            Whisper.events.trigger('openInbox');
         },
         events: function() {
             return {
diff --git a/js/views/network_status_view.js b/js/views/network_status_view.js
index 0f0bc3422..55689b233 100644
--- a/js/views/network_status_view.js
+++ b/js/views/network_status_view.js
@@ -26,7 +26,10 @@
             this.listenTo(this.model, 'change', this.onChange);
         },
         events: {
-            'click .openInstaller': extension.install
+            'click .openInstaller': 'openInstaller'
+        },
+        openInstaller: function() {
+            Whisper.events.trigger('openInstaller');
         },
         onReconnectTimer: function() {
           this.setSocketReconnectInterval(60000);