diff --git a/browserid/static/dialog/controllers/dialog_controller.js b/browserid/static/dialog/controllers/dialog_controller.js
index 1c7a39c8355acc4cefe382dafe0e670d66d3f686..6a16e5af9f9a6f76bbc9d23982b07304e60ca208 100644
--- a/browserid/static/dialog/controllers/dialog_controller.js
+++ b/browserid/static/dialog/controllers/dialog_controller.js
@@ -45,18 +45,28 @@
   var bid = BrowserID,
       user = bid.User,
       errors = bid.Errors,
-      offline = false;
+      offline = false,
+      win = window;
+      
 
   PageController.extend("Dialog", {}, {
-      init: function(el) {
+      init: function(el, options) {
+        offline = false;
+
+        options = options || {};
+
+        if(options.window) {
+          win = options.window;
+        }
+
         var self=this;
 
         // keep track of where we are and what we do on success and error
         self.onsuccess = null;
         self.onerror = null;
-        setupChannel(self);
+
+        win.setupChannel(self);
         self.stateMachine();
-       
       },
         
       getVerifiedEmail: function(origin_url, onsuccess, onerror) {
@@ -74,7 +84,7 @@
 
         self.doCheckAuth();
 
-        $(window).bind("unload", function() {
+        $(win).bind("unload", function() {
           bid.Storage.setStagedOnBehalfOf("");
           self.doCancel();
         });
@@ -154,12 +164,16 @@
       },
 
       doOffline: function() {
-        this.renderError("wait.ejs", errors.offline);
+        this.renderError("offline.ejs", {});
         offline = true;
       },
 
       doXHRError: function(info) {
-        if (!offline) this.renderError("wait.ejs", errors.offline);  
+        if (!offline) {
+          this.renderError("error.ejs", $.extend({
+            action: errors.xhrError
+          }, info));
+        }
       },
 
       doConfirmUser: function(email) {
diff --git a/browserid/static/dialog/controllers/page_controller.js b/browserid/static/dialog/controllers/page_controller.js
index 01ddbb26a0671476402fb333de998bdb32998db5..37389fb1fa4690719e247d23131caecd67d84a7c 100644
--- a/browserid/static/dialog/controllers/page_controller.js
+++ b/browserid/static/dialog/controllers/page_controller.js
@@ -145,12 +145,16 @@
     /**
      * Get a curried function to an error dialog.
      * @method getErrorDialog
-     * @method {object} info - info to use for the error dialog.  Should have 
+     * @method {object} action - info to use for the error dialog.  Should have 
      * two fields, message, description.
      */
-    getErrorDialog: function(info) {
+    getErrorDialog: function(action) {
       var self=this;
-      return self.renderError.bind(self, "wait.ejs", info);
+      return function(lowLevelInfo) {
+        self.renderError("error.ejs", $.extend({
+          action: action
+        }, lowLevelInfo));
+      }
     },
 
     onCancel: function(event) {
diff --git a/browserid/static/dialog/dialog.js b/browserid/static/dialog/dialog.js
index d3070184d61ce0bc8855bb5e552a55f8dbeae6f9..3c70f70d2a0ed2e7fc737f91fa9cd3fe3769c026 100644
--- a/browserid/static/dialog/dialog.js
+++ b/browserid/static/dialog/dialog.js
@@ -68,7 +68,9 @@ steal.plugins(
 	.views('authenticate.ejs',
            'confirmemail.ejs',
            'pickemail.ejs',
-           'wait.ejs'
+           'wait.ejs',
+           'error.ejs',
+           'offline.ejs'
           ).
 
           then(function() {
diff --git a/browserid/static/dialog/resources/error-messages.js b/browserid/static/dialog/resources/error-messages.js
index 7064d82d9f4a1fdaaccd01341763679ab4a412e0..4235913d94eb4b963c3639153d9347a19f26fbc7 100644
--- a/browserid/static/dialog/resources/error-messages.js
+++ b/browserid/static/dialog/resources/error-messages.js
@@ -37,75 +37,68 @@ BrowserID.Errors = (function(){
 
   var Errors = {
     authenticate: {
-      type: "serverError",
       title: "Error Authenticating",
       message: "There was a technical problem while trying to log you in.  Yucky!"
     },
 
     addEmail: {
-      type: "serverError",
       title: "Error Adding Address",
       message: "There was a technical problem while trying to add this email to your account.  Yucky!"
     },
 
     checkAuthentication: {
-      type: "serverError",
       title: "Error Checking Authentication",
       message: "There was a technical problem while trying to log you in.  Yucky!"
     },
 
     createUser: {
-      type: "serverError",
       title: "Error Creating Account",
       message: "There was a technical problem while trying to create your account.  Yucky!"
     },
 
     getAssertion: {
-      type: "serverError",
       title: "Error Getting Assertion",
       message: "There was a technical problem while trying to authenticate you.  Yucky!"
     },
 
     isEmailRegistered: {
-      type: "serverError",
       title: "Error Checking Email Address",
       message: "There was a technical problem while trying to check that email address.  Yucky!"
     },
 
     logoutUser: {
-      type: "serverError",
       title: "Logout Failed",
       message: "An error was encountered while signing you out.  Yucky!"
     },
 
     offline: {
-      type: "networkError",
       title: "You are offline!",
       message: "Unfortunately, BrowserID cannot communicate while offline!"
     },
 
     registration: {
-      type: "serverError",
       title: "Registration Failed",
       message: "An error was encountered and the signup cannot be completed.  Yucky!"
     },
 
     requestPasswordReset: {
-      type: "serverError",
       title: "Error Resetting Password",
       message: "There was a technical problem while trying to reset your password."
     },
 
     signIn: {
-      type: "serverError",
       title: "Signin Failed",
       message: "There was an error signing in. Yucky!"
     },
 
     syncAddress: {
-      type: "serverError",
       title: "Error Syncing Address",
       message: "There was a technical problem while trying to synchronize your account.  Yucky!"
+    },
+
+    xhrError: {
+      title: "Communication Error",
+      message: "It's embarrassing, but for some reason we cannot communicate with BrowserID at the moment.  Please try again."
     }
 
   };
diff --git a/browserid/static/dialog/resources/network.js b/browserid/static/dialog/resources/network.js
index 6f012b6e6e2d2a2c7b4fd9ab12e03af7ed449f76..dadbe3085fe7f2e7dd42f90f435c9cf6ffed7c0a 100644
--- a/browserid/static/dialog/resources/network.js
+++ b/browserid/static/dialog/resources/network.js
@@ -54,10 +54,10 @@ BrowserID.Network = (function() {
     }
   }
 
-  function xhrError(cb, errorMessage) {
+  function xhrError(cb, info) {
     return function() {
-      if (cb) cb();
-      hub && hub.publish("xhrError", errorMessage);
+      if (cb) cb(info);
+      hub && hub.publish("xhrError", info);
     };
   }
 
@@ -69,7 +69,12 @@ BrowserID.Network = (function() {
       // that are thrown in the response handlers and it becomes very difficult 
       // to debug.
       success: deferResponse(options.success),
-      error: deferResponse(xhrError(options.error, options.errorMessage)),
+      error: deferResponse(xhrError(options.error, {
+        network: {
+          type: "GET",
+          url: options.url
+        }
+      })),
       dataType: "json"
     });
   }
@@ -90,7 +95,13 @@ BrowserID.Network = (function() {
         // that are thrown in the response handlers and it becomes very difficult 
         // to debug.
         success: deferResponse(options.success),
-        error: deferResponse(xhrError(options.error, options.errorMessage))
+        error: deferResponse(xhrError(options.error, {
+          network: {
+            type: "POST",
+            url: options.url,
+            data: options.data
+          }
+        }))
       });
     }, options.error);
   }
@@ -98,6 +109,7 @@ BrowserID.Network = (function() {
   function withContext(cb, onFailure) {
     if (typeof auth_status === 'boolean' && typeof csrf_token !== 'undefined') cb();
     else {
+      var url = "/wsapi/session_context";
       xhr.ajax({
         url: "/wsapi/session_context",
         success: function(result) {
@@ -109,7 +121,12 @@ BrowserID.Network = (function() {
           auth_status = result.authenticated;
           cb();
         },
-        error: deferResponse(xhrError(onFailure))
+        error: deferResponse(xhrError(onFailure, {
+          network: {
+            type: "GET",
+            url: url
+          }
+        }))
       });
     }
   }
diff --git a/browserid/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js b/browserid/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..de0ce24219bcbf41bc3b679e3cc0924d90c6dc79
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/controllers/dialog_controller_unit_test.js
@@ -0,0 +1,103 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/controllers/dialog_controller", function() {
+  "use strict";
+
+  var controller, el
+
+  function reset() {
+    el = $("#controller_head");
+    el.find("#formWrap .contents").html("");
+    el.find("#wait .contents").html("");
+    el.find("#error .contents").html("");
+  }
+
+  module("controllers/dialog_controller", {
+    setup: function() {
+      reset();
+
+      controller = el.dialog({
+        window: {
+          setupChannel: function() {}
+        }
+      }).controller();
+    },
+
+    teardown: function() {
+      controller.destroy();
+      reset();
+    } 
+  });
+
+  test("doOffline", function() {
+    controller.doOffline();
+    ok($("#error .contents").length, "contents have been written");
+    ok($("#error #offline").length, "offline error message has been written");
+  });
+
+  test("doXHRError while online, no network info given", function() {
+    controller.doXHRError();
+    ok($("#error .contents").length, "contents have been written");
+    ok($("#error #action").length, "action contents have been written");
+    equal($("#error #network").length, 0, "no network contents to be written");
+  });
+
+  test("doXHRError while online, network info given", function() {
+    controller.doXHRError({
+      network: {
+        type: "POST",
+        url: "browserid.org/verify"
+      }
+    });
+    ok($("#error .contents").length, "contents have been written");
+    ok($("#error #action").length, "action contents have been written");
+    ok($("#error #network").length, "network contents have been written");
+  });
+
+  test("doXHRError while offline does not update contents", function() {
+    controller.doOffline();
+    $("#error #action").remove();
+
+    controller.doXHRError();
+    ok(!$("#error #action").length, "XHR error is not reported if the user is offline.");
+  });
+
+  test("window.unload causes setStagedOnBehalfOf data to be cleared", function() {
+  });
+
+});
+
diff --git a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js b/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
index e2df4dbb6a159fc4aa78c73b6a2e9c5842885996..a7bc08e0eee032725821584c1010917a7fb67809 100644
--- a/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
+++ b/browserid/static/dialog/test/qunit/controllers/page_controller_unit_test.js
@@ -48,7 +48,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
     el.find("#error .contents").html("");
   }
 
-  module("PageController", {
+  module("/controllers/page_controller", {
     setup: function() {
       reset();
     },
@@ -150,9 +150,10 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
       }
     }).controller();
    
+    // This is the medium level info.
     var func = controller.getErrorDialog({
-      title: "error title",
-      message: "error message"
+      title: "medium level info error title",
+      message: "medium level info error message"
     });
 
     equal(typeof func, "function", "a function was returned from getErrorDialog");
diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js
index 7ceaabc35ed72539a42d52a9a1bab5412d3525d3..ffecc86f2b4e903e5884105bdf51827953fa2a96 100644
--- a/browserid/static/dialog/test/qunit/qunit.js
+++ b/browserid/static/dialog/test/qunit/qunit.js
@@ -1,5 +1,6 @@
 steal("/dialog/resources/browserid.js",
       "/dialog/resources/browser-support.js",
+      "/dialog/resources/error-messages.js",
       "/dialog/resources/storage.js",
       "/dialog/resources/tooltip.js",
       "/dialog/resources/validation.js",
@@ -11,9 +12,11 @@ steal("/dialog/resources/browserid.js",
     "jquery/controller/view",
     "jquery/view/ejs",
     "funcunit/qunit")
-	.views('testBodyTemplate.ejs')
-	.views('wait.ejs',
-         'pickemail.ejs')
+	.views('testBodyTemplate.ejs',
+         'wait.ejs',
+         'pickemail.ejs',
+         'offline.ejs',
+         'error.ejs')
   .then("browserid_unit_test")
   .then("include_unit_test")
   .then("relay/relay_unit_test")
@@ -26,4 +29,5 @@ steal("/dialog/resources/browserid.js",
   .then("resources/user_unit_test")
   .then("controllers/page_controller_unit_test")
   .then("controllers/pickemail_controller_unit_test")
+  .then("controllers/dialog_controller_unit_test")
 
diff --git a/browserid/static/dialog/test/qunit/resources/network_unit_test.js b/browserid/static/dialog/test/qunit/resources/network_unit_test.js
index ac5b06c785ffba87efe2ea7f3d2b3404aefb6b16..74d35d40f086fc6a0bf30d66cc332b4799361237 100644
--- a/browserid/static/dialog/test/qunit/resources/network_unit_test.js
+++ b/browserid/static/dialog/test/qunit/resources/network_unit_test.js
@@ -61,8 +61,10 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
 
     var handle;
 
-    var subscriber = function() {
+    var subscriber = function(message, info) {
       ok(true, "xhr error notified application");
+      ok(info.network.url, "url is in network info");
+      ok(info.network.type, "request type is in network info");
       wrappedStart();
       OpenAjax.hub.unsubscribe(handle);
     };
@@ -85,8 +87,10 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     args.push(function onSuccess(authenticated) {
       ok(false, "XHR failure should never pass");
       wrappedStart();
-    }, function onFailure() {
+    }, function onFailure(info) {
       ok(true, "XHR failure should never pass");
+      ok(info.network.url, "url is in network info");
+      ok(info.network.type, "request type is in network info");
       wrappedStart();
     });
 
@@ -195,7 +199,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
   }
 
 
-  module("network", {
+  module("/resources/network", {
     setup: function() {
       network.setXHR(xhr);
       xhr.useResult("valid");
diff --git a/browserid/static/dialog/views/error.ejs b/browserid/static/dialog/views/error.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..336fc1d4c442bdea03251b4a8e6f707434ba5764
--- /dev/null
+++ b/browserid/static/dialog/views/error.ejs
@@ -0,0 +1,27 @@
+
+  <h2>There has been an error!</h2>
+
+  <p>
+    We are very sorry, but there has been an error. You can open the error message below
+    if you care to find out more information.  To retry, you will have to close BrowserID and try again.
+  </p>
+
+  <a href="#">See more info</a>
+
+  <aside>
+    <% if (typeof action !== "undefined") { %>
+      <h3 id="action">Action: <%= action.title %></h3>
+      <p>
+        <%= action.message %>
+      </p>  
+    <% } %>
+
+    <% if (typeof network !== "undefined") { %>
+      <h3 id="network">Network Info: </h3>
+      <p>
+        <%= network.type %>: <%= network.url %>
+      </p>  
+
+    <% } %>
+
+  </aside>
diff --git a/browserid/static/dialog/views/offline.ejs b/browserid/static/dialog/views/offline.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..677951bef1fc6a0fb4cb93eab91ed11c48f7d452
--- /dev/null
+++ b/browserid/static/dialog/views/offline.ejs
@@ -0,0 +1,8 @@
+
+  <h2 id="offline">You are offline!</h2>
+
+  <p>
+    We are sorry, but we cannot communicate with BrowserID while you are offline.
+  </p>
+
+