From c30b4df849b6b12e25c5d329406edd2dfe0ca42e Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <set117@yahoo.com>
Date: Sun, 13 Nov 2011 14:53:58 +0000
Subject: [PATCH] A large merge to unify code, flatten structure, and reduce
 the impact of JavascriptMVC.

* Add new directory `shared` which is code shared between dialog and pages.
* Add a renderer and template loader that is shared to unify rendering of templates.
* Remove JavascriptMVC's Views and use EJS directly.
* Add a script to create our own bundled javascript object with all templates.
* Use EJS templates for both error messages and tooltips
* Simplify unit test dependency chain by doing nearly all dependencies in qunit.js.
* Update compression script for new files/structure.
---
 .../static/communication_iframe/iframe.js     | 20 ++--
 resources/static/css/style.css                |  6 +-
 .../controllers/authenticate_controller.js    |  2 +-
 .../checkregistration_controller.js           |  6 +-
 .../dialog/controllers/dialog_controller.js   |  6 +-
 .../dialog/controllers/page_controller.js     | 49 ++--------
 .../controllers/pickemail_controller.js       |  2 +-
 resources/static/dialog/dialog.js             | 46 +++++----
 resources/static/dialog/views/error.ejs       | 12 ++-
 .../static/dialog/views/testBodyTemplate.ejs  |  2 +-
 resources/static/dialog/views/tooltip.ejs     |  4 +
 resources/static/lib/dom-jquery.js            | 14 ++-
 resources/static/lib/module.js                | 96 +++++++++++++++++++
 resources/static/pages/page_helpers.js        |  8 +-
 resources/static/resources/error-display.js   | 30 ------
 .../{resources => shared}/browser-support.js  |  0
 .../browserid-extensions.js                   |  0
 .../static/{resources => shared}/browserid.js |  0
 resources/static/shared/error-display.js      | 33 +++++++
 .../{resources => shared}/error-messages.js   |  0
 .../static/{resources => shared}/network.js   |  0
 resources/static/shared/renderer.js           | 78 +++++++++++++++
 resources/static/shared/screens.js            | 40 ++++++++
 .../static/{resources => shared}/storage.js   |  0
 resources/static/shared/templates.js          |  2 +
 .../static/{resources => shared}/tooltip.js   | 26 ++---
 .../static/{resources => shared}/user.js      |  0
 .../{resources => shared}/validation.js       |  0
 .../{resources => shared}/wait-messages.js    |  0
 resources/static/test/qunit.html              | 16 +---
 .../authenticate_controller_unit_test.js      |  2 +-
 .../dialog_controller_unit_test.js            |  6 +-
 .../controllers/page_controller_unit_test.js  | 17 ++--
 .../static/test/qunit/mocks/templates.js      |  4 +
 .../qunit/pages/add_email_address_test.js     |  8 +-
 .../test/qunit/pages/browserid_unit_test.js   |  2 +-
 .../test/qunit/pages/forgot_unit_test.js      |  8 +-
 .../qunit/pages/manage_account_unit_test.js   |  4 +-
 .../qunit/pages/page_helpers_unit_test.js     |  2 +-
 .../test/qunit/pages/signin_unit_test.js      |  4 +-
 .../test/qunit/pages/signup_unit_test.js      |  4 +-
 .../qunit/pages/verify_email_address_test.js  | 10 +-
 resources/static/test/qunit/qunit.js          | 89 +++++++++--------
 .../test/qunit/resources/channel_unit_test.js |  6 +-
 .../browser-support_unit_test.js              |  4 +-
 .../error-display_unit_test.js                | 21 ++--
 .../network_unit_test.js                      |  4 +-
 .../test/qunit/shared/renderer_unit_test.js   | 82 ++++++++++++++++
 .../test/qunit/shared/screens_unit_test.js    | 86 +++++++++++++++++
 .../storage_unit_test.js                      |  4 +-
 .../tooltip_unit_test.js                      |  9 +-
 .../{resources => shared}/user_unit_test.js   |  4 +-
 .../validation_unit_test.js                   |  4 +-
 resources/views/dialog_layout.ejs             |  5 -
 resources/views/layout.ejs                    | 80 ++++------------
 resources/views/relay.ejs                     |  2 +-
 scripts/compress.sh                           | 14 ++-
 scripts/create_templates.js                   | 24 +++++
 58 files changed, 696 insertions(+), 311 deletions(-)
 create mode 100644 resources/static/dialog/views/tooltip.ejs
 create mode 100644 resources/static/lib/module.js
 delete mode 100644 resources/static/resources/error-display.js
 rename resources/static/{resources => shared}/browser-support.js (100%)
 rename resources/static/{resources => shared}/browserid-extensions.js (100%)
 rename resources/static/{resources => shared}/browserid.js (100%)
 create mode 100644 resources/static/shared/error-display.js
 rename resources/static/{resources => shared}/error-messages.js (100%)
 rename resources/static/{resources => shared}/network.js (100%)
 create mode 100644 resources/static/shared/renderer.js
 create mode 100644 resources/static/shared/screens.js
 rename resources/static/{resources => shared}/storage.js (100%)
 create mode 100644 resources/static/shared/templates.js
 rename resources/static/{resources => shared}/tooltip.js (90%)
 rename resources/static/{resources => shared}/user.js (100%)
 rename resources/static/{resources => shared}/validation.js (100%)
 rename resources/static/{resources => shared}/wait-messages.js (100%)
 create mode 100644 resources/static/test/qunit/mocks/templates.js
 rename resources/static/test/qunit/{resources => shared}/browser-support_unit_test.js (97%)
 rename resources/static/test/qunit/{resources => shared}/error-display_unit_test.js (78%)
 rename resources/static/test/qunit/{resources => shared}/network_unit_test.js (99%)
 create mode 100644 resources/static/test/qunit/shared/renderer_unit_test.js
 create mode 100644 resources/static/test/qunit/shared/screens_unit_test.js
 rename resources/static/test/qunit/{resources => shared}/storage_unit_test.js (98%)
 rename resources/static/test/qunit/{resources => shared}/tooltip_unit_test.js (89%)
 rename resources/static/test/qunit/{resources => shared}/user_unit_test.js (99%)
 rename resources/static/test/qunit/{resources => shared}/validation_unit_test.js (98%)
 create mode 100755 scripts/create_templates.js

diff --git a/resources/static/communication_iframe/iframe.js b/resources/static/communication_iframe/iframe.js
index 57409c9f1..da4e50dfb 100644
--- a/resources/static/communication_iframe/iframe.js
+++ b/resources/static/communication_iframe/iframe.js
@@ -44,16 +44,16 @@ steal
   .then('../lib/jschannel',
         '../lib/base64',
         '../lib/underscore-min',
-        '../resources/channel',
-        '../resources/browserid',
-        '../resources/storage',
-        '../resources/tooltip',
-        '../resources/validation',
-        '../resources/browserid-extensions',
-        '../resources/network',
-        '../resources/user',
-        '../resources/error-messages',
-        '../resources/wait-messages',
+        '../dialog/resources/channel',
+        '../shared/browserid',
+        '../shared/storage',
+        '../shared/tooltip',
+        '../shared/validation',
+        '../shared/browserid-extensions',
+        '../shared/network',
+        '../shared/user',
+        '../shared/error-messages',
+        '../shared/wait-messages',
         afterResourceLoad);
 
 function afterResourceLoad() {
diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index fc2e75568..c5101f1c5 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -116,6 +116,10 @@ hr {
   background-color: rgba(0,0,0,.6);
 }
 
+#error {
+  display: none;
+}
+
 #error > div {
   background-color: #fff;
   position: absolute;
@@ -128,7 +132,7 @@ hr {
 }
 
 #error ul, #error li {
-    list-style-type: none; 
+    list-style-type: none;
 }
 
 #wait strong, #error strong {
diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js
index b270a65d7..9fd77e4b6 100644
--- a/resources/static/dialog/controllers/authenticate_controller.js
+++ b/resources/static/dialog/controllers/authenticate_controller.js
@@ -186,7 +186,7 @@
       options = options || {};
 
       this._super(el, {
-        bodyTemplate: "authenticate.ejs",
+        bodyTemplate: "authenticate",
         bodyVars: {
           sitename: user.getHostname(),
           email: options.email || ""
diff --git a/resources/static/dialog/controllers/checkregistration_controller.js b/resources/static/dialog/controllers/checkregistration_controller.js
index 06558c8be..b19fa0be9 100644
--- a/resources/static/dialog/controllers/checkregistration_controller.js
+++ b/resources/static/dialog/controllers/checkregistration_controller.js
@@ -1,5 +1,5 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
-/*global BrowserID: true, PageController: true */ 
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*global BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
@@ -43,7 +43,7 @@
     init: function(el, options) {
       var me=this;
       me._super(el, {
-        waitTemplate: "confirmemail.ejs",
+        waitTemplate: "confirmemail",
         waitVars: {
           email: options.email
         }
diff --git a/resources/static/dialog/controllers/dialog_controller.js b/resources/static/dialog/controllers/dialog_controller.js
index e919c3b45..7e0af554a 100644
--- a/resources/static/dialog/controllers/dialog_controller.js
+++ b/resources/static/dialog/controllers/dialog_controller.js
@@ -75,7 +75,7 @@
           win.setupChannel(self);
           self.stateMachine();
         } catch (e) {
-          self.renderError("error.ejs", {
+          self.renderError("error", {
             action: errors.relaySetup
           });
         }
@@ -185,13 +185,13 @@
       },
 
       doOffline: function() {
-        this.renderError("offline.ejs", {});
+        this.renderError("offline", {});
         offline = true;
       },
 
       doXHRError: function(info) {
         if (!offline) {
-          this.renderError("error.ejs", $.extend({
+          this.renderError("error", $.extend({
             action: errors.xhrError
           }, info));
         }
diff --git a/resources/static/dialog/controllers/page_controller.js b/resources/static/dialog/controllers/page_controller.js
index 9ff9f3386..ed4a4fdaa 100644
--- a/resources/static/dialog/controllers/page_controller.js
+++ b/resources/static/dialog/controllers/page_controller.js
@@ -39,7 +39,8 @@
 
   var ANIMATION_TIME = 250,
       bid = BrowserID,
-      dom = bid.DOM;
+      dom = bid.DOM,
+      screens = bid.Screens;
 
 
   $.Controller.extend("PageController", {
@@ -70,16 +71,12 @@
 
       // XXX move all of these, bleck.
       dom.bindEvent("form", "submit", me.onSubmit.bind(me));
-      dom.bindEvent("#cancel", "click", me.onCancel.bind(me));
-      dom.bindEvent("#back", "click", me.onBack.bind(me));
       dom.bindEvent("#thisIsNotMe", "click", me.close.bind(me, "notme"));
     },
 
     destroy: function() {
       dom.unbindEvent("form", "submit");
       dom.unbindEvent("input", "keyup");
-      dom.unbindEvent("#cancel", "click");
-      dom.unbindEvent("#back", "click");
       dom.unbindEvent("#thisIsNotMe", "click");
 
       dom.removeClass("body", "waiting");
@@ -87,40 +84,24 @@
       this._super();
     },
 
-    renderTemplates: function(target, body, body_vars) {
-      if (body) {
-        var bodyHtml = new EJS({url: "/dialog/views/" + body}).render(body_vars);
-        target = $(target + " .contents");
-        target.html(bodyHtml).find("input").eq(0).focus();
-      }
-    },
-
     renderDialog: function(body, body_vars) {
-      this.renderTemplates("#formWrap", body, body_vars);
-      dom.removeClass("body", "error");
-      dom.removeClass("body", "waiting");
-      dom.addClass("body", "form");
+      screens.form(body, body_vars);
       $("#wait, #error").stop().fadeOut(ANIMATION_TIME);
+      dom.focus("input:visible:eq(0)");
     },
 
     renderWait: function(body, body_vars) {
-      this.renderTemplates("#wait", body, body_vars);
-      dom.removeClass("body", "error");
-      dom.removeClass("body", "form");
-      dom.addClass("body", "waiting");
+      screens.wait(body, body_vars);
       $("body").css('opacity', 1);
       $("#wait").stop().hide().fadeIn(ANIMATION_TIME);
     },
 
     renderError: function(body, body_vars) {
-      this.renderTemplates("#error", body, body_vars);
-      dom.removeClass("body", "waiting");
-      dom.removeClass("body", "form");
-      dom.addClass("body", "error");
+      screens.error(body, body_vars);
       $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME);
 
       /**
-       * What a big steaming pile, use CSS animations for this!
+       * TODO What a big steaming pile, use CSS animations for this!
        */
       dom.bindEvent("#openMoreInfo", "click", function(event) {
         event.preventDefault();
@@ -149,7 +130,7 @@
     },
 
     doWait: function(info) {
-      this.renderWait("wait.ejs", info);
+      this.renderWait("wait", info);
 
       dom.addClass("body", "waiting");
     },
@@ -170,22 +151,10 @@
     getErrorDialog: function(action) {
       var self=this;
       return function(lowLevelInfo) {
-        self.renderError("error.ejs", $.extend({
+        self.renderError("error", $.extend({
           action: action
         }, lowLevelInfo));
       }
-    },
-
-    onCancel: function(event) {
-      event.preventDefault();
-      event.stopPropagation();
-      this.close("cancel");
-    },
-
-    onBack: function(event) {
-      event.preventDefault();
-      event.stopPropagation();
-      this.close("start");
     }
   });
 
diff --git a/resources/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js
index e3cb978f9..01cea06fa 100644
--- a/resources/static/dialog/controllers/pickemail_controller.js
+++ b/resources/static/dialog/controllers/pickemail_controller.js
@@ -181,7 +181,7 @@
     init: function(el, options) {
       var origin = user.getOrigin();
       this._super(el, {
-        bodyTemplate: "pickemail.ejs",
+        bodyTemplate: "pickemail",
         bodyVars: {
           identities: user.getStoredEmailKeypairs(),
           // XXX ideal is to get rid of this and have a User function
diff --git a/resources/static/dialog/dialog.js b/resources/static/dialog/dialog.js
index 1270aee6f..319fa4cca 100644
--- a/resources/static/dialog/dialog.js
+++ b/resources/static/dialog/dialog.js
@@ -39,14 +39,7 @@ window.console = window.console || {
   log: function() {}
 };
 
-steal(
-              '../lib/jschannel',
-              '../lib/base64',
-              '../lib/underscore-min',
-              '../lib/ejs',
-              '../resources/browserid',
-              '../lib/jquery-1.6.2.min.js',
-              '../lib/dom-jquery')
+steal
   .plugins(
               'jquery/controller',			// a widget factory
               'jquery/controller/subscribe')	// subscribe to OpenAjax.hub
@@ -54,15 +47,26 @@ steal(
 	.resources(
                'channel')
   .then(
-               '../resources/storage',
-               '../resources/tooltip',
-               '../resources/validation',
-               '../resources/browser-support',
-               '../resources/browserid-extensions',
-               '../resources/network',
-               '../resources/user',
-               '../resources/error-messages',
-               '../resources/wait-messages')					// 3rd party script's (like jQueryUI), in resources folder
+               '../lib/jschannel',
+               '../lib/base64',
+               '../lib/underscore-min',
+               '../lib/ejs',
+               '../shared/browserid',
+               '../lib/dom-jquery',
+
+               '../shared/storage',
+               '../shared/templates',
+               '../shared/renderer',
+               '../shared/error-display',
+               '../shared/screens',
+               '../shared/tooltip',
+               '../shared/validation',
+               '../shared/browser-support',
+               '../shared/browserid-extensions',
+               '../shared/network',
+               '../shared/user',
+               '../shared/error-messages',
+               '../shared/wait-messages')
 
 	.controllers('page',
                'dialog',
@@ -71,7 +75,7 @@ steal(
                'pickemail')					// loads files in controllers folder
 
   .then(function() {
-            $(function() {
-              $('body').dialog().show();
-            });
-          });						// adds views to be added to build
+    $(function() {
+      $('body').dialog().show();
+    });
+  });						// adds views to be added to build
diff --git a/resources/static/dialog/views/error.ejs b/resources/static/dialog/views/error.ejs
index 537d2471f..6c5e9bcbd 100644
--- a/resources/static/dialog/views/error.ejs
+++ b/resources/static/dialog/views/error.ejs
@@ -2,7 +2,11 @@
   <h2>We are very sorry, there has been an error!</h2>
 
   <p>
+  <% if (typeof dialog !== "undefined" && dialog !== false) { %>
+    To retry, you will have to reload the page and try again.  More info can be found by opening the expanded info below.
+  <% } else { %>
     To retry, you will have to close BrowserID and try again.  More info can be found by opening the expanded info below.
+  <% } %>
   </p>
 
   <a href="#" id="openMoreInfo">See more info</a>
@@ -15,7 +19,7 @@
         <% if (action.message) { %>
           <p>
             <%= action.message %>
-          </p>  
+          </p>
         <% } %>
       </li>
     <% } %>
@@ -26,13 +30,13 @@
         <strong id="network">Network Info:</strong> <%= network.type %>: <%= network.url %>
 
         <p>
-          <strong>Response Code - </strong> <%= network.textStatus %> 
+          <strong>Response Code - </strong> <%= network.textStatus %>
         </p>
 
         <% if (network.errorThrown) { %>
           <p>
-            <strong>Error Type:</strong> <%= network.errorThrown %> 
-          </p>  
+            <strong>Error Type:</strong> <%= network.errorThrown %>
+          </p>
         <% } %>
 
       </li>
diff --git a/resources/static/dialog/views/testBodyTemplate.ejs b/resources/static/dialog/views/testBodyTemplate.ejs
index 77a06e46b..cb93998d2 100644
--- a/resources/static/dialog/views/testBodyTemplate.ejs
+++ b/resources/static/dialog/views/testBodyTemplate.ejs
@@ -1,2 +1,2 @@
-<input type="text" value="" />
+<input id="templateInput" type="text" value="" />
 
diff --git a/resources/static/dialog/views/tooltip.ejs b/resources/static/dialog/views/tooltip.ejs
new file mode 100644
index 000000000..191af143e
--- /dev/null
+++ b/resources/static/dialog/views/tooltip.ejs
@@ -0,0 +1,4 @@
+<div class="tooltip">
+  <%= contents %>
+</div>
+
diff --git a/resources/static/lib/dom-jquery.js b/resources/static/lib/dom-jquery.js
index 25d2a691f..889d165bf 100644
--- a/resources/static/lib/dom-jquery.js
+++ b/resources/static/lib/dom-jquery.js
@@ -137,7 +137,6 @@ BrowserID.DOM = ( function() {
             else {
                 target.html( value );
             }
-
         },
 
         /**
@@ -256,7 +255,9 @@ BrowserID.DOM = ( function() {
         * @param {selector || element} elementToAppendTo
         */
         appendTo: function( elementToInsert, elementToAppendTo ) {
-            jQuery( elementToInsert ).appendTo( jQuery( elementToAppendTo ) );
+            var el = jQuery(elementToInsert );
+            el.appendTo( jQuery( elementToAppendTo ) );
+            return el;
         },
 
         /**
@@ -286,6 +287,15 @@ BrowserID.DOM = ( function() {
                 elementToInsert.insertBefore( insertBefore );
             }
 
+        },
+
+        /**
+         * Focus an element
+         * @method focus
+         * @param {selelector || element} elementToFocus
+         */
+        focus: function( elementToFocus ) {
+          jQuery( elementToFocus ).focus();
         }
 
 
diff --git a/resources/static/lib/module.js b/resources/static/lib/module.js
new file mode 100644
index 000000000..e9b06f406
--- /dev/null
+++ b/resources/static/lib/module.js
@@ -0,0 +1,96 @@
+/**
+* Author Shane Tomlinson
+* Original source can be found at:
+* https://github.com/stomlinson/appcore/blob/master/js/module.js
+* Licences under Mozilla Tri-License
+*/
+BrowserID.module = (function() {
+  "use strict";
+
+  var registration = {},
+      created = {},
+      running = {};
+
+  function register(service, module, config) {
+    if (!module) {
+      throw "module constructor missing for " + service;
+    }
+
+    registration[service] = {
+      constructor: module,
+      config: config
+    };
+  }
+
+  function getRegistration(service) {
+    return registration[service];
+  }
+
+  function getModule(service) {
+    return registration[service].constructor;
+  }
+
+  function reset() {
+    registration = {};
+    running = {};
+    created = {};
+  }
+
+  function start(service, data) {
+    if (running[service]) {
+      throw "module already running for " + service;
+    }
+
+    var module = created[service];
+
+    if (!module) {
+      var registration = getRegistration(service);
+      if (registration) {
+        var constr = registration.constructor,
+            config = registration.config;
+
+        module = new constr();
+        created[service] = module;
+        module.init(config);
+      }
+      else {
+        throw "module not registered for " + service;
+      }
+    }
+
+    module.start(data);
+    running[service] = module;
+
+    return module;
+  }
+
+  function stop(service) {
+    var module = running[service];
+
+    if (module) {
+      module.stop();
+      delete running[service];
+    }
+    else {
+      throw "module not started for " + service;
+    }
+  }
+
+  function stopAll() {
+    for(var key in running) {
+      var module = running[key];
+      module.stop();
+      delete running[key];
+    }
+  }
+
+
+  return {
+    register: register,
+    getModule: getModule,
+    reset: reset,
+    start: start,
+    stop: stop,
+    stopAll: stopAll
+  };
+}());
diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js
index f53a0844a..11e8546b3 100644
--- a/resources/static/pages/page_helpers.js
+++ b/resources/static/pages/page_helpers.js
@@ -52,7 +52,7 @@ BrowserID.PageHelpers = (function() {
   }
 
   function prefillEmail() {
-    // If the user tried to sign in on the sign up page with an existing email, 
+    // If the user tried to sign in on the sign up page with an existing email,
     // place that email in the email field, then focus the password.
     var el = $("#email"),
         email = locStorage.signInEmail;
@@ -64,7 +64,7 @@ BrowserID.PageHelpers = (function() {
 
     el.keyup(onEmailKeyUp);
   }
-  
+
   function clearStoredEmail() {
     locStorage.removeItem("signInEmail");
   }
@@ -86,8 +86,8 @@ BrowserID.PageHelpers = (function() {
 
   function getFailure(error) {
     return function onFailure(info) {
-      info = $.extend(info, { action: error });
-      errorDisplay.render("#error", "#templateError", info);
+      info = $.extend(info, { action: error, dialog: false });
+      bid.Screens.error("error", info);
       $("#errorBackground").fadeIn();
       $("#error").fadeIn();
     }
diff --git a/resources/static/resources/error-display.js b/resources/static/resources/error-display.js
deleted file mode 100644
index c70452133..000000000
--- a/resources/static/resources/error-display.js
+++ /dev/null
@@ -1,30 +0,0 @@
-BrowserID.ErrorDisplay = (function() {
-  "use strict";
-
-  function render(target, template, data) {
-      template = $(template).html();
-      _.templateSettings = {
-          interpolate : /\{\{(.+?)\}\}/g,
-          evaluate : /\{\%(.+?)\%\}/g
-      };
-      var display = $(_.template(template, data)).appendTo(target);
-
-      /**
-       * What a big steaming pile, use CSS animations for this!
-       */
-      display.find("#openMoreInfo").click(function(event) {
-        event.preventDefault();
-
-        display.find("#moreInfo").slideDown();
-        display.find("#openMoreInfo").css({visibility: "hidden"});
-      });
-    
-      return display;
-  }
-
-  return {
-    render: render
-  };
-
-}());
-
diff --git a/resources/static/resources/browser-support.js b/resources/static/shared/browser-support.js
similarity index 100%
rename from resources/static/resources/browser-support.js
rename to resources/static/shared/browser-support.js
diff --git a/resources/static/resources/browserid-extensions.js b/resources/static/shared/browserid-extensions.js
similarity index 100%
rename from resources/static/resources/browserid-extensions.js
rename to resources/static/shared/browserid-extensions.js
diff --git a/resources/static/resources/browserid.js b/resources/static/shared/browserid.js
similarity index 100%
rename from resources/static/resources/browserid.js
rename to resources/static/shared/browserid.js
diff --git a/resources/static/shared/error-display.js b/resources/static/shared/error-display.js
new file mode 100644
index 000000000..c26c830ec
--- /dev/null
+++ b/resources/static/shared/error-display.js
@@ -0,0 +1,33 @@
+BrowserID.ErrorDisplay = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      dom = bid.DOM;
+
+  function open(event) {
+    event && event.preventDefault();
+
+    /**
+     * XXX What a big steaming pile, use CSS animations for this!
+     */
+    $("#moreInfo").slideDown();
+    $("#openMoreInfo").css({visibility: "hidden"});
+  }
+
+  function init(target) {
+    dom.bindEvent("#openMoreInfo", "click", open);
+    return dom.getElements(target);
+  }
+
+  function stop() {
+    dom.unbindEvent("#openMoreInfo", "click", open);
+  }
+
+  return {
+    start: init,
+    stop: stop,
+    open: open
+  };
+
+}());
+
diff --git a/resources/static/resources/error-messages.js b/resources/static/shared/error-messages.js
similarity index 100%
rename from resources/static/resources/error-messages.js
rename to resources/static/shared/error-messages.js
diff --git a/resources/static/resources/network.js b/resources/static/shared/network.js
similarity index 100%
rename from resources/static/resources/network.js
rename to resources/static/shared/network.js
diff --git a/resources/static/shared/renderer.js b/resources/static/shared/renderer.js
new file mode 100644
index 000000000..60785aeed
--- /dev/null
+++ b/resources/static/shared/renderer.js
@@ -0,0 +1,78 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global BrowserID: true, _: 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 ***** */
+BrowserID.Renderer = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      dom = bid.DOM;
+
+  function getTemplateHTML(body, vars) {
+    var config,
+        templateText = bid.Templates[body];
+
+    if(templateText) {
+      config = {
+        text: templateText
+      };
+    }
+    else {
+      // TODO - be able to set the directory
+      config = {
+        url: "/dialog/views/" + body + ".ejs"
+      };
+    }
+
+    var html = new EJS(config).render(vars);
+    return html;
+  }
+
+  function render(target, body, vars) {
+    var html = getTemplateHTML(body, vars);
+    return dom.setInner(target, html);
+  }
+
+  function append(target, body, vars) {
+    var html = getTemplateHTML(body, vars);
+    return dom.appendTo(html, target);
+  }
+
+  return {
+    render: render,
+    append: append
+  }
+}());
+
diff --git a/resources/static/shared/screens.js b/resources/static/shared/screens.js
new file mode 100644
index 000000000..97338199f
--- /dev/null
+++ b/resources/static/shared/screens.js
@@ -0,0 +1,40 @@
+BrowserID.Screens = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      dom = BrowserID.DOM,
+      renderer = bid.Renderer;
+
+  function render(target, body, vars) {
+    renderer.render(target + " .contents", body, vars);
+  }
+
+  function form(template, vars) {
+    render("#formWrap", template, vars);
+    dom.removeClass("body", "error");
+    dom.removeClass("body", "waiting");
+    dom.addClass("body", "form");
+  }
+
+  function wait(template, vars) {
+    render("#wait", template, vars);
+    dom.removeClass("body", "error");
+    dom.removeClass("body", "form");
+    dom.addClass("body", "waiting");
+  }
+
+  function error(template, vars) {
+    render("#error", template, vars);
+    dom.removeClass("body", "waiting");
+    dom.removeClass("body", "form");
+    dom.addClass("body", "error");
+
+    bid.ErrorDisplay.start("#error");
+  }
+
+  return {
+    form: form,
+    wait: wait,
+    error: error
+  };
+}());
diff --git a/resources/static/resources/storage.js b/resources/static/shared/storage.js
similarity index 100%
rename from resources/static/resources/storage.js
rename to resources/static/shared/storage.js
diff --git a/resources/static/shared/templates.js b/resources/static/shared/templates.js
new file mode 100644
index 000000000..58a7358fe
--- /dev/null
+++ b/resources/static/shared/templates.js
@@ -0,0 +1,2 @@
+BrowserID.Templates = {};
+
diff --git a/resources/static/resources/tooltip.js b/resources/static/shared/tooltip.js
similarity index 90%
rename from resources/static/resources/tooltip.js
rename to resources/static/shared/tooltip.js
index 602602b4a..8800b6cea 100644
--- a/resources/static/resources/tooltip.js
+++ b/resources/static/shared/tooltip.js
@@ -1,4 +1,4 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*globals BrowserID: true, _:true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
@@ -40,17 +40,17 @@ BrowserID.Tooltip = (function() {
 
   var ANIMATION_TIME = 250,
       TOOLTIP_DISPLAY = 2000,
-      READ_WPM = 200;
+      READ_WPM = 200,
+      bid = BrowserID,
+      dom = bid.DOM,
+      renderer = bid.Renderer;
 
   function createTooltip(el) {
       var contents = el.html();
-      var template = $("#templateTooltip").html();
-      _.templateSettings = {
-          interpolate : /\{\{(.+?)\}\}/g
-      };
-      var tooltip = $(_.template(template, {
+
+      var tooltip = renderer.append("body", "tooltip", {
         contents: contents
-      }));
+      });
 
       return tooltip;
   }
@@ -79,11 +79,11 @@ BrowserID.Tooltip = (function() {
   }
 
   function createAndShowRelatedTooltip(el, relatedTo, complete) {
-      // This means create a copy of the tooltip element and position it in 
-      // relation to an element.  Right now we are putting the tooltip directly 
-      // above the element.  Once the tooltip is no longer needed, remove it 
+      // This means create a copy of the tooltip element and position it in
+      // relation to an element.  Right now we are putting the tooltip directly
+      // above the element.  Once the tooltip is no longer needed, remove it
       // from the DOM.
-      var tooltip = createTooltip(el).appendTo("body");
+      var tooltip = createTooltip(el);
 
       var target = $("#" + relatedTo);
       positionTooltip(tooltip, target);
@@ -101,7 +101,7 @@ BrowserID.Tooltip = (function() {
     el = $(el);
     var messageFor = el.attr("for");
 
-    // First, see if we are "for" another element, if we are, create a copy of 
+    // First, see if we are "for" another element, if we are, create a copy of
     // the tooltip to attach to the element.
     if(messageFor) {
       createAndShowRelatedTooltip(el, messageFor, complete);
diff --git a/resources/static/resources/user.js b/resources/static/shared/user.js
similarity index 100%
rename from resources/static/resources/user.js
rename to resources/static/shared/user.js
diff --git a/resources/static/resources/validation.js b/resources/static/shared/validation.js
similarity index 100%
rename from resources/static/resources/validation.js
rename to resources/static/shared/validation.js
diff --git a/resources/static/resources/wait-messages.js b/resources/static/shared/wait-messages.js
similarity index 100%
rename from resources/static/resources/wait-messages.js
rename to resources/static/shared/wait-messages.js
diff --git a/resources/static/test/qunit.html b/resources/static/test/qunit.html
index 3e2ae0292..05fcf8726 100644
--- a/resources/static/test/qunit.html
+++ b/resources/static/test/qunit.html
@@ -58,23 +58,13 @@
     </div>
 
     <ul class="notifications">
-      <li class="notification emailsent">Email Sent</li> 
-      <li class="notification doh">doh</li> 
+      <li class="notification emailsent">Email Sent</li>
+      <li class="notification doh">doh</li>
     </ul>
 
-    <ul id="emailList"> 
+    <ul id="emailList">
     </ul>
 
-    <script type="text/html" id="templateError">
-      <div class="message">{{ action.title }}</div>
-    </script>
-
-    <script type="text/html" id="templateTooltip">
-      <div class="tooltip" id="createdTooltip">
-        {{ contents }}
-      </div>
-    </script>
-
     <script type="text/html" id="templateUser">
       <li>{{email}}</li>
     </script>
diff --git a/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js b/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js
index 2d0aa6964..a09787fac 100644
--- a/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js
+++ b/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/dialog/controllers/page_controller", "/dialog/controllers/authenticate_controller", function() {
+steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authenticate_controller", function() {
   "use strict";
 
   var controller,
diff --git a/resources/static/test/qunit/controllers/dialog_controller_unit_test.js b/resources/static/test/qunit/controllers/dialog_controller_unit_test.js
index bdfbe742b..fa602bbc4 100644
--- a/resources/static/test/qunit/controllers/dialog_controller_unit_test.js
+++ b/resources/static/test/qunit/controllers/dialog_controller_unit_test.js
@@ -53,8 +53,8 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
   function initController() {
     controller = el.dialog({
       window: {
-        setupChannel: function() { 
-          if (channelError) throw "Channel error";  
+        setupChannel: function() {
+          if (channelError) throw "Channel error";
         }
       }
     }).controller();
@@ -69,7 +69,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", "/dialog/con
     teardown: function() {
       controller.destroy();
       reset();
-    } 
+    }
   });
 
   test("initialization with channel error", function() {
diff --git a/resources/static/test/qunit/controllers/page_controller_unit_test.js b/resources/static/test/qunit/controllers/page_controller_unit_test.js
index 299b8ed00..8d898fc49 100644
--- a/resources/static/test/qunit/controllers/page_controller_unit_test.js
+++ b/resources/static/test/qunit/controllers/page_controller_unit_test.js
@@ -38,13 +38,14 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
   "use strict";
 
   var controller, el,
-      bodyTemplate = "testBodyTemplate.ejs",
-      waitTemplate = "wait.ejs";
+      bodyTemplate = "testBodyTemplate",
+      waitTemplate = "wait";
 
   function reset() {
     el = $("#controller_head");
     el.find("#formWrap .contents").html("");
     el.find("#wait .contents").html("");
+    $("#error").html("<div class='contents'></div>");
     el.find("#error .contents").html("");
   }
 
@@ -56,7 +57,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
     teardown: function() {
       controller.destroy();
       reset();
-    } 
+    }
   });
 
   test("page controller with no template causes no side effects", function() {
@@ -130,8 +131,8 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
         message: "Test message"
       }
     }).controller();
-   
-    controller.renderError("wait.ejs", {
+
+    controller.renderError("wait", {
       title: "error title",
       message: "error message"
     });
@@ -149,8 +150,8 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
         message: "Test message"
       }
     }).controller();
-   
-    controller.renderError("error.ejs", {
+
+    controller.renderError("error", {
       action: {
         title: "expanded action info",
         message: "expanded message"
@@ -180,7 +181,7 @@ steal.plugins("jquery").then("/dialog/controllers/page_controller", function() {
         message: "Test message"
       }
     }).controller();
-   
+
     // This is the medium level info.
     var func = controller.getErrorDialog({
       title: "medium level info error title",
diff --git a/resources/static/test/qunit/mocks/templates.js b/resources/static/test/qunit/mocks/templates.js
new file mode 100644
index 000000000..d1d8ee165
--- /dev/null
+++ b/resources/static/test/qunit/mocks/templates.js
@@ -0,0 +1,4 @@
+BrowserID.Templates = {
+  inMemoryTemplate: "<div id='templateInput'></div>"
+};
+
diff --git a/resources/static/test/qunit/pages/add_email_address_test.js b/resources/static/test/qunit/pages/add_email_address_test.js
index 4a4c4ed73..6bbca4c28 100644
--- a/resources/static/test/qunit/pages/add_email_address_test.js
+++ b/resources/static/test/qunit/pages/add_email_address_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/resources/network", "/pages/add_email_address", function() {
+steal.then("/pages/add_email_address", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -47,12 +47,14 @@ steal.plugins("jquery").then("/resources/network", "/pages/add_email_address", f
     setup: function() {
       network.setXHR(xhr);
       xhr.useResult("valid");
-      $(".error").stop().hide();
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       $(".website").text("");
     },
     teardown: function() {
       network.setXHR($);
-      $(".error").stop().hide();
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       $(".website").text("");
     }
   });
diff --git a/resources/static/test/qunit/pages/browserid_unit_test.js b/resources/static/test/qunit/pages/browserid_unit_test.js
index ab0e6ec97..7a5184845 100644
--- a/resources/static/test/qunit/pages/browserid_unit_test.js
+++ b/resources/static/test/qunit/pages/browserid_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/pages/page_helpers", "/pages/browserid", function() {
+steal.then("/pages/page_helpers", "/pages/browserid", function() {
   "use strict";
 
   module("/pages/browserid");
diff --git a/resources/static/test/qunit/pages/forgot_unit_test.js b/resources/static/test/qunit/pages/forgot_unit_test.js
index d49932e66..ab17c1d0e 100644
--- a/resources/static/test/qunit/pages/forgot_unit_test.js
+++ b/resources/static/test/qunit/pages/forgot_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/resources/network", "/resources/user", "/pages/forgot", function() {
+steal.then("/pages/forgot", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -46,13 +46,15 @@ steal.plugins("jquery").then("/resources/network", "/resources/user", "/pages/fo
   module("pages/forgot", {
     setup: function() {
       network.setXHR(xhr);
-      $(".error").stop().hide();
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       xhr.useResult("valid");
       bid.forgot();
     },
     teardown: function() {
       network.setXHR($);
-      $(".error").stop().hide();
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       $(".website").text("");
       bid.forgot.reset();
     }
diff --git a/resources/static/test/qunit/pages/manage_account_unit_test.js b/resources/static/test/qunit/pages/manage_account_unit_test.js
index 97a4b8a9b..8ac855e0c 100644
--- a/resources/static/test/qunit/pages/manage_account_unit_test.js
+++ b/resources/static/test/qunit/pages/manage_account_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/resources/network", "/pages/manage_account", function() {
+steal.then("/pages/manage_account", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -57,6 +57,7 @@ steal.plugins("jquery").then("/resources/network", "/pages/manage_account", func
       xhr.useResult("valid");
       user.setOrigin(TEST_ORIGIN);
       $("#emailList").empty();
+      $(".error").removeClass("error");
       $("#error").hide();
       mocks.document.location = "";
       storage.clear();
@@ -64,6 +65,7 @@ steal.plugins("jquery").then("/resources/network", "/pages/manage_account", func
     teardown: function() {
       network.setXHR($);
       $("#emailList").empty();
+      $(".error").removeClass("error");
       $("#error").hide();
     }
   });
diff --git a/resources/static/test/qunit/pages/page_helpers_unit_test.js b/resources/static/test/qunit/pages/page_helpers_unit_test.js
index 1ccd4958d..813b669be 100644
--- a/resources/static/test/qunit/pages/page_helpers_unit_test.js
+++ b/resources/static/test/qunit/pages/page_helpers_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/browserid", function() {
+steal.then(function() {
   "use strict";
 
   var pageHelpers = BrowserID.PageHelpers;
diff --git a/resources/static/test/qunit/pages/signin_unit_test.js b/resources/static/test/qunit/pages/signin_unit_test.js
index 28d907a7f..8f001abb4 100644
--- a/resources/static/test/qunit/pages/signin_unit_test.js
+++ b/resources/static/test/qunit/pages/signin_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/resources/user", "/pages/signin", function() {
+steal.then("/pages/signin", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -49,6 +49,7 @@ steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/re
   module("pages/signin", {
     setup: function() {
       network.setXHR(xhr);
+      $(".error").removeClass("error");
       $("#error").stop().hide();
       xhr.useResult("valid");
       docMock.location = "signin";
@@ -56,6 +57,7 @@ steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/re
     },
     teardown: function() {
       network.setXHR($);
+      $(".error").removeClass("error");
       $("#error").stop().hide();
       $("#error .message").remove();
       bid.signIn.reset();
diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js
index 57b2d6c07..e6aae6e93 100644
--- a/resources/static/test/qunit/pages/signup_unit_test.js
+++ b/resources/static/test/qunit/pages/signup_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/resources/user", "/pages/signup", function() {
+steal.then("/pages/signup", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -47,6 +47,7 @@ steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/re
   module("pages/signup", {
     setup: function() {
       network.setXHR(xhr);
+      $(".error").removeClass("error");
       $("#error").stop().hide();
       $(".notification").stop().hide();
       xhr.useResult("valid");
@@ -55,6 +56,7 @@ steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/resources/network", "/re
     },
     teardown: function() {
       network.setXHR($);
+      $(".error").removeClass("error");
       $("#error").stop().hide();
       $(".notification").stop().hide();
       $("#error .message").remove();
diff --git a/resources/static/test/qunit/pages/verify_email_address_test.js b/resources/static/test/qunit/pages/verify_email_address_test.js
index 1108be14c..e1d273a9a 100644
--- a/resources/static/test/qunit/pages/verify_email_address_test.js
+++ b/resources/static/test/qunit/pages/verify_email_address_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/resources/network", "/pages/verify_email_address", function() {
+steal.then("/pages/verify_email_address", function() {
   "use strict";
 
   var bid = BrowserID,
@@ -47,12 +47,16 @@ steal.plugins("jquery").then("/resources/network", "/pages/verify_email_address"
     setup: function() {
       network.setXHR(xhr);
       xhr.useResult("valid");
-      $("#error,.error").stop().hide();
+      $("body").removeClass("error");
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       $(".website").text("");
     },
     teardown: function() {
       network.setXHR($);
-      $("#error,.error").stop().hide();
+      $("body").removeClass("error");
+      $(".error").removeClass("error");
+      $("#error").stop().hide();
       $(".website").text("");
     }
   });
diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js
index fa8952941..c4a5bce55 100644
--- a/resources/static/test/qunit/qunit.js
+++ b/resources/static/test/qunit/qunit.js
@@ -1,41 +1,50 @@
-steal("/resources/browserid.js",
-      "/test/qunit/mocks/mocks.js",
-      "/test/qunit/mocks/xhr.js",
-      "/lib/ejs.js",
-      "/resources/browser-support.js",
-      "/resources/error-messages.js",
-      "/resources/error-display.js",
-      "/resources/storage.js",
-      "/resources/tooltip.js",
-      "/resources/validation.js",
-      "/lib/underscore-min.js"
-      )
-  .plugins("jquery",
-    "jquery/controller",
-    "jquery/controller/subscribe",
-    "funcunit/qunit")
-  .then("/lib/dom-jquery.js",
-        "pages/browserid_unit_test",
-        "pages/page_helpers_unit_test",
-        "include_unit_test",
-        "relay/relay_unit_test",
-        "pages/add_email_address_test",
-        "pages/verify_email_address_test",
-        "pages/forgot_unit_test",
-        "pages/signin_unit_test",
-        "pages/signup_unit_test",
-        "pages/manage_account_unit_test",
-        "resources/tooltip_unit_test",
-        "resources/error-display_unit_test",
-        "resources/channel_unit_test",
-        "resources/browser-support_unit_test",
-        "resources/validation_unit_test",
-        "resources/storage_unit_test",
-        "resources/network_unit_test",
-        "resources/user_unit_test",
-        "controllers/page_controller_unit_test",
-        "controllers/pickemail_controller_unit_test",
-        "controllers/dialog_controller_unit_test",
-        "controllers/checkregistration_controller_unit_test",
-        "controllers/authenticate_controller_unit_test");
+steal.plugins(
+      "jquery",
+      "jquery/controller",
+      "jquery/controller/subscribe",
+      "funcunit/qunit")
+  .then(
+      "/lib/underscore-min",
+      "/lib/ejs",
+      "/shared/browserid",
+      "/lib/dom-jquery",
+      "mocks/mocks",
+      "mocks/xhr",
+      "/shared/renderer",
+      "/shared/screens",
+      "/shared/browser-support",
+      "/shared/error-messages",
+      "/shared/error-display",
+      "/shared/storage",
+      "/shared/tooltip",
+      "/shared/network",
+      "/shared/user",
+      "/shared/validation",
+
+      "pages/browserid_unit_test",
+      "pages/page_helpers_unit_test",
+
+      "include_unit_test",
+      "relay/relay_unit_test",
+      "pages/add_email_address_test",
+      "pages/verify_email_address_test",
+      "pages/forgot_unit_test",
+      "pages/signin_unit_test",
+      "pages/signup_unit_test",
+      "pages/manage_account_unit_test",
+      "shared/renderer_unit_test",
+      "shared/screens_unit_test",
+      "shared/tooltip_unit_test",
+      "shared/error-display_unit_test",
+      "shared/browser-support_unit_test",
+      "shared/validation_unit_test",
+      "shared/storage_unit_test",
+      "shared/network_unit_test",
+      "shared/user_unit_test",
+      "resources/channel_unit_test",
+      "controllers/page_controller_unit_test",
+      "controllers/pickemail_controller_unit_test",
+      "controllers/dialog_controller_unit_test",
+      "controllers/checkregistration_controller_unit_test",
+      "controllers/authenticate_controller_unit_test");
 
diff --git a/resources/static/test/qunit/resources/channel_unit_test.js b/resources/static/test/qunit/resources/channel_unit_test.js
index e5c26c079..94c2df40a 100644
--- a/resources/static/test/qunit/resources/channel_unit_test.js
+++ b/resources/static/test/qunit/resources/channel_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/channel", function() {
+steal.then("/dialog/resources/channel", function() {
   var channel = BrowserID.Channel;
 
   var navMock = {
@@ -83,7 +83,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/channel", func
   test("window.setupChannel exists for legacy uses", function() {
     ok(typeof window.setupChannel, "function", "window.setupChannel exists for legacy uses");
   });
-  
+
   test("IFRAME channel with assertion", function() {
     channel.init({
       window: winMock,
@@ -138,7 +138,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/channel", func
 
   test("IFRAME channel with error on open", function() {
     var winMockWithoutRelay = $.extend(true, {}, winMock);
-    delete winMockWithoutRelay.opener.frames.browserid_relay_1234; 
+    delete winMockWithoutRelay.opener.frames.browserid_relay_1234;
 
     channel.init({
       window: winMockWithoutRelay,
diff --git a/resources/static/test/qunit/resources/browser-support_unit_test.js b/resources/static/test/qunit/shared/browser-support_unit_test.js
similarity index 97%
rename from resources/static/test/qunit/resources/browser-support_unit_test.js
rename to resources/static/test/qunit/shared/browser-support_unit_test.js
index 36a29531c..bbba6db90 100644
--- a/resources/static/test/qunit/resources/browser-support_unit_test.js
+++ b/resources/static/test/qunit/shared/browser-support_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then(function() {
+steal.then(function() {
   "use strict";
 
   var bid = BrowserID,
@@ -42,7 +42,7 @@ steal.plugins("jquery", "funcunit/qunit").then(function() {
       stubWindow,
       stubNavigator;
 
-  module("resources/browser-support", {
+  module("shared/browser-support", {
     setup: function() {
       // Hard coded goodness for testing purposes
       stubNavigator = {
diff --git a/resources/static/test/qunit/resources/error-display_unit_test.js b/resources/static/test/qunit/shared/error-display_unit_test.js
similarity index 78%
rename from resources/static/test/qunit/resources/error-display_unit_test.js
rename to resources/static/test/qunit/shared/error-display_unit_test.js
index 162aeee56..232641dae 100644
--- a/resources/static/test/qunit/resources/error-display_unit_test.js
+++ b/resources/static/test/qunit/shared/error-display_unit_test.js
@@ -34,27 +34,34 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/error-display", function() {
+steal.then(function() {
   "use strict";
 
   var bid = BrowserID,
       errorDisplay = bid.ErrorDisplay;
 
-  module("/resources/error-display", {
+  module("shared/error-display", {
     setup: function() {
+        $("#error").html("<div class='contents'><a href='#' id='openMoreInfo'>Open</a><div id='moreInfo' style='display:none'>Expanded Info</div></div>");
     },
     teardown: function() {
+      $("#error").hide();
     }
   });
 
-  test("can show an error", function() {
-    var target = $("#error .contents");
-    target.empty();
+  test("can initialize and open the error display", function() {
+    $("#error").show();
+    bid.ErrorDisplay.start("#error");
+    bid.ErrorDisplay.open();
 
-    errorDisplay.render(target, "#templateError", { action: { title: "Error Message" } });
+    setTimeout(function() {
+      ok($("#moreInfo").is(":visible"), "expanded info is visible");
+      start();
+    }, 100);
 
-    ok(target.html(), "Error has some contents");
+    stop();
   });
 
 
+
 });
diff --git a/resources/static/test/qunit/resources/network_unit_test.js b/resources/static/test/qunit/shared/network_unit_test.js
similarity index 99%
rename from resources/static/test/qunit/resources/network_unit_test.js
rename to resources/static/test/qunit/shared/network_unit_test.js
index 97dce2b7d..525ce1c01 100644
--- a/resources/static/test/qunit/resources/network_unit_test.js
+++ b/resources/static/test/qunit/shared/network_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/network", function() {
+steal.then(function() {
   "use strict";
 
   var testName,
@@ -108,7 +108,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/resources/network", function()
 
   var network = BrowserID.Network;
 
-  module("/resources/network", {
+  module("shared/network", {
     setup: function() {
       network.setXHR(xhr);
       xhr.useResult("valid");
diff --git a/resources/static/test/qunit/shared/renderer_unit_test.js b/resources/static/test/qunit/shared/renderer_unit_test.js
new file mode 100644
index 000000000..f33e59cc0
--- /dev/null
+++ b/resources/static/test/qunit/shared/renderer_unit_test.js
@@ -0,0 +1,82 @@
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*globals BrowserID: true, _: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 bid.
+ *
+ * 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.then("/test/qunit/mocks/templates", "/shared/renderer", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      renderer = bid.Renderer;
+
+  module("shared/renderer", {
+    setup: function() {
+
+    },
+
+    teardown: function() {
+
+    }
+  });
+
+  test("render template loaded using XHR", function() {
+    $("#formWrap .contents").empty();
+    $("#templateInput").remove();
+
+    renderer.render("#formWrap .contents", "testBodyTemplate");
+
+    ok($("#templateInput").length, "template written when loaded using XHR");
+  });
+
+  test("render template from memory", function() {
+    $("#formWrap .contents").empty();
+    $("#templateInput").remove();
+
+    renderer.render("#formWrap .contents", "inMemoryTemplate");
+
+    ok($("#templateInput").length, "template written when loaded from memory");
+  });
+
+  test("append template to element", function() {
+    $("#formWrap .contents").empty();
+    $("#templateInput").remove();
+
+    renderer.append("#formWrap", "inMemoryTemplate");
+
+    ok($("#formWrap > #templateInput").length && $("#formWrap > .contents"), "template appended to element instead of overwriting it");
+
+  });
+});
+
+
diff --git a/resources/static/test/qunit/shared/screens_unit_test.js b/resources/static/test/qunit/shared/screens_unit_test.js
new file mode 100644
index 000000000..70df97ee4
--- /dev/null
+++ b/resources/static/test/qunit/shared/screens_unit_test.js
@@ -0,0 +1,86 @@
+/*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.then(function() {
+  "use strict";
+
+  var bid = BrowserID,
+      screens = bid.Screens,
+      el;
+
+  module("shared/screens", {
+    setup: function() {
+
+    },
+
+    teardown: function() {
+      el.empty();
+    }
+  });
+
+  test("form", function() {
+    el = $("#formWrap .contents");
+    el.empty();
+    screens.form("testBodyTemplate");
+
+    ok($("#templateInput").length, "the template has been written");
+    equal($("body").hasClass("error"), false, "error class taken off of body");
+    equal($("body").hasClass("waiting"), false, "waiting class taken off of body");
+    equal($("body").hasClass("form"), true, "form class added to body");
+  });
+
+  test("wait", function() {
+    var el = $("#wait .contents");
+    el.empty();
+    screens.wait("testBodyTemplate");
+
+    ok($("#templateInput").length, "the template has been written");
+    equal($("body").hasClass("error"), false, "error class taken off of body");
+    equal($("body").hasClass("form"), false, "form class taken off of body");
+    equal($("body").hasClass("waiting"), true, "waiting class added to body");
+  });
+
+  test("error", function() {
+    var el = $("#error .contents");
+    el.empty();
+    screens.error("testBodyTemplate");
+
+    ok($("#templateInput").length, "the template has been written");
+    equal($("body").hasClass("waiting"), false, "waiting class taken off of body");
+    equal($("body").hasClass("form"), false, "form class taken off of body");
+    equal($("body").hasClass("error"), true, "error class added to body");
+  });
+});
diff --git a/resources/static/test/qunit/resources/storage_unit_test.js b/resources/static/test/qunit/shared/storage_unit_test.js
similarity index 98%
rename from resources/static/test/qunit/resources/storage_unit_test.js
rename to resources/static/test/qunit/shared/storage_unit_test.js
index 1f7e2ec79..0a6235380 100644
--- a/resources/static/test/qunit/resources/storage_unit_test.js
+++ b/resources/static/test/qunit/shared/storage_unit_test.js
@@ -34,10 +34,10 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/storage", function() {
+steal.then(function() {
   var storage = BrowserID.Storage;
 
-  module("storage", {
+  module("shared/storage", {
     setup: function() {
       storage.clear();
     },
diff --git a/resources/static/test/qunit/resources/tooltip_unit_test.js b/resources/static/test/qunit/shared/tooltip_unit_test.js
similarity index 89%
rename from resources/static/test/qunit/resources/tooltip_unit_test.js
rename to resources/static/test/qunit/shared/tooltip_unit_test.js
index 3070b0080..3dde6b3d3 100644
--- a/resources/static/test/qunit/resources/tooltip_unit_test.js
+++ b/resources/static/test/qunit/shared/tooltip_unit_test.js
@@ -34,13 +34,13 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/tooltip", function() {
+steal.then(function() {
   "use strict";
 
   var bid = BrowserID,
       tooltip = bid.Tooltip
 
-  module("/resources/tooltip", {
+  module("shared/tooltip", {
     setup: function() {
     },
     teardown: function() {
@@ -59,11 +59,6 @@ steal.plugins("jquery", "funcunit/qunit").then("/resources/tooltip", function()
       start();
     });
 
-    var el = $("#createdTooltip");
-    equal(el.length, 1, "one tooltip created");
-    var contents = el.html() || "";
-    equal(contents.indexOf("contents") === -1, true, "contents have been replaced");
-
     stop();
   });
 
diff --git a/resources/static/test/qunit/resources/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js
similarity index 99%
rename from resources/static/test/qunit/resources/user_unit_test.js
rename to resources/static/test/qunit/shared/user_unit_test.js
index a70a87633..66d46bd73 100644
--- a/resources/static/test/qunit/resources/user_unit_test.js
+++ b/resources/static/test/qunit/shared/user_unit_test.js
@@ -38,7 +38,7 @@ var jwk = require("./jwk");
 var jwt = require("./jwt");
 var jwcert = require("./jwcert");
 
-steal.plugins("jquery", "funcunit/qunit").then("/resources/user", function() {
+steal.then(function() {
   var lib = BrowserID.User,
       storage = BrowserID.Storage,
       xhr = BrowserID.Mocks.xhr,
@@ -86,7 +86,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/resources/user", function() {
     */
   }
 
-  module("resources/user", {
+  module("shared/user", {
     setup: function() {
       BrowserID.Network.setXHR(xhr);
       xhr.useResult("valid");
diff --git a/resources/static/test/qunit/resources/validation_unit_test.js b/resources/static/test/qunit/shared/validation_unit_test.js
similarity index 98%
rename from resources/static/test/qunit/resources/validation_unit_test.js
rename to resources/static/test/qunit/shared/validation_unit_test.js
index d834ba9ba..8db0dd50a 100644
--- a/resources/static/test/qunit/resources/validation_unit_test.js
+++ b/resources/static/test/qunit/shared/validation_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery", "funcunit/qunit").then("/resources/browserid", function() {
+steal.then(function() {
   "use strict";
 
   var bid = BrowserID,
@@ -46,7 +46,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/resources/browserid", function(
     tooltipShown = true;
   }
 
-  module("resources/validation", {
+  module("shared/validation", {
     setup: function() {
       origShowTooltip = bid.Tooltip.showTooltip;
       bid.Tooltip.showTooltip = showTooltip;
diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs
index ddc32e74f..b362ae940 100644
--- a/resources/views/dialog_layout.ejs
+++ b/resources/views/dialog_layout.ejs
@@ -46,11 +46,6 @@
       </div>
 
       <% if (useJavascript !== false) { %>
-          <script type="text/html" id="templateTooltip">
-            <div class="tooltip">
-              {{ contents }}
-            </div>
-          </script>
           <script type="text/javascript" src="/vepbundle"></script>
           <script type="text/javascript" src="steal/steal<%= production ? '.production' : '' %>.js?dialog"></script>
       <% } %>
diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs
index 2f8a3d009..1caa4fe8b 100644
--- a/resources/views/layout.ejs
+++ b/resources/views/layout.ejs
@@ -18,18 +18,25 @@
     <script src="/lib/jquery-1.6.2.min.js" type="text/javascript"></script>
     <script src="/lib/json2.js" type="text/javascript"></script>
     <script src="/lib/underscore-min.js" type="text/javascript"></script>
-    <script src="/resources/browserid-extensions.js" type="text/javascript"></script>
-    <script src="/resources/browserid.js" type="text/javascript"></script>
-    <script src="/resources/error-display.js" type="text/javascript"></script>
-    <script src="/resources/error-messages.js" type="text/javascript"></script>
+    <script src="/lib/ejs.js" type="text/javascript"></script>
+    <script src="/shared/browserid-extensions.js" type="text/javascript"></script>
+    <script src="/shared/browserid.js" type="text/javascript"></script>
+    <script src="/lib/dom-jquery.js" type="text/javascript"></script>
+
+    <script src="/shared/templates.js" type="text/javascript"></script>
+    <script src="/shared/renderer.js" type="text/javascript"></script>
+    <script src="/shared/error-display.js" type="text/javascript"></script>
+    <script src="/shared/screens.js" type="text/javascript"></script>
+    <script src="/shared/error-messages.js" type="text/javascript"></script>
+    <script src="/shared/storage.js" type="text/javascript"></script>
+    <script src="/shared/network.js" type="text/javascript"></script>
+    <script src="/shared/user.js" type="text/javascript"></script>
+    <script src="/shared/tooltip.js" type="text/javascript"></script>
+    <script src="/shared/validation.js" type="text/javascript"></script>
+
     <script src="/pages/page_helpers.js" type="text/javascript"></script>
     <script src="/pages/browserid.js" type="text/javascript"></script>
     <script src="/pages/index.js" type="text/javascript"></script>
-    <script src="/resources/storage.js" type="text/javascript"></script>
-    <script src="/resources/network.js" type="text/javascript"></script>
-    <script src="/resources/user.js" type="text/javascript"></script>
-    <script src="/resources/tooltip.js" type="text/javascript"></script>
-    <script src="/resources/validation.js" type="text/javascript"></script>
     <script src="/pages/add_email_address.js" type="text/javascript"></script>
     <script src="/pages/verify_email_address.js" type="text/javascript"></script>
     <script src="/pages/forgot.js" type="text/javascript"></script>
@@ -59,7 +66,7 @@
         </ul>
     </header>
 
-    <div id="error"></div>
+    <div id="error"><div class="contents"></div></div>
 
     <%- body %>
 
@@ -75,58 +82,5 @@
 
 </div>
 
-<script type="text/html" id="templateTooltip">
-  <div class="tooltip">
-    {{ contents }}
-  </div>
-</script>
-
-<script type="text/html" id="templateError">
-  <div>
-
-  <h2>We are very sorry, there has been an error!</h2>
-
-  <p>
-    To retry, you will have to reload the page and try again.  More info can be found by opening the expanded info below.
-  </p>
-
-  <a href="#" id="openMoreInfo">See more info</a>
-
-  <ul id="moreInfo">
-    {% if (typeof action !== "undefined") { %}
-      <li>
-        <strong id="action">Action: </strong>{{ action.title }}
-
-        {% if (action.message) { %}
-          <p>
-            {{ action.message }}
-          </p>
-        {% } %}
-      </li>
-    {% } %}
-
-    {% if (typeof network !== "undefined") { %}
-      <li>
-
-        <strong id="network">Network Info:</strong> {{ network.type }}: {{ network.url }}
-
-        <p>
-          <strong>Response Code - </strong> {{ network.textStatus }}
-        </p>
-
-        {% if (network.errorThrown) { %}
-          <p>
-            <strong>Error Type:</strong> {{ network.errorThrown }}
-          </p>
-        {% } %}
-
-      </li>
-
-    {% } %}
-
-  </ul>
-  </div>
-</script>
-
 </body>
 </html>
diff --git a/resources/views/relay.ejs b/resources/views/relay.ejs
index 53ce9739d..648ae21f6 100644
--- a/resources/views/relay.ejs
+++ b/resources/views/relay.ejs
@@ -14,7 +14,7 @@
         <script type='text/javascript'
           src='https://browserid.org/lib/jschannel.js'></script>
         <script type='text/javascript'
-          src='https://browserid.org/resources/browserid.js'></script>
+          src='https://browserid.org/shared/browserid.js'></script>
         <script type='text/javascript'
           src='https://browserid.org/relay/relay.js'></script>
       <% } %>
diff --git a/scripts/compress.sh b/scripts/compress.sh
index 69e8b934a..530dc9c3f 100755
--- a/scripts/compress.sh
+++ b/scripts/compress.sh
@@ -28,8 +28,18 @@ echo ''
 echo '****Building dialog HTML, CSS, and JS****'
 echo ''
 
+## This creates a combined templates file which is copied into
+## resources/templates.js and included into the minified bundle.
+
+cd dialog/views
+../../../../scripts/create_templates.js
+cd ../../
+cp shared/templates.js shared/templates.js.orig
+cp dialog/views/templates.js shared/templates.js
+
 steal/js dialog/scripts/build.js
 
+
 cd communication_iframe
 $UGLIFY < production.js > production.min.js
 mv production.min.js production.js
@@ -43,7 +53,7 @@ cat popup.css m.css > production.css
 $JAVA -jar $YUI_LOCATION production.css -o production.min.css
 
 cd ../../relay
-cat ../lib/jschannel.js ../resources/browserid.js relay.js > production.js
+cat ../lib/jschannel.js ../shared/browserid.js relay.js > production.js
 $UGLIFY < production.js > production.min.js
 mv production.min.js production.js
 
@@ -54,7 +64,7 @@ echo ''
 
 cd ../pages
 # re-minimize everything together
-cat ../lib/jquery-1.6.2.min.js ../lib/json2.js ../resources/browserid.js ../resources/error-display.js ../resources/error-messages.js page_helpers.js browserid.js ../lib/underscore-min.js ../resources/browserid-extensions.js ../resources/storage.js ../resources/network.js ../resources/user.js ../resources/tooltip.js ../resources/validation.js index.js add_email_address.js verify_email_address.js manage_account.js signin.js signup.js forgot.js > lib.js
+cat ../lib/jquery-1.6.2.min.js ../lib/json2.js ../lib/underscore-min.js ../lib/ejs.js ../shared/browserid-extensions.js ../shared/browserid.js ../lib/dom-jquery.js ../shared/templates.js ../shared/renderer.js ../shared/error-display.js ../shared/screens.js ../shared/error-messages.js ../shared/storage.js ../shared/network.js ../shared/user.js ../shared/tooltip.js ../shared/validation.js page_helpers.js browserid.js index.js add_email_address.js verify_email_address.js forgot.js manage_account.js signin.js signup.js > lib.js
 $UGLIFY < lib.js > lib.min.js
 
 cd ../css
diff --git a/scripts/create_templates.js b/scripts/create_templates.js
new file mode 100755
index 000000000..4595d2333
--- /dev/null
+++ b/scripts/create_templates.js
@@ -0,0 +1,24 @@
+#!/usr/bin/env node
+
+const fs = require("fs");
+
+var dir = process.env.TEMPLATE_DIR || process.cwd();
+console.log(dir);
+
+var templates = {};
+
+
+fs.readdir(dir, function(err, fileNames) {
+  for(var index = 0, max = fileNames.length; index < max; index++) {
+    var fileName = fileNames[index];
+    if(fileName.match(/\.ejs$/)) {
+      var templateName = fileName.replace(/\.ejs/, '');
+      templates[templateName] = fs.readFileSync(dir + "/" + fileName, "utf8")
+    }
+  }
+
+  var templateData = "BrowserID.Templates =" + JSON.stringify(templates) + ";";
+
+  fs.writeFileSync(dir + "/templates.js", templateData, "utf8");
+});
+
-- 
GitLab