Refactor and simplify edit app, and optimize app resource loading and caching.

git-svn-id: https://svn.apache.org/repos/asf/tuscany/sca-cpp/trunk@1188045 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/.gitignore b/.gitignore
index c2b43cc..93663ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,6 +59,7 @@
 *config.js
 all.js
 intro*.png
+intro*.b64
 depcomp
 install-sh
 ltmain.sh
diff --git a/modules/edit/Makefile.am b/modules/edit/Makefile.am
index c749415..55eca3e 100644
--- a/modules/edit/Makefile.am
+++ b/modules/edit/Makefile.am
@@ -20,7 +20,7 @@
 moddir = $(prefix)/modules/edit
 dist_mod_SCRIPTS = start stop ssl-start mkapplinks
 
-nobase_dist_mod_DATA = edit.composite *.py htdocs/*.html htdocs/*.js htdocs/*.cmf htdocs/*.ico htdocs/*.png htdocs/*.txt htdocs/account/*.html htdocs/create/*.html htdocs/clone/*.html htdocs/data/*.html htdocs/app/*.html htdocs/store/*.html htdocs/stats/*.html htdocs/graph/*.html htdocs/graph/*.js htdocs/page/*.html htdocs/page/*.js htdocs/login/*.html htdocs/logout/*.html htdocs/public/*.html htdocs/public/*.png palettes/*/palette.composite apps/*/app.composite apps/*/app.stats apps/*/htdocs/app.html dashboards/*/user.apps store/*/store.apps
-EXTRA_DIST = edit.composite *.py htdocs/*.html htdocs/*.js htdocs/*.cmf htdocs/*.ico htdocs/*.png htdocs/*.txt htdocs/account/*.html htdocs/create/*.html htdocs/clone/*.html htdocs/data/*.html htdocs/app/*.html htdocs/store/*.html htdocs/stats/*.html htdocs/graph/*.html htdocs/graph/*.js htdocs/page/*.html htdocs/page/*.js htdocs/login/*.html htdocs/logout/*.html htdocs/public/*.html htdocs/public/*.png palettes/*/palette.composite apps/*/app.composite apps/*/app.stats apps/*/htdocs/app.html dashboards/*/user.apps store/*/store.apps
+nobase_dist_mod_DATA = edit.composite *.py htdocs/*.html htdocs/*.js htdocs/*.cmf htdocs/*.ico htdocs/home/*.png htdocs/home/*.b64 htdocs/*.txt htdocs/account/*.html htdocs/create/*.html htdocs/clone/*.html htdocs/app/*.html htdocs/store/*.html htdocs/stats/*.html htdocs/graph/*.html htdocs/home/*.html htdocs/page/*.html htdocs/login/*.html htdocs/logout/*.html htdocs/notauth/*.html htdocs/notfound/*.html htdocs/oops/*.html htdocs/public/*.html htdocs/public/*.png htdocs/public/*.b64 palettes/*/palette.composite apps/*/app.composite apps/*/app.stats apps/*/htdocs/app.html dashboards/*/user.apps store/*/store.apps
+EXTRA_DIST = edit.composite *.py htdocs/*.html htdocs/*.js htdocs/*.cmf htdocs/*.ico htdocs/home/*.png htdocs/home/*.b64 htdocs/*.txt htdocs/account/*.html htdocs/create/*.html htdocs/clone/*.html htdocs/app/*.html htdocs/store/*.html htdocs/stats/*.html htdocs/graph/*.html htdocs/home/*.html htdocs/page/*.html htdocs/login/*.html htdocs/logout/*.html htdocs/notauth/*.html htdocs/notfound/*.html htdocs/oops/*.html htdocs/public/*.html htdocs/public/*.png htdocs/public/*.b64 palettes/*/palette.composite apps/*/app.composite apps/*/app.stats apps/*/htdocs/app.html dashboards/*/user.apps store/*/store.apps
 
 endif
diff --git a/modules/edit/accounts.py b/modules/edit/accounts.py
index 6c37fc5..600134c 100644
--- a/modules/edit/accounts.py
+++ b/modules/edit/accounts.py
@@ -22,7 +22,7 @@
 def accountid(user):
     return ("'" + user.id(), "'user.account")
 
-# Get the user's account
+# Get the current user's account
 def get(id, user, cache):
     account = cache.get(accountid(user))
     if isNil(account):
diff --git a/modules/edit/htdocs/account/index.html b/modules/edit/htdocs/account/index.html
index ef1deb0..7fef6c1 100644
--- a/modules/edit/htdocs/account/index.html
+++ b/modules/edit/htdocs/account/index.html
@@ -17,26 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Account</title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr>
 <td><h2><span id="h1"></span><span id="userNameHeader"></span></h2></td>
@@ -53,7 +35,7 @@
 <form id="userForm">
 <table style="width: 100%;">
 <tr><tr><td><b>Photo:</b></td></tr>
-<tr><td><img src="/public/app.png" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
+<tr><td><img id="userimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>Name:</b></td></tr>
 <tr><td><input type="text" id="userTitle" size="30" placeholder="Enter your name" style="width: 300px;"/></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
@@ -97,29 +79,22 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Init service references
 var editWidget = sca.component("EditWidget");
 var user= sca.defun(sca.reference(editWidget, "user"), "id");
 var accounts = sca.reference(editWidget, "accounts");
 
-// Get the user name
-var username = '';
-try {
-    username = user.id()
-} catch(e) {}
-
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - Account - ' + username;
-$('userNameHeader').innerHTML = username;
+document.title = ui.windowtitle(location.hostname) + ' - Account';
 
-// Load the menu bar
-displaymenu();
+// Set images
+$('userimg').src = ui.b64img(appcache.get('/public/user.b64'));
 
 /**
  * The current account entry and corresponding saved XML content.
  */
+var username;
 var accountentry;
 var savedaccountentryxml = '';
 
@@ -134,7 +109,9 @@
             return false;
 
         accountentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name));
+        username = cadr(assoc("'id", cdr(accountentry)));
         var title = cadr(assoc("'title", cdr(accountentry)));
+        $('userNameHeader').innerHTML = username;
         $('userTitle').value = title;
 
         var content = cadr(assoc("'content", cdr(accountentry)));
@@ -170,6 +147,8 @@
  * Save the user's account.
  */
 function save(entryxml) {
+    if (isNil(username))
+        return false;
     $('saveStatus').innerHTML = 'Saving';
     savedaccountentryxml = entryxml;
     accounts.put(username, savedaccountentryxml, function(e) {
@@ -228,14 +207,8 @@
 };
 
 // Get the user's account
-getaccount(username);
+getaccount();
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/app/cache-manifest.cmf b/modules/edit/htdocs/app/cache-manifest.cmf
index 1d9464b..4f3358e 100644
--- a/modules/edit/htdocs/app/cache-manifest.cmf
+++ b/modules/edit/htdocs/app/cache-manifest.cmf
@@ -1,25 +1,17 @@
 CACHE MANIFEST
 
-# Common resources
-/all-min.js
-/ui-min.css
+# Version 5
 
 # App resources
 /
-/data/index.html
 /favicon.ico
-/footconfig.js
-/frame.html
-/headconfig.js
-/public/app.png
+/notauth/
+/notfound/
+/notyet/
+/oops/
 /public/iframe.html
 /public/img.png
-/public/notauth.html
-/public/notfound.html
-/public/notyet.html
-/public/oops.html
 /public/touchicon.png
-/robots.txt
 
 NETWORK:
 *
diff --git a/modules/edit/htdocs/app/frame.html b/modules/edit/htdocs/app/frame.html
deleted file mode 100644
index 3509e1e..0000000
--- a/modules/edit/htdocs/app/frame.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title></title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-</head>
-<body>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/app/index.html b/modules/edit/htdocs/app/index.html
index f62cc5d..cb89cd0 100644
--- a/modules/edit/htdocs/app/index.html
+++ b/modules/edit/htdocs/app/index.html
@@ -23,36 +23,77 @@
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
 <meta name="apple-mobile-web-app-capable" content="yes"/>
 <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<script type="text/javascript">
-document.title = window.location.hostname.split('.')[0];
-</script>
 <link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-<div id="bodydiv" class="bodydiv">
-
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="app">
-<iframe id="appframe" style="position: relative; height: 5000px; width: 100%;" scrolling="no" frameborder="0" src="/frame.html"></iframe>
-</div>
-
-<div id="appbuffer" style="visibility: hidden">
-</div>
-
+<base href="/"/>
 <script type="text/javascript">
-ui.initbody();
+
+window.appcache = {};
 
 /**
- * The main app div.
+ * Get and cache a resource.
  */
-var appdiv = $('app');
-var appframe = $('appframe');
-var appbody;
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="mainbodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="content">
+</div>
+
+<script type="text/javascript">
+
+// Set the document title
+document.title = location.hostname.split('.')[0];
+
+/**
+ * The main page div.
+ */
+var contentdiv = $('content');
 
 /**
  * Start, stop, timer, animation and location components.
@@ -64,6 +105,59 @@
 var locationcomp = sca.httpclient('location', '/location');
 
 /**
+ * Pre-fetch app resources.
+ */
+var appresources = [
+    ['/all-min.js'],
+    ['/app.html'],
+    ['/ui-min.css'],
+    ['/footconfig.js'],
+    ['/headconfig.js'],
+];
+
+/**
+ * Handle application cache events.
+ */
+applicationCache.addEventListener('checking', function(e) {
+    //log('appcache checking', e);
+}, false);
+applicationCache.addEventListener('error', function(e) {
+    //log('appcache error', e);
+}, false);
+applicationCache.addEventListener('noupdate', function(e) {
+    //log('appcache noupdate', e);
+}, false);
+applicationCache.addEventListener('downloading', function(e) {
+    //log('appcache downloading', e);
+}, false);
+applicationCache.addEventListener('progress', function(e) {
+    //log('appcache progress', e);
+}, false);
+applicationCache.addEventListener('updateready', function(e) {
+    //log('appcache updateready', e);
+    applicationCache.swapCache();
+    //log('appcache swapped', e);
+}, false);
+applicationCache.addEventListener('cached', function(e) {
+    //log('appcache cached', e);
+    map(function(res) {
+        appcache.get(res[0]);
+    }, appresources);
+}, false);
+
+/**
+ * Handle network offline/online events.
+ */
+window.addEventListener('offline', function(e) {
+      //log('going offline');
+}, false);
+window.addEventListener('online', function(e) {
+      //log('going online');
+}, false);
+
+//log(navigator.onLine? 'online' : 'offline');
+
+/**
  * Find a named value in a tree of elements. The value name is given
  * as a list of ids.
  */
@@ -172,12 +266,12 @@
 
                 // Define the stylesheet
                 if (s != '') {
-                    var esheet = appframe.contentDocument.getElementById('style_' + e.id);
+                    var esheet = contentdiv.getElementById('style_' + e.id);
                     if (isNil(esheet)) {
                         var nesheet = document.createElement('style');
                         nesheet.id = 'style_' + e.id;
                         nesheet.type = 'text/css';
-                        appframe.contentDocument.getElementsByTagName('head')[0].appendChild(nesheet);
+                        contentdiv.getElementsByTagName('head')[0].appendChild(nesheet);
                         nesheet.innerHTML = s;
                     } else {
                         esheet.innerHTML = s;
@@ -294,7 +388,7 @@
         return e;
     }
 
-    map(updatewidget, filter(function(e) { return !isNil(e.id) && e.id.substring(0, 5) != 'page:'; }, nodeList(ui.elementByID(appbody, 'page').childNodes)));
+    map(updatewidget, filter(function(e) { return !isNil(e.id) && e.id.substring(0, 5) != 'page:'; }, nodeList(ui.elementByID(contentdiv, 'page').childNodes)));
     return true;
 }
 
@@ -474,16 +568,10 @@
         }
 
         // Get the component app data
-        var doc = getdoc(startcomp, 'start', window.location.search);
-
-        // Prepare app HTML page
-        appbody = appframe.contentDocument.body;
-        var appbufferbody = $('appbufferframe').contentDocument.body;
-        appbody.innerHTML = appbufferbody.innerHTML;
-        appbufferbody.innerHTML = '';
+        var doc = getdoc(startcomp, 'start', location.search);
 
         // Setup the widgets
-        map(setupwidget, filter(function(e) { return !isNil(e.id); }, nodeList(ui.elementByID(appbody, 'page').childNodes)));
+        map(setupwidget, filter(function(e) { return !isNil(e.id); }, nodeList(ui.elementByID(contentdiv, 'page').childNodes)));
 
         // Display data on the page
         displaypage(doc);
@@ -513,7 +601,7 @@
         return append(nodeList(n.childNodes), reduce(append, mklist(), map(childrenList, nodeList(n.childNodes))));
     }
 
-    var args = map(queryarg, filter(function(e) { return !isNil(e.id) && !isNil(inputvalue(e)); }, childrenList(ui.elementByID(appbody, 'page'))));
+    var args = map(queryarg, filter(function(e) { return !isNil(e.id) && !isNil(inputvalue(e)); }, childrenList(ui.elementByID(contentdiv, 'page'))));
 
     // Append current location properties if known
     if (!isNil(geoposition)) {
@@ -677,12 +765,35 @@
     return true;
 }
 
-// Load the app frame
-$('appbuffer').innerHTML = '<iframe id="appbufferframe" style="position: relative; height: 5000px; width: 100%;" scrolling="no" frameborder="0" src="app.html" onload="getpagedata()"></iframe>';
+// Load the app page
+var appcontent = appcache.get('/app.html');
+contentdiv.innerHTML = appcontent;
+
+// Merge in the app data
+getpagedata();
+
+/**
+ * Document load post processing.
+ */
+function onload() {
+    //log('onload');
+
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+    return true;
+}
+
 </script>
 
 <div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
 </div>
 
 </div>
diff --git a/modules/edit/htdocs/cache-manifest.cmf b/modules/edit/htdocs/cache-manifest.cmf
index 8e6e666..4f3358e 100644
--- a/modules/edit/htdocs/cache-manifest.cmf
+++ b/modules/edit/htdocs/cache-manifest.cmf
@@ -1,39 +1,17 @@
 CACHE MANIFEST
 
-# Version 2
-
-# Common resources
-/all-min.js
-/ui-min.css
+# Version 5
 
 # App resources
 /
-/account/
-/app/
-/clone/
-/data/
-/create/
 /favicon.ico
-/footconfig.js
-/graph/graph.js
-/graph/
-/headconfig.js
-/home.png
-/menu.js
-/page/
-/page/page.js
-/public/app.png
-/public/grid72.png
+/notauth/
+/notfound/
+/notyet/
+/oops/
 /public/iframe.html
 /public/img.png
-/public/notauth.html
-/public/notfound.html
-/public/notyet.html
-/public/oops.html
 /public/touchicon.png
-/robots.txt
-/stats/
-/store/
 
 NETWORK:
 *
diff --git a/modules/edit/htdocs/clone/index.html b/modules/edit/htdocs/clone/index.html
index 29ebe1c..38b4ede 100644
--- a/modules/edit/htdocs/clone/index.html
+++ b/modules/edit/htdocs/clone/index.html
@@ -17,26 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title></title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr>
 <td><h2><span id="h1"></span><span id="appNameHeader"></span></h2></td>
@@ -54,7 +36,7 @@
 <tr><td><b>New App Name:</b></td></tr>
 <tr><td><input type="text" id="appName" size="15" autocapitalize="off" placeholder="Your app name"/>&nbsp;<span id="appDomain"></span></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr>
-<tr><td><img src="/public/app.png" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
+<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
 <tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
@@ -69,20 +51,17 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Get the app name
-var appname = ui.fragmentParams()['app'];
-if (isNil(appname))
-    window.open('/', '_self');
+var appname = ui.fragmentParams(location)['app'];
 
 /**
  * Return the link to an app.
  */
 function applink(appname) {
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/';
@@ -91,17 +70,17 @@
 
 // Set page titles
 var tclone = isNil(config.clone)? 'Clone' : config.clone;
-document.title = windowtitle(window.location.hostname) + ' - ' + tclone + ' - ' + appname;
+document.title = ui.windowtitle(location.hostname) + ' - ' + tclone + ' - ' + appname;
 $('appNameHeader').innerHTML = '<a href=\"' + applink(appname) + '\" target=\"' + '_blank' + '\">' + appname + '</a>';
 $('th').innerHTML = tclone + ' this App';
 $('cloneAppOKButton').value = tclone;
 $('cloneAppOKButton').title = tclone + ' this app';
 
-// Load the menu bar
-displaymenu();
+// Set images
+$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
 
 // Init form
-$('appDomain').innerHTML = '.' + window.location.hostname;
+$('appDomain').innerHTML = '.' + location.hostname;
 
 // Init service references
 var editWidget = sca.component("EditWidget");
@@ -152,7 +131,7 @@
             return false;
 
         // Open it in the page editor
-        ui.navigate('/page/#app=' + name, '_self');
+        ui.navigate('/#view=page&app=' + name, '_view');
         return false;
     });
     return false;
@@ -162,18 +141,12 @@
  * Cancel cloning an app.
  */
 $('cloneAppCancelButton').onclick = function() {
-    ui.navigate('/stats/#app=' + appname, '_self');
+    history.back();
 };
 
 // Get the current app
 getapp(appname);
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/create/index.html b/modules/edit/htdocs/create/index.html
index 575016a..35c7733 100644
--- a/modules/edit/htdocs/create/index.html
+++ b/modules/edit/htdocs/create/index.html
@@ -17,26 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Create App</title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr><td><h2><span id="h1"></span></h2></td></tr>
 </table>
@@ -52,7 +34,7 @@
 <tr><td><b>App Name:</b></td></tr>
 <tr><td><input type="text" id="appName" size="15" autocapitalize="off" placeholder="Your app name"/>&nbsp;<span id="appDomain"></span></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr>
-<tr><td><img src="/public/app.png" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
+<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
 <tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
@@ -67,17 +49,16 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - Create App';
-$('h1').innerHTML = hometitle(window.location.hostname);
+document.title = ui.windowtitle(location.hostname) + ' - Create App';
+$('h1').innerHTML = ui.hometitle(location.hostname);
 
-// Load the menu bar
-displaymenu();
+// Set images
+$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
 
 // Init form
-$('appDomain').innerHTML = '.' + window.location.hostname;
+$('appDomain').innerHTML = '.' + location.hostname;
 
 // Init service references
 var editWidget = sca.component("EditWidget");
@@ -101,7 +82,7 @@
             return false;
 
         // Open it in the page editor
-        ui.navigate('/page/#app=' + name, '_self');
+        ui.navigate('/#view=page&app=' + name, '_view');
         return false;
     });
     return false;
@@ -111,15 +92,9 @@
  * Cancel creating an app.
  */
 $('createAppCancelButton').onclick = function() {
-    return ui.navigate('/store/', '_self');
+    history.back();
 };
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/data/index.html b/modules/edit/htdocs/data/index.html
deleted file mode 100644
index 23b8668..0000000
--- a/modules/edit/htdocs/data/index.html
+++ /dev/null
@@ -1,105 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>View</title>
-<script type="text/javascript">
-var cn = window.location.search.substring(1).split('=')[1];
-document.title = 'View - ' + window.location.hostname.split('.')[0] + '/' + cn;
-</script>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css">
-<script type="text/javascript" src="/all-min.js"></script>
-</head>  
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-
-<div id="bodydiv" style="position: absolute; top: 0px; left: 0px; right: 0px;">
-<div id="compLinkHeader" style="margin-top: 4px; margin-bottom: 4px;"></div>
-<div id="datadiv" style="position: relative; left: 0px; right: 0px;">
-</div>
-
-<script type="text/javascript">
-ui.initbody();
-
-// Get the component name
-var cname = ui.fragmentParams()['component'];
-
-/**
- * The current component.
- */
-var comp = sca.component(cname);
-
-/**
- * Display an HTML element.
- */
-function display(e) {
-    $('datadiv').innerHTML = e;
-    return true;
-}
-
-/**
- * Convert data to an HTML table.
- */
-function datatable(e) {
-    return ui.datatable(e);
-}
-
-/**
- * Wrap a document in an HTML table.
- */
-function mkdoctable(doc) {
-    var tr = '<tr><td class="datatdl">' + 'value' + '</td>' + '<td class="datatdr">' + doc + '</td></tr>';
-    return '<table class="datatable ' + (window.name == 'previewFrame'? ' databg' : '') + '" style="width: 100%;">' + tr + '</table>';
-}
-
-/**
- * Get and display the contents of the current component.
- */
-function getdata() {
-    return comp.getnocache('', function(doc) {
-
-        // Stop now if we didn't the doc
-        if (doc == null)
-            return false;
-
-        if (json.isJSON(mklist(doc)))
-            return display(datatable(json.readJSON(mklist(doc))));
-
-        if (atom.isATOMEntry(mklist(doc)))
-            return display(datatable(atom.readATOMEntry(mklist(doc))));
-
-        if (atom.isATOMFeed(mklist(doc)))
-            return display(datatable(atom.readATOMFeed(mklist(doc))));
-
-        return display(mkdoctable('<iframe style="width: 100%; height: 5000px;" scrolling="no" frameborder="0" src="' + comp.uri + '"/>'));
-    });
-}
-
-getdata();
-</script>
-
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
-</div>
-</body>
-</html>
diff --git a/modules/edit/htdocs/graph/graph.js b/modules/edit/htdocs/graph/graph.js
deleted file mode 100644
index d459a4b..0000000
--- a/modules/edit/htdocs/graph/graph.js
+++ /dev/null
@@ -1,1778 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
- */
-
-/**
- * SVG composite rendering functions.
- */
-
-var graph = {};
-
-/**
- * Basic colors
- */
-graph.colors = {};
-graph.colors.black = '#000000';
-graph.colors.blue = '#0000ff';
-graph.colors.cyan = '#00ffff';
-graph.colors.gray = '#808080'
-graph.colors.lightgray = '#dcdcdc'
-graph.colors.green = '#00ff00';
-graph.colors.magenta = '#ff00ff';
-graph.colors.orange = '#ffa500';
-graph.colors.pink = '#ffc0cb';
-graph.colors.purple = '#800080';
-graph.colors.red = '#ff0000';
-graph.colors.white = '#ffffff';
-graph.colors.yellow = '#ffff00';
-graph.colors.link = '#598edd';
-
-graph.colors.orange1 = '#ffd666';
-graph.colors.green1 = '#bbe082';
-graph.colors.blue1 = '#66dbdf';
-graph.colors.yellow1 = '#fdf57a';
-graph.colors.cyan1 = '#e6eafb';
-graph.colors.lightgray1 = '#eaeaea'
-graph.colors.pink1 = '#ffd9e0';
-graph.colors.red1 = '#d03f41';
-graph.colors.white1 = '#ffffff';
-
-graph.colors.orange2 = '#ffbb00';
-graph.colors.green2 = '#96d333';
-//graph.colors.blue2 = '#0d7cc1';
-graph.colors.blue2 = '#00c3c9';
-graph.colors.red2 = '#d03f41';
-graph.colors.yellow2 = '#fcee21';
-graph.colors.magenta2 = '#c0688a';
-graph.colors.cyan2 = '#d5dcf9';
-graph.colors.lightgray2 = '#dcdcdc'
-graph.colors.pink2 = '#ffc0cb';
-graph.colors.white2 = '#ffffff';
-
-graph.colors.orange3 = '#ffc700';
-graph.colors.green3 = '#92e120';
-graph.colors.blue3 = '#008fd1';
-graph.colors.yellow3 = '#fdf400';
-graph.colors.cyan3 = '#b4d3fd';
-graph.colors.lightgray3 = '#e3e3e3'
-graph.colors.pink3 = '#da749b';
-graph.colors.red3 = '#ed3f48';
-graph.colors.white3 = '#ffffff';
-
-/**
- * Default positions and sizes.
- */
-var palcx = 2500;
-var proxcx = 20;
-var proxcy = 20;
-var buttoncx = 55;
-var buttoncy = 23;
-var curvsz = 4;
-var tabsz = 2;
-var titlex = 4;
-var titley = 11;
-var titlesp = 3;
-var titlew = ui.isMobile()? -2 : 0;
-
-/**
- * SVG rendering functions.
- */
-
-graph.svgns='http://www.w3.org/2000/svg';
-
-/**
- * Make an SVG graph.
- */
-graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
-
-    // Create a div element to host the graph
-    var div = document.createElement('div');
-    div.id = 'svgdiv';
-    div.style.position = 'absolute';
-    div.style.left = ui.pixpos(pos.xpos() + cdiv.offsetLeft);
-    div.style.top = ui.pixpos(pos.ypos() + cdiv.offsetTop);
-    //div.style.overflow = 'hidden';
-    cdiv.appendChild(div);
-
-    // Create SVG element
-    var svg = document.createElementNS(graph.svgns, 'svg');
-    svg.style.height = ui.pixpos(5000);
-    svg.style.width = ui.pixpos(5000);
-    div.appendChild(svg);
-
-    // Track element dragging and selection
-    graph.dragging = null;
-    graph.dragged = false;
-    graph.moverenderer = null;
-    graph.selected = null;
-    cvalue.disabled = true;
-    ccopy.disabled = true;
-    cdelete.disabled = true;
-
-    /**
-     * Find the first draggable element in a hierarchy of elements.
-     */
-    function draggable(n) {
-        if (n == div || n == svg || n == null)
-            return null;
-        if (n.nodeName == 'g' && !isNil(n.id) && n.id != '')
-            return n;
-        return draggable(n.parentNode);
-    }
-
-    /**
-     * Handle a mouse down or touch start event.
-     */
-    function onmousedown(e) {
-
-        // Remember mouse or touch position
-        var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
-        graph.downX = pos.screenX;
-        graph.downY = pos.screenY;
-        graph.moveX = pos.screenX;
-        graph.moveY = pos.screenY;
-
-        // Engage the click component selection right away
-        // on mouse controlled devices
-        if (typeof e.touches == 'undefined')
-            onclick(e);
-
-        // Find and remember draggable component
-        var dragging = draggable(e.target);
-        if (dragging == null || dragging != graph.selected)
-            return true;
-        graph.dragging = dragging;
-        graph.dragged = false;
-
-        // Remember current drag position
-        graph.dragX = pos.screenX;
-        graph.dragY = pos.screenY;
-
-        e.preventDefault();
-        return true;
-    };
-
-    if (!ui.isMobile()) {
-        div.onmousedown = function(e) {
-            //log('onmousedown');
-            var suspend = svg.suspendRedraw(10);
-            var r = onmousedown(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    } else {
-        div.ontouchstart = function(e) {
-            //log('ontouchstart');
-
-            // Clear current move renderer if it's running
-            if (!isNil(graph.moverenderer)) {
-                clearInterval(graph.moverenderer);
-                graph.moverenderer = null;
-            }
-
-            var suspend = svg.suspendRedraw(10);
-            var r = onmousedown(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    }
-
-    /**
-     * Handle a mouse up or touch end event.
-     */
-    function onmouseup(e) {
-
-        // Engage the click component selection now on touch devices
-        if (ui.isMobile()) {
-            if (!graph.dragged && graph.moveX == graph.downX && graph.moveY == graph.downY)
-                return onclick(e);
-        }
-
-        // Stop here if the component was not dragged
-        if (graph.dragging == null)
-            return true;
-        if (!graph.dragged) {
-            graph.dragging = null;
-            return true;
-        }
-
-        if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
-
-            // Add new dragged component to the composite
-            if (isNil(graph.dragging.compos)) {
-                var compos = scdl.composite(svg.compos);
-                setElement(compos, graph.sortcompos(graph.addcomps(mklist(graph.dragging.comp), compos)));
-                graph.dragging.compos = svg.compos;
-            }
-
-            // Update component position
-            setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, svg)));
-
-            // Wire component to neighboring reference
-            if (!isNil(graph.dragging.svcpos)) {
-                var compos = scdl.composite(svg.compos);
-                setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, svg))));
-            }
-
-            // Snap top level component position to grid
-            if (graph.dragging.parentNode == svg) {
-                var gpos = graph.relpos(graph.dragging);
-                setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.xpos()), graph.gridsnap(gpos.ypos()))));
-            }
-        }
-
-        // Forget current dragged component
-        graph.dragging = null;
-        graph.dragged = false;
-
-        // Refresh the composite
-        //log('onmouseup refresh');
-        var nodes = graph.refresh(svg);
-
-        // Reselected the previously selected component
-        if (!isNil(graph.selected)) {
-            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
-            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-            // Trigger component select event
-            svg.oncompselect(graph.selected);
-        }
-
-        // Trigger composite change event
-        svg.oncomposchange(false);
-        return true;
-    };
-
-    if (!ui.isMobile()) {
-        div.onmouseup = function(e) {
-            //log('onmouseup');
-            var suspend = svg.suspendRedraw(10);
-            var r = onmouseup(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    } else {
-        div.ontouchend = function(e) {
-            //log('ontouchend');
-
-            // Clear current move renderer if it's running
-            if (!isNil(graph.moverenderer)) {
-                clearInterval(graph.moverenderer);
-                graph.moverenderer = null;
-            }
-
-            var suspend = svg.suspendRedraw(10);
-            var r = onmouseup(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    }
-
-    /**
-     * Handle a mouse or touch click event.
-     */
-    function onclick(e) {
-        //log('onclick logic');
-
-        // Find selected component
-        var selected = draggable(e.target);
-        if (selected == null) {
-            if (graph.selected != null) {
-
-                // Reset current selection
-                graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
-                graph.selected = null;
-
-                // Trigger component select event
-                svg.oncompselect(null);
-            }
-
-            // Dismiss the palette
-            if (e.target == div || e.target == svg && ui.numpos(div.style.left) != (palcx * -1))
-            	div.style.left = ui.pixpos(palcx * -1);
-
-            return true;
-        }
-
-        // Ignore multiple click events
-        if (selected == graph.selected)
-            return true;
-        if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(div.style.left) != 0)
-            return true;
-
-        // Deselect previously selected component
-        graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
-
-        // Clone component from the palette
-        if (selected.id.substring(0, 8) == 'palette:') {
-            var compos = scdl.composite(svg.compos);
-            var comp = graph.clonepalette(selected, compos, svg);
-            setElement(compos, graph.sortcompos(graph.addcomps(mklist(comp), compos)));
-
-            // Move into the editing area and hide the palette
-            div.style.left = ui.pixpos(palcx * -1);
-
-            // Refresh the composite
-            //log('onclick refresh');
-            var nodes = graph.refresh(svg);
-
-            // Reselect the previously selected component
-            graph.selected = graph.findcompnode(scdl.name(comp), nodes);
-            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-            // Trigger component select event
-            svg.oncompselect(graph.selected);
-
-            // Trigger composite change event
-            svg.oncomposchange(true);
-
-        } else {
-            graph.selected = selected;
-
-            // Select the component
-            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-            // Trigger component select event
-            svg.oncompselect(graph.selected);
-        }
-
-        //log('comp selected');
-
-        if (e.preventDefault)
-            e.preventDefault();
-        else
-            e.returnValue = false;
-        return true;
-    }
-
-    if (!ui.isMobile()) {
-        div.onclick = function(e) {
-            //log('div onclick');
-            var suspend = svg.suspendRedraw(10);
-            var r = onclick(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-        svg.onclick = function(e) {
-            //log('svg onclick');
-            var suspend = svg.suspendRedraw(10);
-            var r = onclick(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    }
-
-    /**
-     * Handle a mouse or touch move event.
-     */
-    function onmousemove(e) {
-        if (graph.dragging == null)
-            return true;
-
-        // Ignore duplicate  mouse move events
-        if (graph.moveX == graph.dragX && graph.moveY == graph.dragY)
-            return true;
-
-        // Remember that the component was dragged
-        graph.dragged = true;
-
-        // Cut wire to component
-        if (graph.dragging.parentNode != svg) {
-            var compos = scdl.composite(svg.compos);
-            setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, svg)));
-
-            // Bring component to the top
-            graph.bringtotop(graph.dragging, svg);
-        }
-
-        // Calculate new position of dragged element
-        var gpos = graph.relpos(graph.dragging);
-        var newX = gpos.xpos() + (graph.moveX - graph.dragX);
-        var newY = gpos.ypos() + (graph.moveY - graph.dragY);
-        if (newX >= palcx)
-            graph.dragX = graph.moveX
-        else
-            newX = palcx;
-        if (newY >= 0)
-            graph.dragY = graph.moveY;
-        else
-            newY = 0;
-
-        // Detach child elements to speedup rendering
-        graph.compoutline(graph.dragging, true);
-
-        // Move the dragged element
-        graph.move(graph.dragging, graph.mkpath().pos(newX, newY));
-
-        return false;
-    };
-
-    if (!ui.isMobile()) {
-        window.onmousemove = function(e) {
-            //log('onmousemove');
-
-            // Remember mouse position
-            graph.moveX = e.screenX;
-            graph.moveY = e.screenY;
-
-            var suspend = svg.suspendRedraw(10);
-            var r = onmousemove(e);
-            svg.unsuspendRedraw(suspend);
-            return r;
-        }
-    } else {
-        div.ontouchmove = function(e) {
-            //log('ontouchmove');
-            
-            // Remember touch position
-            var pos = e.touches[0];
-            if (graph.moveX == pos.screenX && graph.moveY == pos.screenY)
-                return true;
-            graph.moveX = pos.screenX;
-            graph.moveY = pos.screenY;
-            if (graph.moveX == graph.dragX && graph.moveY == graph.dragY)
-                return true;
-
-            // Start async move renderer
-            if (graph.moverenderer == null) {
-                graph.moverenderer = setInterval(function() {
-                    var suspend = svg.suspendRedraw(10);
-                    onmousemove(e);
-                    svg.unsuspendRedraw(suspend);
-                }, 10);
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Handle field on change events.
-     */
-    function onvaluechange() {
-        if (graph.selected == null)
-            return false;
-        if (g.parentNode.style.visibility == 'hidden')
-            return false;
-
-        // Change component name and refactor references to it
-        function changename() {
-            var compos = scdl.composite(svg.compos);
-            cvalue.value = graph.ucid(cvalue.value, compos, false);
-            graph.selected.id = cvalue.value;
-            setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value)));
-
-            // Refresh the composite
-            //log('onchangename refresh');
-            var nodes = graph.refresh(svg);
-
-            // Reselected the previously selected component
-            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
-            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-            // Trigger component select event
-            svg.oncompselect(graph.selected);
-
-            // Trigger composite change event
-            svg.oncomposchange(true);
-            return false;
-        }
-
-        // Change the component property value
-        function changeprop() {
-            graph.setproperty(graph.selected.comp, cvalue.value);
-            cvalue.value = graph.property(graph.selected.comp);
-            cvalue.disabled = !graph.hasproperty(graph.selected.comp);
-
-            // Refresh the composite
-            //log('onchangeprop refresh');
-            var nodes = graph.refresh(svg);
-
-            // Reselected the previously selected component
-            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
-            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-            // Trigger component select event
-            svg.oncompselect(graph.selected);
-
-            // Trigger composite change event
-            svg.oncomposchange(true);
-            return false;
-        }
-
-        return graph.hasproperty(graph.selected.comp)? changeprop() : changename();
-    };
-
-    cvalue.onchange = function() {
-        var suspend = svg.suspendRedraw(10);
-        var r = onvaluechange();
-        svg.unsuspendRedraw(suspend);
-        return r;
-    }
-    
-    // Handle delete event
-    function ondeleteclick() {
-        if (graph.selected == null)
-            return false;
-        if (graph.selected.id.substring(0, 8) != 'palette:') {
-
-            // Remove selected component
-            var compos = scdl.composite(svg.compos);
-            if (isNil(graph.selected.compos))
-                setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, svg)));
-            setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos)))));
-
-            // Reset current selection
-            graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
-            graph.selected = null;
-
-            // Refresh the composite
-            //log('ondelete refresh');
-            graph.refresh(svg);
-
-            // Trigger component select event
-            svg.oncompselect(null);
-
-            // Trigger composite change event
-            svg.oncomposchange(true);
-        }
-        return false;
-    };
-
-    cdelete.onclick = function() {
-        var suspend = svg.suspendRedraw(10);
-        var r = ondeleteclick();
-        svg.unsuspendRedraw(suspend);
-        return r;
-    };
-
-    // Handle copy event
-    function oncopyclick() {
-        if (graph.selected == null)
-            return false;
-        if (graph.selected.id.substring(0, 8) == 'palette:')
-            return false;
-
-        // Clone the selected component
-        var compos = scdl.composite(svg.compos);
-        var comps = graph.clonecomp(graph.selected, compos, svg);
-        setElement(compos, graph.sortcompos(graph.addcomps(comps, compos)));
-
-        // Refresh the composite
-        //log('onclick refresh');
-        var nodes = graph.refresh(svg);
-
-        // Select the component clone
-        graph.selected = graph.findcompnode(scdl.name(car(comps)), nodes);
-        graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
-
-        // Trigger component select event
-        svg.oncompselect(graph.selected);
-
-        // Trigger composite change event
-        svg.oncomposchange(true);
-
-        return false;
-    };
-
-    ccopy.onclick = function() {
-        var suspend = svg.suspendRedraw(10);
-        var r = oncopyclick();
-        svg.unsuspendRedraw(suspend);
-        return r;
-    };
-
-    // Handle add event
-    cadd.onclick = function() {
-
-        // Show the palette
-        div.style.left = ui.pixpos(0);
-        return false;
-    };
-
-    // Create a hidden SVG element to help compute the width
-    // of component and reference titles
-    graph.svgtitles = document.createElementNS(graph.svgns, 'svg');
-    graph.svgtitles.style.visibility = 'hidden';
-    graph.svgtitles.style.height = ui.pixpos(0);
-    graph.svgtitles.style.width = ui.pixpos(0);
-    div.appendChild(graph.svgtitles);
-
-    return svg;
-};
-
-/**
- * Point class.
- */
-graph.Point = function(x, y) {
-    this.x = x;
-    this.y = y;
-};
-graph.Point.prototype.xpos = function() {
-    return this.x;
-};
-graph.Point.prototype.ypos = function() {
-    return this.y;
-};
-
-graph.mkpoint = function(x, y) {
-    return new graph.Point(x, y);
-};
-
-/**
- * Path class.
- */
-graph.Path = function() {
-    this.path = '';
-    this.x = 0;
-    this.y = 0;
-}
-graph.Path.prototype.pos = function(x, y) {
-    this.x = x;
-    this.y = y;
-    return this;
-};
-graph.Path.prototype.xpos = function() {
-    return this.x;
-};
-graph.Path.prototype.ypos = function() {
-    return this.y;
-};
-graph.Path.prototype.rmove = function(x, y) {
-    return this.move(this.x + x, this.y + y);
-};
-graph.Path.prototype.rline = function(x, y) {
-    return this.line(this.x + x, this.y + y);
-};
-graph.Path.prototype.rcurve = function(x1, y1, x, y) {
-    return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y);
-};
-graph.Path.prototype.str = function() {
-    return this.path;
-};
-graph.Path.prototype.clone = function() {
-    return graph.mkpath().pos(this.xpos(), this.ypos());
-};
-graph.Path.prototype.move = function(x, y) {
-    this.path += 'M' + x + ',' + y + ' '; 
-    return this.pos(x, y);
-};
-graph.Path.prototype.line = function(x, y) {
-    this.path += 'L' + x + ',' + y + ' ';
-    return this.pos(x, y);
-};
-graph.Path.prototype.curve = function(x1, y1, x, y) {
-    this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' ';
-    return this.pos(x, y);
-};
-graph.Path.prototype.end = function() {
-    this.path += 'Z';
-    return this;
-};
-
-graph.mkpath = function() {
-    return new graph.Path();
-};
-
-/**
- * Return an element representing a title.
- */
-graph.mktitle = function(t, x, y) {
-    var title = document.createElementNS(graph.svgns, 'text');
-    title.setAttribute('x', x);
-    title.setAttribute('y', y);
-    title.setAttribute('class', 'svgtitle');
-    title.setAttribute('pointer-events', 'none');
-    title.appendChild(document.createTextNode(t));
-    graph.svgtitles.appendChild(title);
-    return title;
-};
-
-/**
- * Return an element representing the title of a component.
- */
-graph.comptitle = function(comp) {
-    return memo(comp, 'title', function() {
-    	var ct = graph.title(comp);
-    	var pt = graph.propertytitle(comp);
-    	if (ct == '' && pt == '')
-    	    return null;
-        return graph.mktitle((ct != '' && pt != '')? ct + ' ' + pt : ct + pt, titlex, titley);
-    });
-};
-
-/**
- * Return the width of the title of a component.
- */
-graph.comptitlewidth = function(comp) {
-    var title = graph.comptitle(comp);
-    if (isNil(title))
-        return 0;
-    return title.getBBox().width + titlew;
-};
-
-/**
- * Draw a component shape selection.
- */
-graph.compselect = function(g, s, cvalue, ccopy, cdelete) {
-    if (isNil(g) || !s) {
-        cvalue.value = '';
-        cvalue.disabled = true;
-        ccopy.disabled = true;
-        cdelete.disabled = true;
-        if (isNil(g))
-            return true;
-        g.shape.setAttribute('stroke', graph.colors.gray);
-        g.shape.setAttribute('stroke-width', '1');
-        return true;
-    }
-
-    cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id;
-    cvalue.disabled = false;
-    ccopy.disabled = false;
-    cdelete.disabled = false;
-
-    g.shape.setAttribute('stroke', graph.colors.link);
-    g.shape.setAttribute('stroke-width', '2');
-    g.parentNode.appendChild(g);
-    return true;
-};
-
-/**
- * Draw a palette shape selection.
- */
-graph.paletteselect = function(g, s) {
-    if (isNil(g))
-        return true;
-    if (!s) {
-        g.shape.setAttribute('stroke', graph.colors.gray);
-        g.shape.setAttribute('stroke-width', '1');
-        return true;
-    }
-
-    g.shape.setAttribute('stroke', graph.colors.link);
-    g.shape.setAttribute('stroke-width', '2');
-    g.parentNode.appendChild(g);
-    return true;
-};
-
-/**
- * Draw a component outline for faster rendering.
- */
-graph.compoutline = function(g, s) {
-    if (s == (isNil(g.outlined)? false : g.outlined))
-        return true;
-    g.outlined = s;
-
-    if (s) {
-        g.shape.setAttribute('fill', 'none');
-        if (!isNil(g.title))
-            g.removeChild(g.title);
-    } else {
-        g.shape.setAttribute('fill', graph.color(g.comp));
-        if (!isNil(g.title))
-            g.appendChild(g.title);
-    }
-
-    map(function(r) {
-            var n = caddr(r);
-            if (isNil(n))
-                return r;
-            graph.compoutline(n, s);
-            return r;
-        }, g.refpos);
-    return true;
-};
-
-/**
- * Return a node representing a component.
- */
-graph.compnode = function(comp, cassoc, pos, parentg) {
-
-    // Make the component title element
-    var title = graph.comptitle(comp);
-
-    // Compute the path of the component shape
-    var path = graph.comppath(comp, cassoc);
-
-    // Create the main component shape
-    var shape = document.createElementNS(graph.svgns, 'path');
-    shape.setAttribute('d', path.str());
-    shape.setAttribute('fill', graph.color(comp));
-    //shape.setAttribute('fill-opacity', '0.6');
-    shape.setAttribute('stroke', graph.colors.gray);
-    shape.setAttribute('stroke-width', '1');
-    shape.setAttribute('pointer-events', 'visible');
-
-    // Create an svg group and add the shape and title to it
-    var g = document.createElementNS(graph.svgns, 'g');
-    g.comp = comp;
-    g.id = scdl.name(comp);
-    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
-    g.pos = pos.clone();
-    g.appendChild(shape);
-    g.shape = shape;
-    if (!isNil(title)) {
-        g.appendChild(title);
-        g.title = title;
-    }
-
-    // Store the the positions of the services and references
-    g.refpos = reverse(path.refpos);
-    g.svcpos = reverse(path.svcpos);
-
-    // Handle onclick events
-    g.onclick = parentg.onclick;
-
-    return g;
-};
-
-/**
- * Find the node representing a component.
- */
-graph.findcompnode = function(name, nodes) {
-    if (isNil(nodes))
-        return null;
-    if (isNil(car(nodes).comp))
-        return graph.findcompnode(name, cdr(nodes));
-    if (name == scdl.name(car(nodes).comp))
-        return car(nodes);
-    var node = graph.findcompnode(name, nodeList(car(nodes).childNodes));
-    if (!isNil(node))
-        return node;
-    return graph.findcompnode(name, cdr(nodes));
-}
-
-/**
- * Return a graphical group.
- */
-graph.mkgroup = function(pos) {
-    var g = document.createElementNS(graph.svgns, 'g');
-    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
-    g.pos = pos.clone();
-    return g;
-};
-
-/**
- * Return a node representing a button.
- */
-graph.mkbutton = function(t, pos) {
-
-    // Make the button title
-    var title = graph.mktitle(t, titlex, titley);
-
-    // Compute the path of the button shape
-    var path = graph.buttonpath().str();
-
-    // Create the main button shape
-    var shape = document.createElementNS(graph.svgns, 'path');
-    shape.setAttribute('d', path);
-    shape.setAttribute('fill', graph.colors.lightgray1);
-    //shape.setAttribute('fill-opacity', '0.6');
-    shape.setAttribute('stroke', graph.colors.gray);
-    shape.setAttribute('stroke-width', '1');
-    shape.setAttribute('pointer-events', 'visible');
-
-    // Create a group and add the button shape to it
-    var g = document.createElementNS(graph.svgns, 'g');
-    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
-    g.pos = pos.clone();
-    g.appendChild(shape);
-    g.appendChild(title);
-
-    // Store the button shape in the group
-    g.shape = shape;
-
-    return g;
-};
-
-/**
- * Return the relative position of a node.
- */
-graph.relpos = function(e) {
-    var pmatrix = e.parentNode != null? e.parentNode.getCTM() : null;
-    var matrix = e.getCTM();
-    var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
-    var curY = pmatrix != null? (Number(matrix.f) - Number(pmatrix.f)): Number(matrix.f);
-    return graph.mkpath().pos(curX, curY);
-};
-
-/**
- * Move a node.
- */
-graph.move = function(e, pos) {
-    e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
-    e.pos = pos.clone();
-};
-
-/**
- * Return the absolute position of a component node.
- */
-graph.abspos = function(e, g) {
-    if (e == g)
-        return graph.mkpath();
-    var gpos = graph.relpos(e);
-    var pgpos = graph.abspos(e.parentNode, g);
-    return graph.mkpath().pos(gpos.xpos() + pgpos.xpos(), gpos.ypos() + pgpos.ypos());
-};
-
-/**
- * Bring a component node to the top.
- */
-graph.bringtotop = function(n, g) {
-    if (n == g)
-        return null;
-    graph.move(n, graph.abspos(n, g));
-    g.appendChild(n);
-}
-
-/**
- * Return the title of a SCDL element.
- */
-graph.title = function(e) {
-    var t = scdl.title(e);
-    if (t != null) {
-        if (t == 'gt')
-            return '>'
-        if (t == 'lt')
-            return '<';
-        if (t.indexOf('{propval}') != -1)
-            return '';
-        if (t.indexOf('{compname}') == -1)
-            return t;
-        return t.replace('{compname}', scdl.name(e));
-    }
-    return scdl.name(e);
-};
-
-/**
- * Return the property value of a SCDL component.
- */
-graph.property = function(e) {
-    var p = scdl.properties(e);
-    if (isNil(p))
-        return '';
-    if (scdl.visible(car(p)) == 'false')
-        return '';
-    var pv = scdl.propertyValue(car(p));
-    return pv;
-};
-
-/**
- * Return the title of a property of a SCDL component.
- */
-graph.propertytitle = function(e) {
-    var pv = graph.property(e);
-    var t = scdl.title(e);
-    if (t.indexOf('{propval}') == -1)
-        return pv;
-    return t[0] == ' '? t.substr(1).replace('{propval}', pv) : t.replace('{propval}', pv);
-};
-
-/**
- * Return true if a SCDL component has a property.
- */
-graph.hasproperty = function(e) {
-    var p = scdl.properties(e);
-    if (isNil(p))
-        return false;
-    if (scdl.visible(car(p)) == 'false')
-        return false;
-    return true;
-};
-
-/**
- * Change the property value of a SCDL component.
- */
-graph.setproperty = function(e, value) {
-    var p = scdl.properties(e);
-    if (isNil(p))
-        return '';
-    if (scdl.visible(car(p)) == 'false')
-        return '';
-    var name = scdl.name(car(p));
-    setElement(car(p), mklist(element, "'property", mklist(attribute, "'name", name != null? name : "property"), value));
-    return value;
-};
-
-/**
- * Return the color of a SCDL component.
- */
-graph.color = function(comp) {
-    return memo(comp, 'color', function() {
-        var c = scdl.color(comp);
-        return c == null? graph.colors.blue1 : graph.colors[c];
-    });
-};
-
-/**
- * Return the services on the left side of a component.
- */
-graph.lsvcs = function(comp) {
-    return memo(comp, 'lsvcs', function() {
-        var svcs = scdl.services(comp);
-        if (isNil(svcs))
-            return mklist(mklist("'element","'service","'attribute","'name",scdl.name(comp)));
-        var l = filter(function(s) {
-                var a = scdl.align(s);
-                var v = scdl.visible(s);
-                return (a == null || a == 'left') && v != 'false';
-            }, svcs);
-        if (isNil(l))
-            return mklist();
-        return mklist(car(l));
-    });
-};
-
-/**
- * Return the references on the right side of a component.
- */
-graph.rrefs = function(comp) {
-    return memo(comp, 'rrefs', function() {
-        return filter(function(r) {
-            var a = scdl.align(r);
-            var v = scdl.visible(r);
-            return (a == null || a == 'right') && v != 'false';
-        }, scdl.references(comp));
-    });
-};
-
-/**
- * Return the height of a reference on the right side of a component.
- */
-graph.rrefheight = function(ref, cassoc) {
-    return memo(ref, 'rheight', function() {
-        var target = assoc(scdl.target(ref), cassoc);
-        if (isNil(target))
-            return tabsz * 8;
-        return graph.compclosureheight(cadr(target), cassoc);
-    });
-};
-
-/**
- * Return the total height of the references on the right side of a component.
- */
-graph.rrefsheight = function(refs, cassoc) {
-    if (isNil(refs))
-        return 0;
-    return graph.rrefheight(car(refs), cassoc) + graph.rrefsheight(cdr(refs), cassoc);
-};
-
-/**
- * Return the height of a component node.
- */
-graph.compheight = function(comp, cassoc) {
-    return memo(comp, 'height', function() {
-        var lsvcs = graph.lsvcs(comp);
-        var lsvcsh = Math.max(1, length(lsvcs)) * (tabsz * 8) + (tabsz * 4);
-        var rrefs = graph.rrefs(comp);
-        var rrefsh = graph.rrefsheight(rrefs, cassoc) + (tabsz * 2);
-        return Math.max(lsvcsh, rrefsh);
-    });
-};
-
-/**
- * Return the height of a component and the components wired to its bottom side.
- */
-graph.compclosureheight = function(comp, cassoc) {
-    return memo(comp, 'closureheight', function() {
-        return graph.compheight(comp, cassoc);
-    });
-};
-
-/**
- * Return the max width of the references on the right side of a component.
- */
-graph.rrefswidth = function(refs, cassoc) {
-    if (isNil(refs))
-        return 0;
-    return Math.max(graph.rrefwidth(car(refs), cassoc), graph.rrefswidth(cdr(refs), cassoc));
-};
-
-/**
- * Return the width of a component.
- */
-graph.compwidth = function(comp, cassoc) {
-    return memo(comp, 'width', function() {
-        var ctw = graph.comptitlewidth(comp);
-        var rrefsw = (isNil(graph.rrefs(comp))? 0 : (tabsz * 4));
-        var twidth = (titlex * 2) + ctw + rrefsw;
-        var width = Math.max(twidth, (tabsz * 8) + (tabsz * 4));
-        return width;
-    });
-};
-
-/**
- * Return a path representing a reference positioned to the right of a component.
- */
-graph.rrefpath = function(ref, cassoc, path, maxheight) {
-    var height = graph.rrefheight(ref, cassoc);
-
-    // Record reference position in the path
-    var xpos = path.xpos();
-    var ypos = path.ypos();
-    path.refpos = cons(mklist(ref, graph.mkpath().pos(xpos, ypos + (tabsz * 5))), path.refpos);
-
-    // Compute the reference path
-    return path.rline(0,tabsz * 2).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz/2.0).rcurve(0,-tabsz/2.0,-tabsz,0).rcurve(-tabsz,0,0,tabsz/2.0).rline(0,tabsz * 3).rcurve(0,tabsz/2.0,tabsz,0).rcurve(tabsz,0,0,-tabsz/2.0).rcurve(0,-tabsz/2.0,tabsz,0).rcurve(tabsz,0,0,tabsz).line(path.xpos(), Math.min(ypos + height, maxheight));
-};
-
-/**
- * Return a path representing a service positioned to the left of a component.
- */
-graph.lsvcpath = function(svc, cassoc, path, minheight) {
-    var height = tabsz * 8;
-
-    // Record service position in the path
-    var xpos = path.xpos();
-    var ypos = path.ypos();
-    path.svcpos = cons(mklist(svc, graph.mkpath().pos(xpos, ypos - (tabsz * 6))), path.svcpos);
-
-    // Compute the service path
-    return path.rline(0, -(tabsz * 2)).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz/2.0).rcurve(0,tabsz/2.0,-tabsz,0).rcurve(-tabsz,0,0,-tabsz/2.0).rline(0,-(tabsz * 3)).rcurve(0,-tabsz/2.0,tabsz,0).rcurve(tabsz,0,0,tabsz/2.0).rcurve(0,tabsz/2.0,tabsz,0).rcurve(tabsz,0,0,-tabsz).line(path.xpos(), Math.max(ypos - height, minheight));
-};
-
-/**
- * Return a path representing a component node.
- */
-graph.comppath = function(comp, cassoc) {
-
-    // Calculate the width and height of the component node
-    var width = graph.compwidth(comp, cassoc);
-    var height = graph.compheight(comp, cassoc);
-
-    /**
-     * Apply a path rendering function to a list of services or references.
-     */
-    function renderpath(x, f, cassoc, path, height) {
-        if (isNil(x))
-            return path;
-        return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path, height), height);
-    }
-
-    var path = graph.mkpath().move(curvsz,0);
-
-    // Store the positions of services and references in the path
-    path.refpos = mklist();
-    path.svcpos = mklist();
-
-    // Render the references on the right side of the component
-    var rrefs = graph.rrefs(comp);
-    path = path.line(width - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
-    path = renderpath(rrefs, graph.rrefpath, cassoc, path, height - curvsz);
-
-    // Render the references on the bottom side of the component
-    var boffset = curvsz;
-    path = path.line(path.xpos(),height - curvsz).rcurve(0,curvsz,curvsz * -1,0).line(boffset, path.ypos());
-
-    // Render the services on the left side of the component
-    var lsvcs = graph.lsvcs(comp);
-    var loffset = curvsz + (length(lsvcs) * (tabsz * 8));
-    path = path.line(curvsz,path.ypos()).rcurve(curvsz * -1,0,0,curvsz * -1).line(path.xpos(), loffset);
-    path = renderpath(lsvcs, graph.lsvcpath, cassoc, path, curvsz);
-
-    // Close the component node path
-    path = path.line(0,curvsz).rcurve(0,curvsz * -1,curvsz,0);
-
-    return path.end();
-};
-
-/**
- * Return the position of a component.
- */
-graph.comppos = function(comp, pos) {
-    var x = scdl.x(comp);
-    var y = scdl.y(comp);
-    return graph.mkpath().pos(x != null? Number(x) + palcx : pos.xpos(), y != null? Number(y) : pos.ypos());
-};
-
-/**
- * Return a path representing a button node.
- */
-graph.buttonpath = function(t) {
-    var path = graph.mkpath().move(curvsz,0);
-    path = path.line(buttoncx - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
-    path = path.line(path.xpos(),buttoncy - curvsz).rcurve(0,curvsz,-curvsz,0).line(curvsz, path.ypos());
-    path = path.line(curvsz,path.ypos()).rcurve(-curvsz,0,0,-curvsz).line(path.xpos(), curvsz);
-    path = path.line(0,curvsz).rcurve(0,-curvsz,curvsz,0);
-    return path.end();
-};
-
-/**
- * Render a SCDL composite into a list of component nodes.
- */
-graph.composite = function(compos, pos, aspalette, g) {
-    var name = scdl.name(scdl.composite(compos));
-    var comps = scdl.components(compos);
-    var cassoc = scdl.nameToElementAssoc(comps);
-    var proms = scdl.promotions(compos);
-
-    // Unmemoize any memoized info about components and their references.
-    map(function(c) {
-            unmemo(c);
-            map(function(r) {
-                    unmemo(r);
-                }, scdl.references(c));
-        }, comps);
-
-    /**
-     * Render a component.
-     */
-    function rendercomp(comp, cassoc, pos) {
-
-        /**
-         * Render the references on the right side of a component.
-         */
-        function renderrrefs(refs, cassoc, pos, gcomp) {
-
-            /**
-             * Render a reference on the right side of a component.
-             */
-            function renderrref(ref, cassoc, pos, gcomp) {
-                var target = assoc(scdl.target(ref), cassoc);
-                if (isNil(target))
-                    return null;
-
-                // Render the component target of the reference
-                return rendercomp(cadr(target), cassoc, pos);
-            }
-
-            /**
-             * Move the rendering cursor down below a reference.
-             */
-            function rendermove(ref, cassoc, pos) {
-                return pos.clone().rmove(0, graph.rrefheight(ref, cassoc));
-            }
-
-            if (isNil(refs))
-                return mklist();
-
-            // Return list of (ref, comp rendering) pairs
-            var grefcomp = renderrref(car(refs), cassoc, pos, gcomp);
-            return cons(mklist(car(refs), grefcomp), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
-        }
-
-        // Compute the component shape
-        var gcomp = graph.compnode(comp, cassoc, pos, g);
-
-        // Render the components wired to the component references
-        var rrefs = graph.rrefs(comp);
-        var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0);
-        var grrefs = renderrrefs(rrefs, cassoc, rpos, gcomp);
-
-        // Store list of (ref, pos, component rendering) triplets in the component
-        function refposgcomp(refpos, grefs) {
-            if (isNil(refpos))
-                return mklist();
-
-            // Append component rendering to component
-            var gref = cadr(car(grefs));
-            if (gref != null)
-                gcomp.appendChild(gref);
-            return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs)));
-        }
-
-        gcomp.refpos = refposgcomp(gcomp.refpos, grrefs);
-
-        return gcomp;
-    }
-
-    /**
-     * Render a list of promoted service components.
-     */
-    function renderproms(svcs, cassoc, pos) {
-
-        /**
-         * Return the component promoted by a service.
-         */
-        function promcomp(svc, cassoc) {
-            var c = assoc(scdl.promote(svc), cassoc);
-            if (isNil(c))
-                return mklist();
-            return cadr(c);
-        }
-
-        /**
-         * Move the rendering cursor down below a component.
-         */
-        function rendermove(comp, cassoc, pos) {
-            return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((tabsz * 2), 8));
-        }
-
-        if (isNil(svcs))
-            return mklist();
-
-        // Render the first promoted component in the list
-        // then recurse to render the rest of the list
-        var comp = promcomp(car(svcs), cassoc);
-        if (isNil(comp))
-            return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos));
-
-        var cpos = graph.comppos(comp, pos);
-        return cons(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos)));
-    }
-
-    // Render the promoted service components
-    var rproms = renderproms(proms, cassoc, pos.clone().rmove(tabsz * 4, tabsz * 4));
-
-    if (aspalette) {
-
-        // Prefix ids of palette component elements with 'palette:' and
-        // move them to the palette area
-        return map(function(r) {
-                r.id = 'palette:' + r.id;
-                var gpos = r.pos;
-                graph.move(r, graph.mkpath().pos(gpos.xpos() - palcx, gpos.ypos()));
-                return r;
-            }, rproms);
-
-    } else {
-
-        // Link app component elements to the containing composite
-        return map(function(r) { r.compos = compos; return r; }, rproms);
-    }
-};
-
-/**
- * Return a component unique id.
- */
-graph.ucid = function(prefix, compos1, compos2, clone) {
-
-    // Build an assoc list keyed by component name
-    var comps = map(function(c) { return mklist(scdl.name(c), c); }, append(namedElementChildren("'component", compos1), namedElementChildren("'component", compos2)));
-
-    if (!clone && isNil(assoc(prefix, comps)))
-        return prefix;
-
-    /**
-     * Find a free component id.
-     */
-    function ucid(p, id) {
-        if (isNil(assoc(p + id, comps)))
-            return p + id;
-        return ucid(p, id + 1);
-    }
-
-    /**
-     * Remove trailing digits from a prefix.
-     */
-    function untrail(p) { 
-        if (p.length < 2 || p[p.length - 1] < '0' || p[p.length - 1] > '9')
-            return p;
-        return untrail(p.substring(0, p.length - 1));
-    }
-
-    return ucid(prefix == ''? 'comp' : (clone? untrail(prefix) : prefix), 1);
-};
-
-/**
- * Clone a palette component node.
- */
-graph.clonepalette = function(e, compos, g) {
-
-    // Clone the SCDL component and give it a unique name
-    var wcomp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos, compos, true))),
-                filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp)));
-    var x = '<composite>' + writeXML(mklist(wcomp), false) + '</composite>';
-    var rcompos = scdl.composite(readXML(mklist(x)));
-    var comp = car(scdl.components(mklist(rcompos)));
-
-    // Update component position
-    setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(palcx, 0)));
-
-    return comp;
-};
-
-/**
- * Move a SCDL component to the given position.
- */
-graph.movecomp = function(comp, pos) {
-    if (isNil(pos))
-        return append(mklist(element, "'component"),
-                filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
-    return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.xpos() - palcx)), mklist(attribute, "'y", '' + pos.ypos())),
-            filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
-};
-
-/**
- * Align a pos along a 10pixel grid.
- */
-graph.gridsnap = function(x) {
-    return Math.round(x / 10) * 10;
-}
-
-/**
- * Clone a component node and all the components it references.
- */
-graph.clonecomp = function(e, compos, g) {
-
-    // Write the component and the components it references to XML
-    function collectcomp(e) {
-        function collectrefs(refpos) {
-            if (isNil(refpos))
-                return mklist();
-            var r = car(refpos);
-            var n = caddr(r);
-            if (isNil(n))
-                return collectrefs(cdr(refpos));
-            return append(collectcomp(n), collectrefs(cdr(refpos)));
-        }
-
-        return cons(e.comp, collectrefs(e.refpos));
-    }
-
-    var allcomps = collectcomp(e);
-    var ls = map(function(e) { return writeXML(mklist(e), false); }, allcomps);
-    var x = '<composite>' + writeStrings(ls) + '</composite>';
-
-    // Read them back from XML to clone them
-    var rcompos = scdl.composite(readXML(mklist(x)));
-    var comps = scdl.components(mklist(rcompos));
-
-    // Give them new unique names
-    map(function(e) {
-
-        // Rename each component
-        var oname = scdl.name(e);
-        var name = graph.ucid(oname, compos, rcompos, true);
-        setElement(e, append(mklist(element, "'component", mklist(attribute, "'name", name)),
-                        filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e))));
-
-        // Refactor references to the component
-        map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, comps);
-    }, comps);
-
-    // Update the top component position
-    var comp = car(comps);
-    setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(10, 10)));
-
-    return comps;
-};
-
-/**
- * Sort elements of a composite.
- */
-graph.sortcompos = function(compos) {
-    return append(mklist(element, "'composite"), elementChildren(compos).sort(function(a, b) {
-
-        // Sort attributes, place them at the top
-        var aa = isAttribute(a);
-        var ba = isAttribute(b);
-        if (aa && !ba) return -1;
-        if (!aa && ba) return 1;
-        if (aa && ba) {
-            var aan = attributeName(a);
-            var ban = attributeName(b);
-            if (aan < ban) return -1;
-            if (aan > ban) return 1;
-            return 0;
-        }
-
-        // Sort elements, place services before components
-        var aen = elementName(a);
-        var ben = elementName(b);
-        if (aen == "'service" && ben == "'component") return -1;
-        if (aen == "'component" && ben == "'service") return 1;
-        var an = scdl.name(a);
-        var bn = scdl.name(b);
-        if (an < bn) return -1;
-        if (an > bn) return 1;
-        return 0;
-    }));
-}
-
-/**
- * Add a list of components to a SCDL composite. The first
- * component in the list is a promoted component.
- */
-graph.addcomps = function(comps, compos) {
-    var comp = car(comps);
-    var name = scdl.name(comp);
-    var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
-    return append(mklist(element, "'composite"), append(elementChildren(compos), cons(prom, comps)));
-};
-
-/**
- * Remove a component from a SCDL composite.
- */
-graph.removecomp = function(comp, compos) {
-    var name = scdl.name(comp);
-    return append(mklist(element, "'composite"),
-            filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)));
-};
-
-/**
- * Garbage collect components not referenced or promoted.
- */
-graph.gcollect = function(compos) {
-
-    // List the promoted components
-    var proms = map(function(s) { return mklist(scdl.promote(s), true); }, scdl.promotions(mklist(compos)));
-
-    // List the referenced components
-    var refs = reduce(function(a, comp) {
-                return append(a,
-                    map(function(ref) { return mklist(scdl.target(ref), true); }, filter(function(ref) { return scdl.target(ref) != null; }, scdl.references(comp))));
-            }, mklist(), scdl.components(mklist(compos)));
-
-    // Filter out the unused components
-    var used = append(proms, refs);
-    return append(mklist(element, "'composite"),
-            filter(function(c) { return !(isElement(c) && elementName(c) == "'component" && isNil(assoc(scdl.name(c), used))); }, elementChildren(compos)));
-}
-
-/**
- * Clone and cleanup clonable references.
- */
-graph.clonerefs = function(compos) {
-    return append(mklist(element, "'composite"),
-            map(function(c) {
-                if (elementName(c) != "'component")
-                    return c;
-
-                // If the references are clonable
-                var refs = scdl.references(c);
-                if (isNil(refs))
-                    return c;
-                if (scdl.clonable(car(refs)) != 'true')
-                    return c;
-                    
-                // Filter out the unwired references and add a fresh unwired
-                // reference at the end of the list
-                var cc = append(
-                    filter(function(e) { return !(elementName(e) == "'reference" && scdl.target(e) == null); }, elementChildren(c)),
-                    mklist(mklist(element, "'reference", mklist(attribute, "'name", scdl.name(car(refs))), mklist(attribute, "'clonable", "true"))));
-                return append(mklist(element, "'component"), cc);
-            
-            }, elementChildren(compos)));
-}
-
-/**
- * Refactor references to a component.
- */
-graph.refactorrefs = function(refs, oname, nname) {
-    if (isNil(refs))
-        return true;
-    var ref = car(refs);
-    if (scdl.target(ref) != oname)
-        return graph.refactorrefs(cdr(refs), oname, nname);
-
-    // Change the reference's target attribute
-    setElement(ref, append(mklist(element, "'reference"),
-        append(filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(ref)),
-            mklist(mklist(attribute, "'target", nname)))));
-
-    return graph.refactorrefs(cdr(refs), oname, nname);
-};
-
-/**
- * Rename a component.
- */
-graph.renamecomp = function(comp, compos, name) {
-
-    // Refactor all the references to the renamed component
-    var oname = scdl.name(comp);
-    map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, namedElementChildren("'component", compos));
-
-    // Rename the SCDL promoted service and component
-    var proms = filter(function(s) { return scdl.name(s) == oname }, scdl.services(compos));
-    if (!isNil(proms))
-        setElement(car(proms), mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)));
-    setElement(comp, append(mklist(element, "'component"),
-        cons(mklist(attribute, "'name", name),
-            filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'name"); }, elementChildren(comp)))));
-
-    return append(mklist(element, "'composite"), elementChildren(compos));
-};
-
-/**
- * Cut the wire to a component node and make that node a
- * top level component node.
- */
-graph.cutwire = function(node, compos, g) {
-
-    /**
-     * Find the reference wired to a node and cut its wire.
-     */
-    function cutref(refs, node) {
-        if (isNil(refs))
-            return true;
-        var ref = car(refs);
-        if (caddr(ref) == node) {
-            setlist(ref, mklist(car(ref), cadr(ref), null));
-            setElement(car(ref),
-                append(mklist(element, "'reference"),
-                    filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(car(ref)))));
-        }
-        return cutref(cdr(refs), node);
-    }
-
-    // Cut any reference wire, if found
-    cutref(node.parentNode.refpos, node);
-
-    // Make the component node a top level node.
-    node.compos = g.compos;
-
-    // Update the SCDL composite, add a promote element for
-    // that component
-    var comp = node.comp;
-    var name = scdl.name(comp);
-    var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
-    return append(mklist(element, "'composite"),
-            append(mklist(prom), filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos))));
-}
-
-/**
- * Wire a component to the closest neighbor reference.
- */
-graph.wire = function(n, compos, g) {
-
-    // Compute position of the component's service node
-    var spos = cadr(car(n.svcpos));
-    var aspos = graph.abspos(n, g).rmove(spos.xpos(), spos.ypos());
-
-    /**
-     * Find closest unwired reference node among all the references
-     * of all the components.
-     */
-    function closecomprefs(nodes, spos, cref) {
-
-        /**
-         * Find the closest unwired reference node among all the
-         * references of a node.
-         */
-        function closerefs(npos, refs, spos, cref) {
-            if (isNil(refs))
-                return cref;
-            var fdist = cadddr(cref);
-            var ref = car(refs);
-
-            // Skip wired reference
-            if (!isNil(filter(function(n) { return isAttribute(n) && attributeName(n) == "'target"; }, car(ref))))
-                return closerefs(npos, cdr(refs), spos, cref);
-
-            // Compute distance between service node and reference node
-            var rpos = cadr(ref).clone().rmove(npos.xpos(), npos.ypos());
-            var dx = Math.pow(rpos.xpos() - spos.xpos(), 2);
-            var dy = Math.pow(rpos.ypos() - spos.ypos(), 2);
-
-            // Check for proximity threshold
-            var rdist = (dx < (proxcx * proxcx) && dy < (proxcy * proxcy))? Math.sqrt(dx + dy) : 25000000;
-
-            // Go through all the references in the component
-            return closerefs(npos, cdr(refs), spos, fdist < rdist? cref : mklist(car(ref), cadr(ref), caddr(ref), rdist));
-        }
-
-        if (isNil(nodes))
-            return cref;
-
-        // Skip non-component nodes
-        var node = car(nodes);
-        if (isNil(node.comp))
-            return closecomprefs(cdr(nodes), spos, cref);
-
-        // Compute the component absolute position
-        var npos = graph.abspos(node, g);
-
-        // Go through all the components and their references
-        return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref));
-    }
-
-    // Find closest reference node
-    var cref = closecomprefs(nodeList(g.childNodes), aspos, mklist(null, graph.mkpath(), null, 25000000));
-    if (car(cref) == null)
-        return compos;
-    if (cadddr(cref) == 25000000)
-        return compos;
-
-    // Wire component to that reference, un-promote it, and
-    // update the SCDL reference and composite
-    setElement(n.comp, graph.movecomp(graph.dragging.comp, null));
-    n.compos = null;
-    setElement(car(cref), append(mklist(element, "'reference", mklist(attribute, "'target", scdl.name(n.comp))), elementChildren(car(cref))));
-    var name = scdl.name(n.comp);
-    return append(mklist(element, "'composite"),
-            filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos)));
-}
-
-/**
- * Display a list of graphical nodes.
- */
-graph.display = function(nodes, g, svg) {
-    var suspend = svg.suspendRedraw(10);
-
-    // Append the nodes to the graphical canvas
-    appendNodes(nodes, g);
-    
-    svg.unsuspendRedraw(suspend);
-    return nodes;
-};
-
-/**
- * Hide a graph.
- */
-graph.hide = function(g) {
-
-    // Remove nodes from the graph
-    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
-    return g;
-};
-
-/**
- * Refresh a graph.
- */
-graph.refresh = function(g) {
-    //log('refresh');
-
-    // Remove existing nodes from the graph
-    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
-
-    // Redisplay the composite associated with the graph
-    var nodes = graph.composite(g.compos, graph.mkpath().pos(palcx,0), false, g);
-    appendNodes(nodes, g);
-    return nodes;
-};
-
-/**
- * Display and enable editing of a composite and the graphical
- * nodes that represent it.
- */
-graph.edit = function(appname, compos, nodes, onchange, onselect, g) {
-    var suspend = g.suspendRedraw(10);
-
-    // Store the appname and composite in the graphical canvas
-    g.appname = appname;
-    g.compos = compos;
-
-    // Sort the composite elements now to allow for change detection later
-    var scompos = scdl.composite(g.compos);
-    setElement(scompos, graph.sortcompos(scompos));
-
-    // Store event listeners
-    g.oncomposchange = onchange;
-    g.oncompselect = onselect;
-
-    // Remove existing nodes from the graph
-    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
-
-    // Display the composite nodes
-    appendNodes(nodes, g);
-
-    g.unsuspendRedraw(suspend);
-    return nodes;
-};
-
diff --git a/modules/edit/htdocs/graph/index.html b/modules/edit/htdocs/graph/index.html
index 6e93548..730506c 100644
--- a/modules/edit/htdocs/graph/index.html
+++ b/modules/edit/htdocs/graph/index.html
@@ -17,33 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title></title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-<script type="text/javascript" src="graph.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv" style="overflow: visible;">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menubackground" style="position: absolute; top: 0px; left: 0px; z-index: -1; width: 2500px;">
-<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr><td class="dtbar">
-<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ltbar"><span class="tbarsmenu">>&nbsp</span></td></tr></table>
-</td></tr></table>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr>
 <td><h2><span id="appNameHeader"></span></h2></td>
@@ -59,10 +34,6 @@
 
 <table id="compValueTable" style="width: 100%;">
 <tr>
-<td class="thr thl" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%">
-<input id="compValue" type="text" value="" title="Component value" autocapitalize="off" placeholder="Value" style="position: relative; width: 100%;"/>
-</td>
-
 <td class="thl thr" style="text-align: right; padding-right: 2px; vertical-align: top;">
 <span id="deleteCompButton" title="Delete a component" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">-</span>
 
@@ -72,36 +43,38 @@
 
 <span id="playCompButton" title="View component value" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">&gt;</span>
 </td>
+
+<td class="thl thr" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%">
+<input id="compValue" type="text" value="" title="Component value" autocapitalize="off" placeholder="Value" style="position: relative; visibility: hidden; width: 100%;"/>
+</td>
 </tr>
 </table>
 
 <div id="contentdiv" style="margin-top: 4px; width: 2500px;">
-<div id="playdiv" style="position:relative; top: 0x; left: 0px; right: 0px; width: 2500px; height: 5000px; visibility: hidden">
+<div id="playdiv" style="position: relative; top: 0x; left: 0px; right: 0px; width: 2500px; height: 5000px; visibility: hidden">
 </div>
 </div>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Get the app name
-var appname = ui.fragmentParams()['app'];
+var appname = ui.fragmentParams(location)['app'];
 var ispalette = false;
 if (isNil(appname)) {
     appname = ui.fragmentParams()['palette'];
-    if (isNil(appname))
-        window.open('/', '_self');
 
     // Edit a palette instead of a regular app
-    ispalette = true;
+    if (!isNil(appname))
+        ispalette = true;
 }
 
 /**
  * Return the link to an app.
  */
 function applink(appname) {
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/';
@@ -109,12 +82,9 @@
 }
 
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - ' + (isNil(config.compose)? 'Composition' : config.compose) + ' - ' + appname;
+document.title = ui.windowtitle(location.hostname) + ' - ' + (isNil(config.compose)? 'Composition' : config.compose) + ' - ' + appname;
 $('appNameHeader').innerHTML = '<a href=\"' + applink(appname) + '\" target=\"' + '_blank' + '\">' + appname + '</a>';
 
-// Load the menu bar
-displaymenu();
-
 /**
  * Component value field, add, delete and play buttons.
  */
@@ -125,10 +95,6 @@
 var cplay = $('playCompButton');
 
 // Position background divs
-var mbackground = $('menubackground');
-var menudiv = $('menu');
-mbackground.style.top = ui.pixpos(menudiv.offsetTop);
-
 var cvbackground = $('compValueBackground');
 var cvtable = $('compValueTable');
 cvbackground.style.top = ui.pixpos(cvtable.offsetTop);
@@ -154,6 +120,1764 @@
 //rconsole = sca.defun(sca.reference(editWidget, "log"), "log");
 
 /**
+ * SVG composite rendering functions.
+ */
+var graph = {};
+
+/**
+ * Basic colors
+ */
+graph.colors = {};
+graph.colors.black = '#000000';
+graph.colors.blue = '#0000ff';
+graph.colors.cyan = '#00ffff';
+graph.colors.gray = '#808080'
+graph.colors.lightgray = '#dcdcdc'
+graph.colors.green = '#00ff00';
+graph.colors.magenta = '#ff00ff';
+graph.colors.orange = '#ffa500';
+graph.colors.pink = '#ffc0cb';
+graph.colors.purple = '#800080';
+graph.colors.red = '#ff0000';
+graph.colors.white = '#ffffff';
+graph.colors.yellow = '#ffff00';
+graph.colors.link = '#598edd';
+
+graph.colors.orange1 = '#ffd666';
+graph.colors.green1 = '#bbe082';
+graph.colors.blue1 = '#66dbdf';
+graph.colors.yellow1 = '#fdf57a';
+graph.colors.cyan1 = '#e6eafb';
+graph.colors.lightgray1 = '#eaeaea'
+graph.colors.pink1 = '#ffd9e0';
+graph.colors.red1 = '#d03f41';
+graph.colors.white1 = '#ffffff';
+
+graph.colors.orange2 = '#ffbb00';
+graph.colors.green2 = '#96d333';
+//graph.colors.blue2 = '#0d7cc1';
+graph.colors.blue2 = '#00c3c9';
+graph.colors.red2 = '#d03f41';
+graph.colors.yellow2 = '#fcee21';
+graph.colors.magenta2 = '#c0688a';
+graph.colors.cyan2 = '#d5dcf9';
+graph.colors.lightgray2 = '#dcdcdc'
+graph.colors.pink2 = '#ffc0cb';
+graph.colors.white2 = '#ffffff';
+
+graph.colors.orange3 = '#ffc700';
+graph.colors.green3 = '#92e120';
+graph.colors.blue3 = '#008fd1';
+graph.colors.yellow3 = '#fdf400';
+graph.colors.cyan3 = '#b4d3fd';
+graph.colors.lightgray3 = '#e3e3e3'
+graph.colors.pink3 = '#da749b';
+graph.colors.red3 = '#ed3f48';
+graph.colors.white3 = '#ffffff';
+
+/**
+ * Default positions and sizes.
+ */
+graph.palcx = 2500;
+graph.proxcx = 20;
+graph.proxcy = 20;
+graph.buttoncx = 55;
+graph.buttoncy = 23;
+graph.curvsz = 4;
+graph.tabsz = 2;
+graph.titlex = 4;
+graph.titley = 11;
+graph.titlew = ui.isMobile()? -2 : 0;
+
+/**
+ * SVG rendering functions.
+ */
+
+graph.svgns='http://www.w3.org/2000/svg';
+
+/**
+ * Make an SVG graph.
+ */
+graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
+
+    // Create a div element to host the graph
+    var div = document.createElement('div');
+    div.id = 'svgdiv';
+    div.style.position = 'absolute';
+    div.style.left = ui.pixpos(pos.xpos() + cdiv.offsetLeft);
+    div.style.top = ui.pixpos(pos.ypos() + cdiv.offsetTop);
+    cdiv.appendChild(div);
+
+    // Create SVG element
+    var svg = document.createElementNS(graph.svgns, 'svg');
+    svg.style.height = ui.pixpos(5000);
+    svg.style.width = ui.pixpos(5000);
+    div.appendChild(svg);
+
+    // Track element dragging and selection
+    graph.dragging = null;
+    graph.dragged = false;
+    graph.moverenderer = null;
+    graph.selected = null;
+    cvalue.disabled = true;
+    cvalue.style.visibility = 'hidden';
+    ccopy.disabled = true;
+    cdelete.disabled = true;
+
+    /**
+     * Find the first draggable element in a hierarchy of elements.
+     */
+    function draggable(n) {
+        if (n == div || n == svg || n == null)
+            return null;
+        if (n.nodeName == 'g' && !isNil(n.id) && n.id != '')
+            return n;
+        return draggable(n.parentNode);
+    }
+
+    /**
+     * Handle a mouse down or touch start event.
+     */
+    function onmousedown(e) {
+
+        // Remember mouse or touch position
+        var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
+        graph.downX = pos.screenX;
+        graph.downY = pos.screenY;
+        graph.moveX = pos.screenX;
+        graph.moveY = pos.screenY;
+
+        // Engage the click component selection right away
+        // on mouse controlled devices
+        if (typeof e.touches == 'undefined')
+            onclick(e);
+
+        // Find and remember draggable component
+        var dragging = draggable(e.target);
+        if (dragging == null || dragging != graph.selected)
+            return true;
+        graph.dragging = dragging;
+        graph.dragged = false;
+
+        // Remember current drag position
+        graph.dragX = pos.screenX;
+        graph.dragY = pos.screenY;
+
+        e.preventDefault();
+        return true;
+    };
+
+    if (!ui.isMobile()) {
+        div.onmousedown = function(e) {
+            //log('onmousedown');
+            var suspend = svg.suspendRedraw(10);
+            var r = onmousedown(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    } else {
+        div.ontouchstart = function(e) {
+            //log('ontouchstart');
+
+            // Clear current move renderer if it's running
+            if (!isNil(graph.moverenderer)) {
+                clearInterval(graph.moverenderer);
+                graph.moverenderer = null;
+            }
+
+            var suspend = svg.suspendRedraw(10);
+            var r = onmousedown(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    }
+
+    /**
+     * Handle a mouse up or touch end event.
+     */
+    function onmouseup(e) {
+
+        // Engage the click component selection now on touch devices
+        if (ui.isMobile()) {
+            if (!graph.dragged && graph.moveX == graph.downX && graph.moveY == graph.downY)
+                return onclick(e);
+        }
+
+        // Stop here if the component was not dragged
+        if (graph.dragging == null)
+            return true;
+        if (!graph.dragged) {
+            graph.dragging = null;
+            return true;
+        }
+
+        if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
+
+            // Add new dragged component to the composite
+            if (isNil(graph.dragging.compos)) {
+                var compos = scdl.composite(svg.compos);
+                setElement(compos, graph.sortcompos(graph.addcomps(mklist(graph.dragging.comp), compos)));
+                graph.dragging.compos = svg.compos;
+            }
+
+            // Update component position
+            setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, svg)));
+
+            // Wire component to neighboring reference
+            if (!isNil(graph.dragging.svcpos)) {
+                var compos = scdl.composite(svg.compos);
+                setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, svg))));
+            }
+
+            // Snap top level component position to grid
+            if (graph.dragging.parentNode == svg) {
+                var gpos = graph.relpos(graph.dragging);
+                setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.xpos()), graph.gridsnap(gpos.ypos()))));
+            }
+        }
+
+        // Forget current dragged component
+        graph.dragging = null;
+        graph.dragged = false;
+
+        // Refresh the composite
+        //log('onmouseup refresh');
+        var nodes = graph.refresh(svg);
+
+        // Reselected the previously selected component
+        if (!isNil(graph.selected)) {
+            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
+            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+            // Trigger component select event
+            svg.oncompselect(graph.selected);
+        }
+
+        // Trigger composite change event
+        svg.oncomposchange(false);
+        return true;
+    };
+
+    if (!ui.isMobile()) {
+        div.onmouseup = function(e) {
+            //log('onmouseup');
+            var suspend = svg.suspendRedraw(10);
+            var r = onmouseup(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    } else {
+        div.ontouchend = function(e) {
+            //log('ontouchend');
+
+            // Clear current move renderer if it's running
+            if (!isNil(graph.moverenderer)) {
+                clearInterval(graph.moverenderer);
+                graph.moverenderer = null;
+            }
+
+            var suspend = svg.suspendRedraw(10);
+            var r = onmouseup(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    }
+
+    /**
+     * Handle a mouse or touch click event.
+     */
+    function onclick(e) {
+        //log('onclick logic');
+
+        // Find selected component
+        var selected = draggable(e.target);
+        if (selected == null) {
+            if (graph.selected != null) {
+
+                // Reset current selection
+                graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+                graph.selected = null;
+
+                // Trigger component select event
+                svg.oncompselect(null);
+            }
+
+            // Dismiss the palette
+            if (e.target == div || e.target == svg && ui.numpos(div.style.left) != (graph.palcx * -1))
+            	div.style.left = ui.pixpos(graph.palcx * -1);
+
+            return true;
+        }
+
+        // Ignore multiple click events
+        if (selected == graph.selected)
+            return true;
+        if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(div.style.left) != 0)
+            return true;
+
+        // Deselect previously selected component
+        graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+
+        // Clone component from the palette
+        if (selected.id.substring(0, 8) == 'palette:') {
+            var compos = scdl.composite(svg.compos);
+            var comp = graph.clonepalette(selected, compos, svg);
+            setElement(compos, graph.sortcompos(graph.addcomps(mklist(comp), compos)));
+
+            // Move into the editing area and hide the palette
+            div.style.left = ui.pixpos(graph.palcx * -1);
+
+            // Refresh the composite
+            //log('onclick refresh');
+            var nodes = graph.refresh(svg);
+
+            // Reselect the previously selected component
+            graph.selected = graph.findcompnode(scdl.name(comp), nodes);
+            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+            // Trigger component select event
+            svg.oncompselect(graph.selected);
+
+            // Trigger composite change event
+            svg.oncomposchange(true);
+
+        } else {
+            graph.selected = selected;
+
+            // Select the component
+            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+            // Trigger component select event
+            svg.oncompselect(graph.selected);
+        }
+
+        //log('comp selected');
+
+        e.preventDefault();
+        return true;
+    }
+
+    if (!ui.isMobile()) {
+        div.onclick = function(e) {
+            //log('div onclick');
+            var suspend = svg.suspendRedraw(10);
+            var r = onclick(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+        svg.onclick = function(e) {
+            //log('svg onclick');
+            var suspend = svg.suspendRedraw(10);
+            var r = onclick(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    }
+
+    /**
+     * Handle a mouse or touch move event.
+     */
+    function onmousemove(e) {
+        if (graph.dragging == null)
+            return true;
+
+        // Ignore duplicate  mouse move events
+        if (graph.moveX == graph.dragX && graph.moveY == graph.dragY)
+            return true;
+
+        // Remember that the component was dragged
+        graph.dragged = true;
+
+        // Cut wire to component
+        if (graph.dragging.parentNode != svg) {
+            var compos = scdl.composite(svg.compos);
+            setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, svg)));
+
+            // Bring component to the top
+            graph.bringtotop(graph.dragging, svg);
+        }
+
+        // Calculate new position of dragged element
+        var gpos = graph.relpos(graph.dragging);
+        var newX = gpos.xpos() + (graph.moveX - graph.dragX);
+        var newY = gpos.ypos() + (graph.moveY - graph.dragY);
+        if (newX >= graph.palcx)
+            graph.dragX = graph.moveX
+        else
+            newX = graph.palcx;
+        if (newY >= 0)
+            graph.dragY = graph.moveY;
+        else
+            newY = 0;
+
+        // Detach child elements to speedup rendering
+        graph.compoutline(graph.dragging, true);
+
+        // Move the dragged element
+        graph.move(graph.dragging, graph.mkpath().pos(newX, newY));
+
+        return false;
+    };
+
+    if (!ui.isMobile()) {
+        window.onmousemove = function(e) {
+            //log('onmousemove');
+
+            // Remember mouse position
+            graph.moveX = e.screenX;
+            graph.moveY = e.screenY;
+
+            var suspend = svg.suspendRedraw(10);
+            var r = onmousemove(e);
+            svg.unsuspendRedraw(suspend);
+            return r;
+        }
+    } else {
+        div.ontouchmove = function(e) {
+            //log('ontouchmove');
+            
+            // Remember touch position
+            var pos = e.touches[0];
+            if (graph.moveX == pos.screenX && graph.moveY == pos.screenY)
+                return true;
+            graph.moveX = pos.screenX;
+            graph.moveY = pos.screenY;
+            if (graph.moveX == graph.dragX && graph.moveY == graph.dragY)
+                return true;
+
+            // Start async move renderer
+            if (graph.moverenderer == null) {
+                graph.moverenderer = setInterval(function() {
+                    var suspend = svg.suspendRedraw(10);
+                    onmousemove(e);
+                    svg.unsuspendRedraw(suspend);
+                }, 10);
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Handle field on change events.
+     */
+    function onvaluechange() {
+        if (graph.selected == null)
+            return false;
+        if (g.parentNode.style.visibility == 'hidden')
+            return false;
+
+        // Change component name and refactor references to it
+        function changename() {
+            var compos = scdl.composite(svg.compos);
+            cvalue.value = graph.ucid(cvalue.value, compos, false);
+            graph.selected.id = cvalue.value;
+            setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value)));
+
+            // Refresh the composite
+            //log('onchangename refresh');
+            var nodes = graph.refresh(svg);
+
+            // Reselected the previously selected component
+            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
+            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+            // Trigger component select event
+            svg.oncompselect(graph.selected);
+
+            // Trigger composite change event
+            svg.oncomposchange(true);
+            return false;
+        }
+
+        // Change the component property value
+        function changeprop() {
+            graph.setproperty(graph.selected.comp, cvalue.value);
+            var hasprop = graph.hasproperty(graph.selected.comp);
+            cvalue.disabled = hasprop? false : true;
+            cvalue.style.visibility = hasprop? 'visible' : 'hidden';
+            cvalue.value = graph.property(graph.selected.comp);
+
+            // Refresh the composite
+            //log('onchangeprop refresh');
+            var nodes = graph.refresh(svg);
+
+            // Reselected the previously selected component
+            graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
+            graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+            // Trigger component select event
+            svg.oncompselect(graph.selected);
+
+            // Trigger composite change event
+            svg.oncomposchange(true);
+            return false;
+        }
+
+        return graph.hasproperty(graph.selected.comp)? changeprop() : changename();
+    };
+
+    cvalue.onchange = function() {
+        var suspend = svg.suspendRedraw(10);
+        var r = onvaluechange();
+        svg.unsuspendRedraw(suspend);
+        return r;
+    }
+    
+    // Handle delete event
+    function ondeleteclick() {
+        if (graph.selected == null)
+            return false;
+        if (graph.selected.id.substring(0, 8) != 'palette:') {
+
+            // Remove selected component
+            var compos = scdl.composite(svg.compos);
+            if (isNil(graph.selected.compos))
+                setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, svg)));
+            setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos)))));
+
+            // Reset current selection
+            graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+            graph.selected = null;
+
+            // Refresh the composite
+            //log('ondelete refresh');
+            graph.refresh(svg);
+
+            // Trigger component select event
+            svg.oncompselect(null);
+
+            // Trigger composite change event
+            svg.oncomposchange(true);
+        }
+        return false;
+    };
+
+    cdelete.onclick = function() {
+        var suspend = svg.suspendRedraw(10);
+        var r = ondeleteclick();
+        svg.unsuspendRedraw(suspend);
+        return r;
+    };
+
+    // Handle copy event
+    function oncopyclick() {
+        if (graph.selected == null)
+            return false;
+        if (graph.selected.id.substring(0, 8) == 'palette:')
+            return false;
+
+        // Clone the selected component
+        var compos = scdl.composite(svg.compos);
+        var comps = graph.clonecomp(graph.selected, compos, svg);
+        setElement(compos, graph.sortcompos(graph.addcomps(comps, compos)));
+
+        // Refresh the composite
+        //log('onclick refresh');
+        var nodes = graph.refresh(svg);
+
+        // Select the component clone
+        graph.selected = graph.findcompnode(scdl.name(car(comps)), nodes);
+        graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+
+        // Trigger component select event
+        svg.oncompselect(graph.selected);
+
+        // Trigger composite change event
+        svg.oncomposchange(true);
+
+        return false;
+    };
+
+    ccopy.onclick = function() {
+        var suspend = svg.suspendRedraw(10);
+        var r = oncopyclick();
+        svg.unsuspendRedraw(suspend);
+        return r;
+    };
+
+    // Handle add event
+    cadd.onclick = function() {
+
+        // Show the palette
+        div.style.left = ui.pixpos(0);
+        return false;
+    };
+
+    // Create a hidden SVG element to help compute the width
+    // of component and reference titles
+    graph.svgtitles = document.createElementNS(graph.svgns, 'svg');
+    graph.svgtitles.style.visibility = 'hidden';
+    graph.svgtitles.style.height = ui.pixpos(0);
+    graph.svgtitles.style.width = ui.pixpos(0);
+    div.appendChild(graph.svgtitles);
+
+    return svg;
+};
+
+/**
+ * Point class.
+ */
+graph.Point = function(x, y) {
+    this.x = x;
+    this.y = y;
+};
+graph.Point.prototype.xpos = function() {
+    return this.x;
+};
+graph.Point.prototype.ypos = function() {
+    return this.y;
+};
+
+graph.mkpoint = function(x, y) {
+    return new graph.Point(x, y);
+};
+
+/**
+ * Path class.
+ */
+graph.Path = function() {
+    this.path = '';
+    this.x = 0;
+    this.y = 0;
+}
+graph.Path.prototype.pos = function(x, y) {
+    this.x = x;
+    this.y = y;
+    return this;
+};
+graph.Path.prototype.xpos = function() {
+    return this.x;
+};
+graph.Path.prototype.ypos = function() {
+    return this.y;
+};
+graph.Path.prototype.rmove = function(x, y) {
+    return this.move(this.x + x, this.y + y);
+};
+graph.Path.prototype.rline = function(x, y) {
+    return this.line(this.x + x, this.y + y);
+};
+graph.Path.prototype.rcurve = function(x1, y1, x, y) {
+    return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y);
+};
+graph.Path.prototype.str = function() {
+    return this.path;
+};
+graph.Path.prototype.clone = function() {
+    return graph.mkpath().pos(this.xpos(), this.ypos());
+};
+graph.Path.prototype.move = function(x, y) {
+    this.path += 'M' + x + ',' + y + ' '; 
+    return this.pos(x, y);
+};
+graph.Path.prototype.line = function(x, y) {
+    this.path += 'L' + x + ',' + y + ' ';
+    return this.pos(x, y);
+};
+graph.Path.prototype.curve = function(x1, y1, x, y) {
+    this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' ';
+    return this.pos(x, y);
+};
+graph.Path.prototype.end = function() {
+    this.path += 'Z';
+    return this;
+};
+
+graph.mkpath = function() {
+    return new graph.Path();
+};
+
+/**
+ * Return an element representing a title.
+ */
+graph.mktitle = function(t, x, y) {
+    var title = document.createElementNS(graph.svgns, 'text');
+    title.setAttribute('x', x);
+    title.setAttribute('y', y);
+    title.setAttribute('class', 'svgtitle');
+    title.setAttribute('pointer-events', 'none');
+    title.appendChild(document.createTextNode(t));
+    graph.svgtitles.appendChild(title);
+    return title;
+};
+
+/**
+ * Return an element representing the title of a component.
+ */
+graph.comptitle = function(comp) {
+    return memo(comp, 'title', function() {
+    	var ct = graph.title(comp);
+    	var pt = graph.propertytitle(comp);
+    	if (ct == '' && pt == '')
+    	    return null;
+        return graph.mktitle((ct != '' && pt != '')? ct + ' ' + pt : ct + pt, graph.titlex, graph.titley);
+    });
+};
+
+/**
+ * Return the width of the title of a component.
+ */
+graph.comptitlewidth = function(comp) {
+    var title = graph.comptitle(comp);
+    if (isNil(title))
+        return 0;
+    return title.getBBox().width + graph.titlew;
+};
+
+/**
+ * Draw a component shape selection.
+ */
+graph.compselect = function(g, s, cvalue, ccopy, cdelete) {
+    if (isNil(g) || !s) {
+        cvalue.value = '';
+        cvalue.disabled = true;
+        cvalue.style.visibility = 'hidden';
+        ccopy.disabled = true;
+        cdelete.disabled = true;
+        if (isNil(g))
+            return true;
+        g.shape.setAttribute('stroke', graph.colors.gray);
+        g.shape.setAttribute('stroke-width', '1');
+        return true;
+    }
+
+    cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id;
+    cvalue.disabled = false;
+    cvalue.style.visibility = 'visible';
+    ccopy.disabled = false;
+    cdelete.disabled = false;
+
+    g.shape.setAttribute('stroke', graph.colors.link);
+    g.shape.setAttribute('stroke-width', '2');
+    g.parentNode.appendChild(g);
+    return true;
+};
+
+/**
+ * Draw a palette shape selection.
+ */
+graph.paletteselect = function(g, s) {
+    if (isNil(g))
+        return true;
+    if (!s) {
+        g.shape.setAttribute('stroke', graph.colors.gray);
+        g.shape.setAttribute('stroke-width', '1');
+        return true;
+    }
+
+    g.shape.setAttribute('stroke', graph.colors.link);
+    g.shape.setAttribute('stroke-width', '2');
+    g.parentNode.appendChild(g);
+    return true;
+};
+
+/**
+ * Draw a component outline for faster rendering.
+ */
+graph.compoutline = function(g, s) {
+    if (s == (isNil(g.outlined)? false : g.outlined))
+        return true;
+    g.outlined = s;
+
+    if (s) {
+        g.shape.setAttribute('fill', 'none');
+        if (!isNil(g.title))
+            g.removeChild(g.title);
+    } else {
+        g.shape.setAttribute('fill', graph.color(g.comp));
+        if (!isNil(g.title))
+            g.appendChild(g.title);
+    }
+
+    map(function(r) {
+            var n = caddr(r);
+            if (isNil(n))
+                return r;
+            graph.compoutline(n, s);
+            return r;
+        }, g.refpos);
+    return true;
+};
+
+/**
+ * Return a node representing a component.
+ */
+graph.compnode = function(comp, cassoc, pos, parentg) {
+
+    // Make the component title element
+    var title = graph.comptitle(comp);
+
+    // Compute the path of the component shape
+    var path = graph.comppath(comp, cassoc);
+
+    // Create the main component shape
+    var shape = document.createElementNS(graph.svgns, 'path');
+    shape.setAttribute('d', path.str());
+    shape.setAttribute('fill', graph.color(comp));
+    //shape.setAttribute('fill-opacity', '0.6');
+    shape.setAttribute('stroke', graph.colors.gray);
+    shape.setAttribute('stroke-width', '1');
+    shape.setAttribute('pointer-events', 'visible');
+
+    // Create an svg group and add the shape and title to it
+    var g = document.createElementNS(graph.svgns, 'g');
+    g.comp = comp;
+    g.id = scdl.name(comp);
+    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+    g.pos = pos.clone();
+    g.appendChild(shape);
+    g.shape = shape;
+    if (!isNil(title)) {
+        g.appendChild(title);
+        g.title = title;
+    }
+
+    // Store the the positions of the services and references
+    g.refpos = reverse(path.refpos);
+    g.svcpos = reverse(path.svcpos);
+
+    // Handle onclick events
+    g.onclick = parentg.onclick;
+
+    return g;
+};
+
+/**
+ * Find the node representing a component.
+ */
+graph.findcompnode = function(name, nodes) {
+    if (isNil(nodes))
+        return null;
+    if (isNil(car(nodes).comp))
+        return graph.findcompnode(name, cdr(nodes));
+    if (name == scdl.name(car(nodes).comp))
+        return car(nodes);
+    var node = graph.findcompnode(name, nodeList(car(nodes).childNodes));
+    if (!isNil(node))
+        return node;
+    return graph.findcompnode(name, cdr(nodes));
+}
+
+/**
+ * Return a graphical group.
+ */
+graph.mkgroup = function(pos) {
+    var g = document.createElementNS(graph.svgns, 'g');
+    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+    g.pos = pos.clone();
+    return g;
+};
+
+/**
+ * Return a node representing a button.
+ */
+graph.mkbutton = function(t, pos) {
+
+    // Make the button title
+    var title = graph.mktitle(t, graph.titlex, graph.titley);
+
+    // Compute the path of the button shape
+    var path = graph.buttonpath().str();
+
+    // Create the main button shape
+    var shape = document.createElementNS(graph.svgns, 'path');
+    shape.setAttribute('d', path);
+    shape.setAttribute('fill', graph.colors.lightgray1);
+    //shape.setAttribute('fill-opacity', '0.6');
+    shape.setAttribute('stroke', graph.colors.gray);
+    shape.setAttribute('stroke-width', '1');
+    shape.setAttribute('pointer-events', 'visible');
+
+    // Create a group and add the button shape to it
+    var g = document.createElementNS(graph.svgns, 'g');
+    g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+    g.pos = pos.clone();
+    g.appendChild(shape);
+    g.appendChild(title);
+
+    // Store the button shape in the group
+    g.shape = shape;
+
+    return g;
+};
+
+/**
+ * Return the relative position of a node.
+ */
+graph.relpos = function(e) {
+    var pmatrix = e.parentNode != null? e.parentNode.getCTM() : null;
+    var matrix = e.getCTM();
+    var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
+    var curY = pmatrix != null? (Number(matrix.f) - Number(pmatrix.f)): Number(matrix.f);
+    return graph.mkpath().pos(curX, curY);
+};
+
+/**
+ * Move a node.
+ */
+graph.move = function(e, pos) {
+    e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+    e.pos = pos.clone();
+};
+
+/**
+ * Return the absolute position of a component node.
+ */
+graph.abspos = function(e, g) {
+    if (e == g)
+        return graph.mkpath();
+    var gpos = graph.relpos(e);
+    var pgpos = graph.abspos(e.parentNode, g);
+    return graph.mkpath().pos(gpos.xpos() + pgpos.xpos(), gpos.ypos() + pgpos.ypos());
+};
+
+/**
+ * Bring a component node to the top.
+ */
+graph.bringtotop = function(n, g) {
+    if (n == g)
+        return null;
+    graph.move(n, graph.abspos(n, g));
+    g.appendChild(n);
+}
+
+/**
+ * Return the title of a SCDL element.
+ */
+graph.title = function(e) {
+    var t = scdl.title(e);
+    if (t != null) {
+        if (t == 'gt')
+            return '>'
+        if (t == 'lt')
+            return '<';
+        if (t.indexOf('{propval}') != -1)
+            return '';
+        if (t.indexOf('{compname}') == -1)
+            return t;
+        return t.replace('{compname}', scdl.name(e));
+    }
+    return scdl.name(e);
+};
+
+/**
+ * Return the property value of a SCDL component.
+ */
+graph.property = function(e) {
+    var p = scdl.properties(e);
+    if (isNil(p))
+        return '';
+    if (scdl.visible(car(p)) == 'false')
+        return '';
+    var pv = scdl.propertyValue(car(p));
+    return pv;
+};
+
+/**
+ * Return the title of a property of a SCDL component.
+ */
+graph.propertytitle = function(e) {
+    var pv = graph.property(e);
+    var t = scdl.title(e);
+    if (t.indexOf('{propval}') == -1)
+        return pv;
+    return t[0] == ' '? t.substr(1).replace('{propval}', pv) : t.replace('{propval}', pv);
+};
+
+/**
+ * Return true if a SCDL component has a property.
+ */
+graph.hasproperty = function(e) {
+    var p = scdl.properties(e);
+    if (isNil(p))
+        return false;
+    if (scdl.visible(car(p)) == 'false')
+        return false;
+    return true;
+};
+
+/**
+ * Change the property value of a SCDL component.
+ */
+graph.setproperty = function(e, value) {
+    var p = scdl.properties(e);
+    if (isNil(p))
+        return '';
+    if (scdl.visible(car(p)) == 'false')
+        return '';
+    var name = scdl.name(car(p));
+    setElement(car(p), mklist(element, "'property", mklist(attribute, "'name", name != null? name : "property"), value));
+    return value;
+};
+
+/**
+ * Return the color of a SCDL component.
+ */
+graph.color = function(comp) {
+    return memo(comp, 'color', function() {
+        var c = scdl.color(comp);
+        return c == null? graph.colors.blue1 : graph.colors[c];
+    });
+};
+
+/**
+ * Return the services on the left side of a component.
+ */
+graph.lsvcs = function(comp) {
+    return memo(comp, 'lsvcs', function() {
+        var svcs = scdl.services(comp);
+        if (isNil(svcs))
+            return mklist(mklist("'element","'service","'attribute","'name",scdl.name(comp)));
+        var l = filter(function(s) {
+                var a = scdl.align(s);
+                var v = scdl.visible(s);
+                return (a == null || a == 'left') && v != 'false';
+            }, svcs);
+        if (isNil(l))
+            return mklist();
+        return mklist(car(l));
+    });
+};
+
+/**
+ * Return the references on the right side of a component.
+ */
+graph.rrefs = function(comp) {
+    return memo(comp, 'rrefs', function() {
+        return filter(function(r) {
+            var a = scdl.align(r);
+            var v = scdl.visible(r);
+            return (a == null || a == 'right') && v != 'false';
+        }, scdl.references(comp));
+    });
+};
+
+/**
+ * Return the height of a reference on the right side of a component.
+ */
+graph.rrefheight = function(ref, cassoc) {
+    return memo(ref, 'rheight', function() {
+        var target = assoc(scdl.target(ref), cassoc);
+        if (isNil(target))
+            return graph.tabsz * 8;
+        return graph.compclosureheight(cadr(target), cassoc);
+    });
+};
+
+/**
+ * Return the total height of the references on the right side of a component.
+ */
+graph.rrefsheight = function(refs, cassoc) {
+    if (isNil(refs))
+        return 0;
+    return graph.rrefheight(car(refs), cassoc) + graph.rrefsheight(cdr(refs), cassoc);
+};
+
+/**
+ * Return the height of a component node.
+ */
+graph.compheight = function(comp, cassoc) {
+    return memo(comp, 'height', function() {
+        var lsvcs = graph.lsvcs(comp);
+        var lsvcsh = Math.max(1, length(lsvcs)) * (graph.tabsz * 8) + (graph.tabsz * 4);
+        var rrefs = graph.rrefs(comp);
+        var rrefsh = graph.rrefsheight(rrefs, cassoc) + (graph.tabsz * 2);
+        return Math.max(lsvcsh, rrefsh);
+    });
+};
+
+/**
+ * Return the height of a component and the components wired to its bottom side.
+ */
+graph.compclosureheight = function(comp, cassoc) {
+    return memo(comp, 'closureheight', function() {
+        return graph.compheight(comp, cassoc);
+    });
+};
+
+/**
+ * Return the max width of the references on the right side of a component.
+ */
+graph.rrefswidth = function(refs, cassoc) {
+    if (isNil(refs))
+        return 0;
+    return Math.max(graph.rrefwidth(car(refs), cassoc), graph.rrefswidth(cdr(refs), cassoc));
+};
+
+/**
+ * Return the width of a component.
+ */
+graph.compwidth = function(comp, cassoc) {
+    return memo(comp, 'width', function() {
+        var ctw = graph.comptitlewidth(comp);
+        var rrefsw = (isNil(graph.rrefs(comp))? 0 : (graph.tabsz * 4));
+        var twidth = (graph.titlex * 2) + ctw + rrefsw;
+        var width = Math.max(twidth, (graph.tabsz * 8) + (graph.tabsz * 4));
+        return width;
+    });
+};
+
+/**
+ * Return a path representing a reference positioned to the right of a component.
+ */
+graph.rrefpath = function(ref, cassoc, path, maxheight) {
+    var height = graph.rrefheight(ref, cassoc);
+
+    // Record reference position in the path
+    var xpos = path.xpos();
+    var ypos = path.ypos();
+    path.refpos = cons(mklist(ref, graph.mkpath().pos(xpos, ypos + (graph.tabsz * 5))), path.refpos);
+
+    // Compute the reference path
+    return path.rline(0,graph.tabsz * 2).rcurve(0,graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rline(0,graph.tabsz * 3).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz).line(path.xpos(), Math.min(ypos + height, maxheight));
+};
+
+/**
+ * Return a path representing a service positioned to the left of a component.
+ */
+graph.lsvcpath = function(svc, cassoc, path, minheight) {
+    var height = graph.tabsz * 8;
+
+    // Record service position in the path
+    var xpos = path.xpos();
+    var ypos = path.ypos();
+    path.svcpos = cons(mklist(svc, graph.mkpath().pos(xpos, ypos - (graph.tabsz * 6))), path.svcpos);
+
+    // Compute the service path
+    return path.rline(0, -(graph.tabsz * 2)).rcurve(0,-graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rline(0,-(graph.tabsz * 3)).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz).line(path.xpos(), Math.max(ypos - height, minheight));
+};
+
+/**
+ * Return a path representing a component node.
+ */
+graph.comppath = function(comp, cassoc) {
+
+    // Calculate the width and height of the component node
+    var width = graph.compwidth(comp, cassoc);
+    var height = graph.compheight(comp, cassoc);
+
+    /**
+     * Apply a path rendering function to a list of services or references.
+     */
+    function renderpath(x, f, cassoc, path, height) {
+        if (isNil(x))
+            return path;
+        return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path, height), height);
+    }
+
+    var path = graph.mkpath().move(graph.curvsz,0);
+
+    // Store the positions of services and references in the path
+    path.refpos = mklist();
+    path.svcpos = mklist();
+
+    // Render the references on the right side of the component
+    var rrefs = graph.rrefs(comp);
+    path = path.line(width - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz);
+    path = renderpath(rrefs, graph.rrefpath, cassoc, path, height - graph.curvsz);
+
+    // Render the references on the bottom side of the component
+    var boffset = graph.curvsz;
+    path = path.line(path.xpos(),height - graph.curvsz).rcurve(0,graph.curvsz,graph.curvsz * -1,0).line(boffset, path.ypos());
+
+    // Render the services on the left side of the component
+    var lsvcs = graph.lsvcs(comp);
+    var loffset = graph.curvsz + (length(lsvcs) * (graph.tabsz * 8));
+    path = path.line(graph.curvsz,path.ypos()).rcurve(graph.curvsz * -1,0,0,graph.curvsz * -1).line(path.xpos(), loffset);
+    path = renderpath(lsvcs, graph.lsvcpath, cassoc, path, graph.curvsz);
+
+    // Close the component node path
+    path = path.line(0,graph.curvsz).rcurve(0,graph.curvsz * -1,graph.curvsz,0);
+
+    return path.end();
+};
+
+/**
+ * Return the position of a component.
+ */
+graph.comppos = function(comp, pos) {
+    var x = scdl.x(comp);
+    var y = scdl.y(comp);
+    return graph.mkpath().pos(x != null? Number(x) + graph.palcx : pos.xpos(), y != null? Number(y) : pos.ypos());
+};
+
+/**
+ * Return a path representing a button node.
+ */
+graph.buttonpath = function(t) {
+    var path = graph.mkpath().move(graph.curvsz,0);
+    path = path.line(graph.buttoncx - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz);
+    path = path.line(path.xpos(),graph.buttoncy - graph.curvsz).rcurve(0,graph.curvsz,-graph.curvsz,0).line(graph.curvsz, path.ypos());
+    path = path.line(graph.curvsz,path.ypos()).rcurve(-graph.curvsz,0,0,-graph.curvsz).line(path.xpos(), graph.curvsz);
+    path = path.line(0,graph.curvsz).rcurve(0,-graph.curvsz,graph.curvsz,0);
+    return path.end();
+};
+
+/**
+ * Render a SCDL composite into a list of component nodes.
+ */
+graph.composite = function(compos, pos, aspalette, g) {
+    var name = scdl.name(scdl.composite(compos));
+    var comps = scdl.components(compos);
+    var cassoc = scdl.nameToElementAssoc(comps);
+    var proms = scdl.promotions(compos);
+
+    // Unmemoize any memoized info about components and their references.
+    map(function(c) {
+            unmemo(c);
+            map(function(r) {
+                    unmemo(r);
+                }, scdl.references(c));
+        }, comps);
+
+    /**
+     * Render a component.
+     */
+    function rendercomp(comp, cassoc, pos) {
+
+        /**
+         * Render the references on the right side of a component.
+         */
+        function renderrrefs(refs, cassoc, pos, gcomp) {
+
+            /**
+             * Render a reference on the right side of a component.
+             */
+            function renderrref(ref, cassoc, pos, gcomp) {
+                var target = assoc(scdl.target(ref), cassoc);
+                if (isNil(target))
+                    return null;
+
+                // Render the component target of the reference
+                return rendercomp(cadr(target), cassoc, pos);
+            }
+
+            /**
+             * Move the rendering cursor down below a reference.
+             */
+            function rendermove(ref, cassoc, pos) {
+                return pos.clone().rmove(0, graph.rrefheight(ref, cassoc));
+            }
+
+            if (isNil(refs))
+                return mklist();
+
+            // Return list of (ref, comp rendering) pairs
+            var grefcomp = renderrref(car(refs), cassoc, pos, gcomp);
+            return cons(mklist(car(refs), grefcomp), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
+        }
+
+        // Compute the component shape
+        var gcomp = graph.compnode(comp, cassoc, pos, g);
+
+        // Render the components wired to the component references
+        var rrefs = graph.rrefs(comp);
+        var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0);
+        var grrefs = renderrrefs(rrefs, cassoc, rpos, gcomp);
+
+        // Store list of (ref, pos, component rendering) triplets in the component
+        function refposgcomp(refpos, grefs) {
+            if (isNil(refpos))
+                return mklist();
+
+            // Append component rendering to component
+            var gref = cadr(car(grefs));
+            if (gref != null)
+                gcomp.appendChild(gref);
+            return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs)));
+        }
+
+        gcomp.refpos = refposgcomp(gcomp.refpos, grrefs);
+
+        return gcomp;
+    }
+
+    /**
+     * Render a list of promoted service components.
+     */
+    function renderproms(svcs, cassoc, pos) {
+
+        /**
+         * Return the component promoted by a service.
+         */
+        function promcomp(svc, cassoc) {
+            var c = assoc(scdl.promote(svc), cassoc);
+            if (isNil(c))
+                return mklist();
+            return cadr(c);
+        }
+
+        /**
+         * Move the rendering cursor down below a component.
+         */
+        function rendermove(comp, cassoc, pos) {
+            return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((graph.tabsz * 2), 8));
+        }
+
+        if (isNil(svcs))
+            return mklist();
+
+        // Render the first promoted component in the list
+        // then recurse to render the rest of the list
+        var comp = promcomp(car(svcs), cassoc);
+        if (isNil(comp))
+            return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos));
+
+        var cpos = graph.comppos(comp, pos);
+        return cons(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos)));
+    }
+
+    // Render the promoted service components
+    var rproms = renderproms(proms, cassoc, pos.clone().rmove(graph.tabsz * 4, graph.tabsz * 4));
+
+    if (aspalette) {
+
+        // Prefix ids of palette component elements with 'palette:' and
+        // move them to the palette area
+        return map(function(r) {
+                r.id = 'palette:' + r.id;
+                var gpos = r.pos;
+                graph.move(r, graph.mkpath().pos(gpos.xpos() - graph.palcx, gpos.ypos()));
+                return r;
+            }, rproms);
+
+    } else {
+
+        // Link app component elements to the containing composite
+        return map(function(r) { r.compos = compos; return r; }, rproms);
+    }
+};
+
+/**
+ * Return a component unique id.
+ */
+graph.ucid = function(prefix, compos1, compos2, clone) {
+
+    // Build an assoc list keyed by component name
+    var comps = map(function(c) { return mklist(scdl.name(c), c); }, append(namedElementChildren("'component", compos1), namedElementChildren("'component", compos2)));
+
+    if (!clone && isNil(assoc(prefix, comps)))
+        return prefix;
+
+    /**
+     * Find a free component id.
+     */
+    function ucid(p, id) {
+        if (isNil(assoc(p + id, comps)))
+            return p + id;
+        return ucid(p, id + 1);
+    }
+
+    /**
+     * Remove trailing digits from a prefix.
+     */
+    function untrail(p) { 
+        if (p.length < 2 || p[p.length - 1] < '0' || p[p.length - 1] > '9')
+            return p;
+        return untrail(p.substring(0, p.length - 1));
+    }
+
+    return ucid(prefix == ''? 'comp' : (clone? untrail(prefix) : prefix), 1);
+};
+
+/**
+ * Clone a palette component node.
+ */
+graph.clonepalette = function(e, compos, g) {
+
+    // Clone the SCDL component and give it a unique name
+    var wcomp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos, compos, true))),
+                filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp)));
+    var x = '<composite>' + writeXML(mklist(wcomp), false) + '</composite>';
+    var rcompos = scdl.composite(readXML(mklist(x)));
+    var comp = car(scdl.components(mklist(rcompos)));
+
+    // Update component position
+    setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(graph.palcx, 0)));
+
+    return comp;
+};
+
+/**
+ * Move a SCDL component to the given position.
+ */
+graph.movecomp = function(comp, pos) {
+    if (isNil(pos))
+        return append(mklist(element, "'component"),
+                filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
+    return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.xpos() - graph.palcx)), mklist(attribute, "'y", '' + pos.ypos())),
+            filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
+};
+
+/**
+ * Align a pos along a 10pixel grid.
+ */
+graph.gridsnap = function(x) {
+    return Math.round(x / 10) * 10;
+}
+
+/**
+ * Clone a component node and all the components it references.
+ */
+graph.clonecomp = function(e, compos, g) {
+
+    // Write the component and the components it references to XML
+    function collectcomp(e) {
+        function collectrefs(refpos) {
+            if (isNil(refpos))
+                return mklist();
+            var r = car(refpos);
+            var n = caddr(r);
+            if (isNil(n))
+                return collectrefs(cdr(refpos));
+            return append(collectcomp(n), collectrefs(cdr(refpos)));
+        }
+
+        return cons(e.comp, collectrefs(e.refpos));
+    }
+
+    var allcomps = collectcomp(e);
+    var ls = map(function(e) { return writeXML(mklist(e), false); }, allcomps);
+    var x = '<composite>' + writeStrings(ls) + '</composite>';
+
+    // Read them back from XML to clone them
+    var rcompos = scdl.composite(readXML(mklist(x)));
+    var comps = scdl.components(mklist(rcompos));
+
+    // Give them new unique names
+    map(function(e) {
+
+        // Rename each component
+        var oname = scdl.name(e);
+        var name = graph.ucid(oname, compos, rcompos, true);
+        setElement(e, append(mklist(element, "'component", mklist(attribute, "'name", name)),
+                        filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e))));
+
+        // Refactor references to the component
+        map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, comps);
+    }, comps);
+
+    // Update the top component position
+    var comp = car(comps);
+    setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(10, 10)));
+
+    return comps;
+};
+
+/**
+ * Sort elements of a composite.
+ */
+graph.sortcompos = function(compos) {
+    return append(mklist(element, "'composite"), elementChildren(compos).sort(function(a, b) {
+
+        // Sort attributes, place them at the top
+        var aa = isAttribute(a);
+        var ba = isAttribute(b);
+        if (aa && !ba) return -1;
+        if (!aa && ba) return 1;
+        if (aa && ba) {
+            var aan = attributeName(a);
+            var ban = attributeName(b);
+            if (aan < ban) return -1;
+            if (aan > ban) return 1;
+            return 0;
+        }
+
+        // Sort elements, place services before components
+        var aen = elementName(a);
+        var ben = elementName(b);
+        if (aen == "'service" && ben == "'component") return -1;
+        if (aen == "'component" && ben == "'service") return 1;
+        var an = scdl.name(a);
+        var bn = scdl.name(b);
+        if (an < bn) return -1;
+        if (an > bn) return 1;
+        return 0;
+    }));
+}
+
+/**
+ * Add a list of components to a SCDL composite. The first
+ * component in the list is a promoted component.
+ */
+graph.addcomps = function(comps, compos) {
+    var comp = car(comps);
+    var name = scdl.name(comp);
+    var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
+    return append(mklist(element, "'composite"), append(elementChildren(compos), cons(prom, comps)));
+};
+
+/**
+ * Remove a component from a SCDL composite.
+ */
+graph.removecomp = function(comp, compos) {
+    var name = scdl.name(comp);
+    return append(mklist(element, "'composite"),
+            filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)));
+};
+
+/**
+ * Garbage collect components not referenced or promoted.
+ */
+graph.gcollect = function(compos) {
+
+    // List the promoted components
+    var proms = map(function(s) { return mklist(scdl.promote(s), true); }, scdl.promotions(mklist(compos)));
+
+    // List the referenced components
+    var refs = reduce(function(a, comp) {
+                return append(a,
+                    map(function(ref) { return mklist(scdl.target(ref), true); }, filter(function(ref) { return scdl.target(ref) != null; }, scdl.references(comp))));
+            }, mklist(), scdl.components(mklist(compos)));
+
+    // Filter out the unused components
+    var used = append(proms, refs);
+    return append(mklist(element, "'composite"),
+            filter(function(c) { return !(isElement(c) && elementName(c) == "'component" && isNil(assoc(scdl.name(c), used))); }, elementChildren(compos)));
+}
+
+/**
+ * Clone and cleanup clonable references.
+ */
+graph.clonerefs = function(compos) {
+    return append(mklist(element, "'composite"),
+            map(function(c) {
+                if (elementName(c) != "'component")
+                    return c;
+
+                // If the references are clonable
+                var refs = scdl.references(c);
+                if (isNil(refs))
+                    return c;
+                if (scdl.clonable(car(refs)) != 'true')
+                    return c;
+                    
+                // Filter out the unwired references and add a fresh unwired
+                // reference at the end of the list
+                var cc = append(
+                    filter(function(e) { return !(elementName(e) == "'reference" && scdl.target(e) == null); }, elementChildren(c)),
+                    mklist(mklist(element, "'reference", mklist(attribute, "'name", scdl.name(car(refs))), mklist(attribute, "'clonable", "true"))));
+                return append(mklist(element, "'component"), cc);
+            
+            }, elementChildren(compos)));
+}
+
+/**
+ * Refactor references to a component.
+ */
+graph.refactorrefs = function(refs, oname, nname) {
+    if (isNil(refs))
+        return true;
+    var ref = car(refs);
+    if (scdl.target(ref) != oname)
+        return graph.refactorrefs(cdr(refs), oname, nname);
+
+    // Change the reference's target attribute
+    setElement(ref, append(mklist(element, "'reference"),
+        append(filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(ref)),
+            mklist(mklist(attribute, "'target", nname)))));
+
+    return graph.refactorrefs(cdr(refs), oname, nname);
+};
+
+/**
+ * Rename a component.
+ */
+graph.renamecomp = function(comp, compos, name) {
+
+    // Refactor all the references to the renamed component
+    var oname = scdl.name(comp);
+    map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, namedElementChildren("'component", compos));
+
+    // Rename the SCDL promoted service and component
+    var proms = filter(function(s) { return scdl.name(s) == oname }, scdl.services(compos));
+    if (!isNil(proms))
+        setElement(car(proms), mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)));
+    setElement(comp, append(mklist(element, "'component"),
+        cons(mklist(attribute, "'name", name),
+            filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'name"); }, elementChildren(comp)))));
+
+    return append(mklist(element, "'composite"), elementChildren(compos));
+};
+
+/**
+ * Cut the wire to a component node and make that node a
+ * top level component node.
+ */
+graph.cutwire = function(node, compos, g) {
+
+    /**
+     * Find the reference wired to a node and cut its wire.
+     */
+    function cutref(refs, node) {
+        if (isNil(refs))
+            return true;
+        var ref = car(refs);
+        if (caddr(ref) == node) {
+            setlist(ref, mklist(car(ref), cadr(ref), null));
+            setElement(car(ref),
+                append(mklist(element, "'reference"),
+                    filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(car(ref)))));
+        }
+        return cutref(cdr(refs), node);
+    }
+
+    // Cut any reference wire, if found
+    cutref(node.parentNode.refpos, node);
+
+    // Make the component node a top level node.
+    node.compos = g.compos;
+
+    // Update the SCDL composite, add a promote element for
+    // that component
+    var comp = node.comp;
+    var name = scdl.name(comp);
+    var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
+    return append(mklist(element, "'composite"),
+            append(mklist(prom), filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos))));
+}
+
+/**
+ * Wire a component to the closest neighbor reference.
+ */
+graph.wire = function(n, compos, g) {
+
+    // Compute position of the component's service node
+    var spos = cadr(car(n.svcpos));
+    var aspos = graph.abspos(n, g).rmove(spos.xpos(), spos.ypos());
+
+    /**
+     * Find closest unwired reference node among all the references
+     * of all the components.
+     */
+    function closecomprefs(nodes, spos, cref) {
+
+        /**
+         * Find the closest unwired reference node among all the
+         * references of a node.
+         */
+        function closerefs(npos, refs, spos, cref) {
+            if (isNil(refs))
+                return cref;
+            var fdist = cadddr(cref);
+            var ref = car(refs);
+
+            // Skip wired reference
+            if (!isNil(filter(function(n) { return isAttribute(n) && attributeName(n) == "'target"; }, car(ref))))
+                return closerefs(npos, cdr(refs), spos, cref);
+
+            // Compute distance between service node and reference node
+            var rpos = cadr(ref).clone().rmove(npos.xpos(), npos.ypos());
+            var dx = Math.pow(rpos.xpos() - spos.xpos(), 2);
+            var dy = Math.pow(rpos.ypos() - spos.ypos(), 2);
+
+            // Check for proximity threshold
+            var rdist = (dx < (graph.proxcx * graph.proxcx) && dy < (graph.proxcy * graph.proxcy))? Math.sqrt(dx + dy) : 25000000;
+
+            // Go through all the references in the component
+            return closerefs(npos, cdr(refs), spos, fdist < rdist? cref : mklist(car(ref), cadr(ref), caddr(ref), rdist));
+        }
+
+        if (isNil(nodes))
+            return cref;
+
+        // Skip non-component nodes
+        var node = car(nodes);
+        if (isNil(node.comp))
+            return closecomprefs(cdr(nodes), spos, cref);
+
+        // Compute the component absolute position
+        var npos = graph.abspos(node, g);
+
+        // Go through all the components and their references
+        return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref));
+    }
+
+    // Find closest reference node
+    var cref = closecomprefs(nodeList(g.childNodes), aspos, mklist(null, graph.mkpath(), null, 25000000));
+    if (car(cref) == null)
+        return compos;
+    if (cadddr(cref) == 25000000)
+        return compos;
+
+    // Wire component to that reference, un-promote it, and
+    // update the SCDL reference and composite
+    setElement(n.comp, graph.movecomp(graph.dragging.comp, null));
+    n.compos = null;
+    setElement(car(cref), append(mklist(element, "'reference", mklist(attribute, "'target", scdl.name(n.comp))), elementChildren(car(cref))));
+    var name = scdl.name(n.comp);
+    return append(mklist(element, "'composite"),
+            filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos)));
+}
+
+/**
+ * Display a list of graphical nodes.
+ */
+graph.display = function(nodes, g, svg) {
+    var suspend = svg.suspendRedraw(10);
+
+    // Append the nodes to the graphical canvas
+    appendNodes(nodes, g);
+    
+    svg.unsuspendRedraw(suspend);
+    return nodes;
+};
+
+/**
+ * Hide a graph.
+ */
+graph.hide = function(g) {
+
+    // Remove nodes from the graph
+    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
+    return g;
+};
+
+/**
+ * Refresh a graph.
+ */
+graph.refresh = function(g) {
+    //log('refresh');
+
+    // Remove existing nodes from the graph
+    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
+
+    // Redisplay the composite associated with the graph
+    var nodes = graph.composite(g.compos, graph.mkpath().pos(graph.palcx,0), false, g);
+    appendNodes(nodes, g);
+    return nodes;
+};
+
+/**
+ * Display and enable editing of a composite and the graphical
+ * nodes that represent it.
+ */
+graph.edit = function(appname, compos, nodes, onchange, onselect, g) {
+    var suspend = g.suspendRedraw(10);
+
+    // Store the appname and composite in the graphical canvas
+    g.appname = appname;
+    g.compos = compos;
+
+    // Sort the composite elements now to allow for change detection later
+    var scompos = scdl.composite(g.compos);
+    setElement(scompos, graph.sortcompos(scompos));
+
+    // Store event listeners
+    g.oncomposchange = onchange;
+    g.oncompselect = onselect;
+
+    // Remove existing nodes from the graph
+    map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
+
+    // Display the composite nodes
+    appendNodes(nodes, g);
+
+    g.unsuspendRedraw(suspend);
+    return nodes;
+};
+
+/**
  * Track the current app composite and corresponding saved XML content.
  */
 var savedcomposxml = '';
@@ -216,7 +1940,7 @@
         }
 
         // Display the composite
-        graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(palcx,0), false, g), oncomposchange, oncompselect, g);
+        graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(graph.palcx,0), false, g), oncomposchange, oncompselect, g);
 
         // Track the saved composite XML
         savedcomposxml = car(writeXML(composite, false));
@@ -319,29 +2043,14 @@
 }
 
 /**
- * Return the link to a component value.
- */
-function compdatalink(appname, cname) {
-    if (cname == '' || isNil(cname))
-        return '';
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
-    if (port == ':80' || port == ':443' || port == ':')
-        port = '';
-    var link = protocol + '//' + appname + '.' + host + port + '/data/#component=' + cname;
-    return link;
-}
-
-/**
  * Return the link to a component.
  */
 function complink(appname, cname) {
     if (cname == '' || isNil(cname))
         return '';
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/c/' + cname;
@@ -355,16 +2064,14 @@
     if (gsel == gcomp)
         return true;
     gcomp = gsel;
-    var cname = isNil(gsel)? '' : gsel.id;
-    var link = compdatalink(appname, cname);
 
     function updateButton(b, v) {
         b.style.color = v? '#000000' : '#808080';
     }
 
-    updateButton(cdelete, link != '');
-    updateButton(ccopy, link != '');
-    updateButton(cplay, link != '');
+    updateButton(cdelete, !isNil(gsel));
+    updateButton(ccopy, !isNil(gsel));
+    updateButton(cplay, !isNil(gsel));
     return true;
 }
 
@@ -376,14 +2083,47 @@
         return true;
     if (isNil(gcomp))
         return true;
-    cvalue.value = complink(appname, gcomp.id);
+    var clink = complink(appname, gcomp.id);
+    cvalue.value = clink;
     cplay.innerHTML = '&lt;';
-    gdiv.style.visibility = 'hidden'
     gvisible = false;
-    pdiv.style.visibility = 'visible';
     pdiv.innerHTML = '';
-    pdiv.innerHTML = '<iframe id="dataFrame" style="position: relative; height: 5000px; width: 2500px; border: 0px;" scrolling="no" frameborder="0" src="' +
-                        compdatalink(appname, gcomp.id) + '"></iframe>';
+    pdiv.style.visibility = 'visible';
+
+    // Get the component result data
+    var comp = sca.component(gcomp.id, appname);
+    comp.getnocache('', function(doc) {
+        function displaydata(t, w) {
+            pdiv.style.width = w;
+            pdiv.innerHTML = t;
+            return true;
+        }
+
+        // Stop now if we didn't get the doc
+        if (doc == null)
+            return displaydata('No content', '2500px');
+
+        // Format data table
+        if (json.isJSON(mklist(doc)))
+            return displaydata(ui.datatable(json.readJSON(mklist(doc))), '2500px');
+
+        if (atom.isATOMEntry(mklist(doc)))
+            return displaydata(ui.datatable(atom.readATOMEntry(mklist(doc))), '2500px');
+
+        if (atom.isATOMFeed(mklist(doc)))
+            return display(ui.datatable(atom.readATOMFeed(mklist(doc))), '2500px');
+
+        // Insert the doc as is in an iframe
+        var t = '<table class="datatable" style="width: 100%;">' +
+                '<tr><td class="datatdltop">' + 'value' + '</td>' + '<td class="datatdr">' +
+                '<iframe style="width: 100%; height: 5000px;" scrolling="no" frameborder="0" src="' + clink + '"/>' +
+                '</td></tr></table>'
+        return displaydata(t, '100%');
+    });
+
+    setTimeout(function() {
+        gdiv.style.visibility = 'hidden'
+    }, 0);
     return true;
 }
 
@@ -394,11 +2134,13 @@
     if (gvisible)
         return true;
     cplay.innerHTML = '&gt;';
-    pdiv.style.visibility = 'hidden';
-    pdiv.innerHTML = '';
     gdiv.style.visibility = 'visible'
     gvisible = true;
     graph.compselect(gcomp, true, cvalue, ccopy, cdelete);
+    setTimeout(function() {
+        pdiv.style.visibility = 'hidden';
+        pdiv.innerHTML = '';
+    }, 0);
     return true;
 }
 
@@ -437,13 +2179,7 @@
 
 // Get and display the current app
 getapp(appname, g);
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/home/home.b64 b/modules/edit/htdocs/home/home.b64
new file mode 100644
index 0000000..9131135
--- /dev/null
+++ b/modules/edit/htdocs/home/home.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAaoAAACRCAIAAAAdJ2t7AAAgAElEQVR42u2d+1NT1/r/99/AD2f68ZfjOOOFGXFwhoLScqgow2E+IAylGUqDCAlBwFgU6yUwMkVyKnKGD7anSvVrcWwZLziVFioUiQJCzIWQkHATwrXCEVQUBP1Uz6d+n7XXTtgJm5ug3J5n3tMuHtfaa+2tefE8a629wrxGQ0NDW5HG4CNAQ0ND/KGhoaEh/tDQ0NAQf2hoaGiIPzQ0NDTEHxoaGhriDw0NDQ3xh4aGhob4Q0NDQ0P8oaGhoSH+0NDQ0BB/aGhoaMsSfy9fvhwbGxsdHX2G9tYMHi88ZHjUS+75L92R4/NchPe4iPA3MNyhsV4pbzz1q/mr4oZM1FsSPN5yS6763qX23obBwcHnz5/T5/981DrQ90NfV9b9zmP3O1MXpWBgWX09F/vvG/kjb3nyNK/5nsJg3KczJGnrUDMUPC5FnfGMpVnf3ePwL2GoZbAxr1+r6Ffv669NWtqCW4AbaTjT36Xn3+Piwl/rv6tKzEpk07sUPHBjR3l/f//w8PDTxxX3O9MWK/UmKq2v9wYdeUnv/b1IvbkJHuD1e230eQ73lPSr9y556glwcG9/y3V6j4sLfxD32dlXYs4sa838rS2zvF2JekuCxwsPGR41PHaIAXu67va0HaVk6e9Kfdyf+nQgdXhwMQoGBsODQQIBIQa8025NUOu4j7HOkGC0JDQ0JZibUTMVPC6jBR4dEBBiwJ62O73VCZQXD9RJT/QJI4aEZ/VLW3ALcCNwO4SAXXog4NxjwPnEH+S8lH1lrcrb9zMr+5SodyB41PDASRbccrqj+SCwD8jyn1HF/40tdsEgYaiQBWdr66TVtcC+RKNF1tYpa+9CvYnaOuEBQhZsvZvdeUsK7HuiT/yzSfa6efkIbgduimTB/f2QBS8W/L18+bK88RSN+5B9C0DAhv9pb063Nqfc7zy6JNhnJ2CP9cT+O2pJVU2Ctg7ZN3cCKrQG6+39nSpJf03CMmOfnYAPdIp+1ua4EjJv+BsbG6NrHZCOTftxVdXHfRYXVNCB5JpEPYcPy33/UXts5k1KGr5qbzoE+BvoPbyQRBved/6gd5Hl4Myb3O84JquqAfzJ6oz2j7G4MNd1x1aXLYnhjXPCgbT2qvvu9IjGhWk+hew3GHYLujgGXUhrCmhhjlfepzV0qGSAv4d3x5HxoibCqpHOFjRv1uod6EmdnOIPsLMo8Dc6Okoz39/apsdfaVkQw7x3ug1JN4naktYwTHxZ2izwZ84E9oEe3j+0kPgbiHVjmBO1B2bepL87TcLiT2ow2T7DdV4Mw6yP9ErLnSMOYorSGWZDqGlhmk+u8Rv8+HomlKCLmKJjtDDHi+/VGYB9oMd2chnDQxgm43L07CjzZq3eiUYMCRR/gJ1Fgb9nz55R/JW3T/9ZLVXBg3X9vgfKx1U9yDvH0BgeSE/yhwwjnw3+fjUrKf4e9S1Y9PfHMER/8fBXm/tG+Iurb7B9hitdGca7pHXuoIkpUTKMf3hLl6ylXfqGzbeGzT/+BG4wpgQ4uCVsHvBXT/E3pI3jeGEhIDtVJJkdZd6s1TvRs3oOf4CdRYm/nuTPXN+L/+kwG8vIw13fi81PIeWOZCjv/3//Db/8wuXvM6z5p+4uZT/DxVVR/q7U9568IBk85bVRH7q6xx50rmkXW8E1PM6dVvjsX0kq1n+xIGQzw9haRRWzzis/cc41YR8Ee7/32dkUwU6dVFT2qb1CfL58ik5nPn7BTq9c5Ya3OeA9+O+BOeOvqSjwI9tD+Czzk0djijHLJyEb132+dyN1HsmP/WMSJzR/VP/JZ5zvL7lF8f/H1dz4Zdpm2zUjnrI1u37lOvoo6C/w3zNzwV9jhZttzGszSsEjOq9cZfccOhfNfsijik6vXU99W30uVtJPfnTZOZtzg/f5Chu/GNfdodTrkVMsdW6+wSuvVDaJk4e/xoDPQyFe26lt57NGojq3er2/m+367uz1WefWtSFbSdc5Fc6XdbxBScXp/1ofE25ywN/EG5kD/mIKgrnukrNF4BkqCRCvow6XU2fBIy2Vr2Y83Q16Qhbrtx4Ms/rnil1OrRB/s4r+vkwLYxhxFHChuPjv5CmGicq5tHfN/xSFEI/rBzmq5K//5QtFueqYqn43ZHyb5R9frP+COuN/UrBxIhipmZPzAYGC6tiEQBIu5Xtad/js2R1QjL16uLyKOINPxF6p/+J0PukdEsnSKhEUPkyNKtDJD8cRvnyYkSTYqQNe2VZr4j6+qEvJOUEG8FnBF4Kdznz8gjXLecOTB5A2U0R/5R3KYvNxUHnHpPgbqw+Ei8i+juyyJqmv+jFsTvpUR5zMRm916z59wd+g+OWv+wSdf1gj4FP60cGgJmtSVb43aX4zmavJeN7Uxd/M8ySk0x0cqyf3GJL5SVurNDeI/PEU0d+zh4r+LiIoTBL9tUZcIsxyO1EgqmqUlLH8yiiIrK0JzTsCZa8ik0x7wQU+u7uzRVWVgYf8ye+VqnZpLXGu2pMtqq3ZmZNIal6qo/hj1ifuLKukTo+LdZKyXMbWPCgjlmDrfI2gkwseG03+u8ll/EpMQuEhuX5olSbsm/3s9TWck4n1zUjfnvfVhMve4t9gTAnkvFtCefgTvJHJYLfH0pKoM4CgMFn013eZ/BuTpQdaaySvKgM3Mcw2mW9LZWR1Nvn9nfVD1GvNTvJXuCtgsIL8y9umCHVqhfibdfJ7rQAen3tBn/L0wTXsv4b3L5Iy4eDPLBT+UU9rHv4MUHU25fsTEA6tOalLKzYriptT4r3Ha57k1XSaESt1qHD8yzDSqrTti6+vJhX3KMvb0q6UfbqGRcnZjDWMa1Ax1zAZfrdtzkgS7LScd33SivG9wv14/B9i+CwG/STU6XczHr9gp9+Sjv7ODa9DPkXya7yX3dZ0GFSt/+pK1fFfjMcF8ffHQFLVr7GPhhVjAwe6asPcWCpRft2wUvQcuRrBMBEhA0JO3dcQIfz1RuuBR93Jj/oSTvxtYs19R1ik6rP/CmR4RAH3WDpF8nu9qTCxuhr0490Ckz6t15omnPy2lK625YZSU83Oi8XRLV0Skymy6DRwwbvIFJ4ByIuN5BCgCTiRG65tZ51bA6tM0dq66LpKry2A5NxdLIkCamnNdj8Y3I7snWkQlyXamrcHwP2uTxd0itnmq7Yw7JXbJ8mOmUDb9f1DeJ1WEWeY0GVjeDdop569IHgjEiH2Fel/vKdKBF29dfbzctVejW7a5NecDj2vKq2IHqqJGtJEZMGvsGC/FxASXvbmgr1g3yFMfudh7q85Dj7QaSo5/MqLL4iCv9LDZUlQDi84zDJrzVlu6ePYYW9mTUbSxX+9z/DNFVLUj39yrHnAVRh/XzdzP17McSWM6zmcdtCdfzE5Bx3fIq7hl1N0ys+vv4cLMjtsrej1Q34S6vTMjMcv2OmZE/yOvpx4p/a4D8BHSQcFwB8InALJ7/C+H9M28vux46+qz5Yd561jNvp3Cznv5m92GORGxi0i6D6p+Vf9AK158MxGgj8TAeXfHnCMO0SdgnEfgI+SDgqAPxAJBifir7F4FYs5FoUav89D+QMBvygHPOkxjiwQ5UQ6DHg94xKSzfJrfO1CdMKfkA7+y+yPauc7laFCTjEXx7H9CoVgFH876xyuH8nGjGHsok240GVjeDc4EX+CNxIjFPcB+CjpoAD4A4FTAH/GsG0AsmtkEaMl29Xh4uuYTcG+I2yOzEbzTJ6dd7xWiL83WPo4dhhCadf3yCpHxzESIrGf9dPNSif8HfAmgRgbZ31gi7OUV8piTxanlDjWlAvj7z07iU7G0UAMLuX6D9UXpZAb9sg3s8n192x4ZeNLSjgzaacq5+hvHH8n5YwNf86dfjvj8Qt2mseP/iZf+oCEl2Kut/0g6KdqJeAPnDcbTzjhz/Q1BGXrinRJTx8DCqUfAf50B1n8/cVOuht77TGds7OWxHSeXTZ4ddVG3qhMeOyIv9yNE6K/yZc+IOGlmJPUakF3dUrAHzjbO76aAn/hGRA9+QeU1MQ0kqiQ+Esa2ZBqv5gDgcl/T6T3xRrWaQ8JuyKLCgILK6PZyTsb/tjojIv+xpEUuAcQoxR0Unru1JoCSfLrH1oniL8NdvyRalz0xy2YhAlddmr8Cd7IxHWbRNsK7/0qotSbNwB/4FTo1YL4o1wzKKBnt24bR7qvBZVejngFhW/t4YK71eLcCvH3Jiu/V/LJbBcTEALpZAENeegM4AT8QSDGTX4djLrSnFbwEwmv1qTuKZ0R/pg14pCC5rTv88k03GdXD7Oke//r+mPlbV98KSepd/C/klX1UWRVQS76vipJzrL4w0k6dbz+x+yU3O6iNu76kKcLdjrz8U9S82N2vjKuqO3wyVT3yeb++FHeo74vDMZMIGCxJtPSZPd/weGPUGlzlfXg2EDS1YN/JfM4+fE0+nOLDWzrO2C6Sqb5jtjm/pyc3Ixe2iddfQfabrIVMqOfCuHvqS6InWSMejCw70bmxsnm/vhRnsTQkKP+BQhobMg8pb/BYdEghD/CgsidtY0SU43/Hm4xQaIi83Rr0y5EmUyhOWRCzU/VSifvVn9+LrLORCfXXA5d56bhQpQRdaZwdurQnczNZZOah0jz8DwyYef6TeUkThZkjWRIa8l1cmOEoj+XCHr9/bbrj+NP8LKyKfEneCNTL3EMaSW3b2cBARVVN+/U/NPmt5FLHwogE6f4t1TGvCghs8Ah8oBuTbT1Bx8ob0oJ5SYEFWGAPPK3Lgt85dgK8fdGG1/MsRB5+eeQ1VJV7ad03YBdT4CPuisffx+ydQoKgtbYfgdtjhNd65lQczL82ScuMmLLybbq2GD7dcS+wRDvx0WR0En16Yes88O4Hf5kMIcFO3UizkV28YS/dCvY6azGL9jpRXYVhQ2RXdn5ymNT4+/50BFa5guc3NyfNVJm6+KjWG8ZcGnvJxzp/sb5ZdmRY2MKQSdcoa3I375G+dHekN+HFWP1QLp1TskvyZcLbI03rmMnGQ9Ojb+4pnsc8ngCpw1/pWttdJDWFtiztVURia7rGWb3OfBHnE+3/6W429Zzwelir7w7V9wCKMmm+SP3XDOu0poiFoW21eQLMZM42eb+FGRRl47Q9RAB/G2xXT+tQOLYSrgv3g2ylXew+OMKgjcyNf7+1xRHy3yB08aL6HyRCyUdWds9673JdvFtu/z6LFGnIO1d50FDwr6zHtx6iGMrxN+87fub8oWHL4ub00o7js+wPreFsEOpaksrdXiN5Hip43WuFPw9+GAUN6/XkbSZv4oybadchWk7nc34BWuyTtWMN7gM9H7BZx/86AidI0/7Djx9fMTuYUm3zvRY8cfAAZIUT+60TSAeeuR4hcnf9yA1/5jxBhep3shnH/w4+WaO9pg6U0zjhJWHltZo8LcIOSdUFrgCV7Nreue0+wobu6SmKVvN8rJT3MhkG1we3pXy2Qc/Or/CYZS8Gt/TJxnSRI8Yp3+jw6EV4u+t42+WmvkLJKpaEZt9+x448Xc2BnQ/3fzWO513TVzigHAPEl6QPe6bQk9rIer9i3pgeue8a+ISB4kBDSQRHo/7lpre2mshb7K9GcI9SHhBvLhveQrxZ4OaOelAxqdXZvb6cJEqNl7+QbD4/dgTn15sPv5uOl1U+PujO/ZMdljX4+mdiL8ZvRSsLfZJOx3ZiPhD/C0E/laanPB3r+PET9VKkMGYKZT8LiI54e+0peKuTgnKUf8yXfKLmh5/Wn1u6s0boNu3swSTX8Qf4m9Z4Q/CPbrxBQQFp6WPxYw/CPfoxhcQFJyXPlCzxB+Ee3TjCwgKE5Y+EH+Iv+WCP7rtGfA3cF9B2XftLpHTxpdFiD+67Rnwt9dYT9nXalaAnDe+oGaGP7rtGfA3oNtL2SevvANy3vhiU8u3XjKRu6rC5tdEFKZ7HZW5HZX7VJdxu1teaUSFCq+ju9wyFP5WvdMhCJF5MtfklMAReuxK2c58pW9Bth+R0rf0WhRbTdqS75chc0uWef18TQyewWuB49XYmgVnw8lyikZUoPBI3uV2NMXXUClB/KGmV7H5eLX+KyBgV9uRu+YTFH/gXAwnvkyDvy7Fj3cLgIBJavUVwyWKP/L+r8CJL6jplagzXL11FgjYVZVYWPM9xV8iOe9+wlsflFYl/nS/C/cihz5M7PD+B5NfFPPaGC5z8Nm2QLOqTmGPnvD0oW/FsTuo+W9WhQL7VCkOzrxr0YYUF8bZvIb0Th2tUtVIEX+oaVTewWW7fAm/9LbI9OyhgiKPL+GX3lAz0B5LC0UeX8IvvTVLDNnj74BS/HXnsns6Pb2sGnHhLsKsbSmhfflste0+3TXhR9lTYbJs5/2NFPlw7T192OhPwh4J43JU4Zuv9Dml8Cq8LH5dE8QS1qWwRGxQrqVM7Lvsf0rhk5fuk690t20sFY1cYzfNbvfuM0b/vIvwMSNfjPhDTa9fjA7sm+zIg0WoXqsD+yY98gA1w/xXo+Ozb9IjD/Shm3iBFsWf9ay3ePvqjG8jyeY+Fm0hirChsrBCpU9pCaSiUnrgFYc/S0Syvf12Fn+WCJaPLoVFkd0VYro3cDCfbJneJAsYaZYOloVVXw7v5p0W86LIlzaH4JHiL4Q9WkYjJ/BNZkeC+EPNKAac9sCrRRsDTnfgFWp2MeC0B169NoqydnkZasT5wbzkd1xRp7YTv1hpO9dPHyajBwJ6eg2yHhVLKLHcI8TGr9eandscste11ZVSg2I137VN5NM3njtHZbG4/LmCJrli9kdmk+cqFs2uZj3O/aHmbz/gUhHi723vB7QpJm/7BPxZxHnBztN8L0r8tjF0wm71zyUxQ9fYtFfk/6o5gkzYbfd9QWYS/QBbm7Z7qK6F5rG5MxPsp09fSyfysnIDTolWsZFgEA0M6YFam2SBNiLT4JFh1nEdlVZIEH8oxB/qbeEv3wl/RhHHIMbNoHFGTB+byTIiv9Pbac7rniXj6CaT+VqNvMoadsrP0+d2+mrKQfbEBDY89PQe4nWdV8StL7ewM4MhKTvJLCT7ljGzK/AV4g+F+EO9E/yxJx0QQnnYWdbCrpAcZafhhi57sX/qnes5YeWWcSk55yP2dJGlh5G9MmV+tGYnPTOVIq8mcJO9bAwnq8zrPPpsg6FpMk236cyjbUUF8YdC/KHecvJrzR0/93QTGwOGpIdxC7KMi0zmSldLknMjXuijhzTRQ/qYkcqgEHZCsFsvfUGRxzAyuQf95hByXpZFRLezbAt2E3sy9nPzuYlCHuAGKVsZl2S5RwjbXJwdgckvCvGHemvRX7Btf1+zpHCX83a8EBLKSTW8LTLJ6TtfOH71pdi+8tssNed62BeUxba90C8qgrhlE7igzH+Qt2mGrvPaJDVke4x3LQ8Ywm3PKMQf6q3hb+ZfcRnDHoc1k5oSNjB02rEsHSFOyZs2R/yhEH+ohcIfvvOL+EP8If4Qf4g/xB/iD/GH+EP8If4Qf4i/RY+/DpVsmePPKEf8oRB/qAn409dbb+8H/D3WLNsTT0caFBR/o6OjiD8U4g9lOwhLb7xXcxLwN6hettHfk9bvKP7GxsYQfyjEH4pTQr25sb68QyX5vTr+z6ZlyL4/m+Mf9Bgo/l6+fIn4QyH+UDb8mZubmpqaNRd6q/c80ScuMwIC+550lVD2DQ4OzpFaiD/EH+JvueGvs7PTbDZ3NFf3N5x5oFM8qZOPGBKe1S9lGeUjDQrIee1xH9jz588RfyjEH8oBf4CGtrY2q9Xav3xteHh47tRC/CH+EH/LEH80NwRbfuCDm5p73If4Q/wtFvzJEH/zJ7mlhb8n7uXLl2NjY6Ojo8+WuMEtwI3Mca0D8Yf4W1z6d3fa/jtq8k3nBhPCa+5SNLXOy5bglWCIP8TfAutBT+pJtZZEf/p6hNfc9V1bB+IP8Yf4Wxp61JdaXm8E/MWrtbK2TuTXXBTf3mXo/R3xh/hD/C0NDQ+mNjU1XdDp96i1iUYLEnAu7Cvp6bUvESD+EH+IvyWAP7pPrbql9YylWaEz7NMa9upA9StR+vpEvTGh3pxgbnZWvRn+CCo4Nflcb0ytN55paFS33rPabHBwcJEsLyD+UIi/qfBH96mVmy3Z2rr9d9R0IXgFCm58f7X6pFpbXm+EiBh+K9A4rqftjvVutvX2fnqUy7TqqZT11ybNWup9/VpFf8OZ/i79PG4uQfyhEH/T4O+HBktctVpaXStZqexz0gWdHiJi+K3QbfqhYwbI43RL2lu9503Y58DBvf0t1+drazHiD4X4mwp/d9qtZOmjVpOkrUvQ1snqjFKDKa6+YQWK3Li+Pl6t3aPWVre03mso77wV11sVD/q9On5QHfdYIx3Sxk3UE51suC5+ju+WjRgSnugTHqhZAnbp5+XFMsQfCvE3Ff4g56X4w6UPTm2d8CjOWJoh54WADiKyd3l4AXQE3ZEseD6OFUD8oRB/U218odueceOLEwEVWgM9uLS/JuEdH9wC3T3QKeblUCnEHwrxN9W2Z+6lN962Z4m2MqKufWpASGuuuu8+FtHoXH8mbedR4sJc1x1bXbYkht3ixiOtKbAPbC7apzXQtY6Hd8fB9KImwjr7Y5zfoNWTOvm8HCmK+EMh/qZ66Y1O9o+/9NZYupphPAqneQcupiSTYdaHmhzrz6zt/KnOi4FRRHql5X58nRtPTNExbmBz/dYOA13QGD+23hgewjAZl6Nnh783ajViSJiXA+URfyjE32xOfGkhCPMuaZ0pKfj1Z9t2rqp0ndAdy+UtYfOAvwnf2WYhIDtVJJnl15a/Sav5+jY1xB8K8Tdz/DX6wYeVNbd/fu++foPXJQ3xm0rd1m/wyKtkQ7wKKPucy/mv9THhJl79nGJeuQJqRpedW7ueOjZ4nyceierc6vVb14ZsBZcrW4dLmYnf3213KK3tnlMsnVA5qui0/WpeeaVkGFxvzNqMUknFaXY8DvibOIA54C+mIJjrLjlbBJ6hkgDxOupwOXUWPNJS+WrG092gJ/yyfuvBMKt/rtjl1Arxh/hD/C2Mnj1U9HcRQWGy6E9cmE1wk3EhQttAcBZxDkgUXXiEfIJDciUQXhWlM8zWoMuQY26BHJNXv5VfltZecGGYVXuyRbU1O3MSwe91qS6mRMmiINY3I92/xMQL2Vj/+sTQKk3YN/uh6HFRw6+8Pe8rgpnd2aKqyqCMWILI87ciLpEKbicKRFWNMSXceOz4ExzAZLDbY2lJ1BlAUJgs+uu77AsXkaUHWmskryoDNzHMNplvS2VkdbY7+LN+iHqt2Un4vytgsCIA/r9NEerUCvGH+EP8LYyuNxUmVleDfrxbYNKn9VrTpk1+xeeBGqER7V2hn29lSRQpImXCwV32IGuS5Dc8wx8oGVhlitbWRddVem2hrQiwAqomziQSf2At/bHdP8S5clgaDCAxkqvfHhABrEyP4XVnp569IDgAiRD7ivQ/3lMlgq7eOvt5uWqvRjdt8mtOh55XlVZED9VEDWkisjwZJtjvBYSEl725YC/YdwiTX8Qf4m+RxH0APko6KAD+QCQYnIi/xuJVwJQiNjSruwoBlF9JKSRyXufPrWUY36JiKLud14znmPz6vLIoJ5Lh23rGJSRbTIjmH9YojL+dddyPohP+QLdIXuVw8DD7o9r5FZQxvO4m4k9wADFCcR+Aj5IOCoA/EDgF8GcM2wYgu0YWMVqyXR0uvo7ZFOw7wubIeZ7EkWfnHa8V4g/xh/hbiDm+LgX3RletFnRXpwT8gbO94ytB/Plw6wmNvjsAHBsAQ+GNjf7c1J5/aF2XE/64+rwyG6/F2uK1rsiigsDCymhCtK0T1yVY/G2w4y9wtz364yqzVxvHX+AeZlr8CQ5AOvGbeW0rvPeriFJv3gD8gVOhVwvij3LNoICe3bpttOq+FlR6OeIVFL51txHR3WpxboX4Q/wh/hZ4iUNiaMhR/wIENDZkntLf4LBosOHPdB0+2WsPnRbVNhJq5JGJNmaHEtLGCBpP0RlAO/749XllSVku1F39+bnIOhOdpHM5dD1mKvwxLhHKiDpTeB6Z+3Pn5v64yjElZFZx9aELUSaugus3lbIp8Sc4gKmXOIa0ktu3s4CAiqqbd2r+afPbyKUPBZCJU/xbKmNelPiRJyEP6NZEW3/wgfKmlFBuQlARBsgjvylkga8cWyH+EH+IvwXGX1zTvYkv+YPTRgSTb8SGcVhoCwjRTpSSrc6q0+yaQw1Zqy0DHu1g2cSv79A24ny6iy0WWrU7V9xCW/lPir8tXGXXtAJJu3NlUd4Re6659tCFGHab4Vob/uzjYUFJByYwgKnx97+muIkHGYDTRqXofJELJR1Z2z3rvcl28W27/PosUacg7V3nQUPCvrMe3HqIYyvEH+IP8bfAG1ykeiOfffCjExQkja3S2WwT4dd3aNvSGl1nimlsn24TtZLNr7ukJqg8xR5DerXZ7OCbbgBOc3wP70r57IMfnV/hMEpeje/pkwxpokeM07/R4dAK8Yf4Q/wt7PZmEgMaSCLMi/sWTOxmmg1zf1VjXrY3Q7gHCS+IF/e9ayH+EH+Iv5WCP6m22CftdGQj4g/xh/hD/L1l/J22VNzVKUE56l8Ek9+VIyf8afW5qTdvgG7fzhJMfhF/iD/U0l76oBtfQFCYsPSxcvEH4R7d+AKCwoSlD8Qf4g+1NPFHtz0D/vYa6yn7Ws0KkPPGl5WHP7rtGfA3oNtL2SevvANy2vjyqjI0S+aWkR70AvGH+EMtrW3PP94tAAImqdVXDJco/sj7vxPf+lhhStQZrt46CwTsqkosrPme4i+R/a47pznBkWte7C4XryHEH+IPtbReeqPI40v4pbcVpj2WFoo8voRferNEGX7YqSkRz4Rfr4zSV4g/xB/ib5Go1+rAvkmPPFiB+a9Gx2ffZEcevKgIlG1fLZYHjFDA1YRliVZz255FXoYabpFk8Jq/7SCsVafyIxB/iCESqGQAAAU2SURBVD/E35I58GrFxoDTHng1co283MZ4+pDkVx8mpojzXCv25F5FMesBkf70BECZzJ2+IX1qtqdDI/4Qf4i/BTvtGTUZ/oo4/EH0Z80lZ61uEvnRU14K2dfaxMpwAzkIiwlJIcsj3ex7b/QgLMQf4g/xh/hbJvgzp68lZxpkc7lti5L8GKIIo37Hs7C8hxB/iD/EH+Jv2eCPnve3aVcAu74hLZWtIvhLDzco2OhPsXPEIukrCsrP9lddi3iF+EP8If4Qf8sGf69rgjZxix7uR0Wr2OJqjcZ+2vOqLKVvMjsnOKuDXhB/iD/EH+JvMeLvRQn51g5muw9d+R0qsq/wQobrqirjjvNr+dbLnviG7PIbxJVfxB/iD/G3pPH3Si9WKdmvlvN0mMt7oY8e0kw4x9QSM8ODsBB/iD/EH+JvsePPkMIdnLpJFoTv/CL+UIi/lYS/XO/kXW5HU/y6jXjkAeIPhfhbYXN/eOIL4g+F+Fsp+OtQyRYMf0Y54g/xh/hD/C0Q/vT11tv7AX+PNQtw4ulIg4Lib3R0FPGH+EP8If7e7UFYeuO9mpOAv0H1AkR/T1q/o/gbGxtD/CH+EH+Iv3eqhHpzY315h0rye3X8n03vlH1/Nsc/6DFQ/L18+RLxh/hD/CH+3i3+zM1NTU3Nmgu91Xue6BPfGQGBfU+6Sij7BgcHXy9fQ/wh/hB/ixd/nZ2dZrO5o7m6v+HMA53iSZ18xJDwrP7tyCgfaVBAzmuP+8CeP3+O+EP8If4QfwuAPwBQW1ub1WrtXwgbHh5+vawN8Yf4Q/wtavzRDBTsXYIPulvecR/iD/G3WPAnQ/wJSW5p4e+8e/ny5djY2Ojo6LO3ZnBx6GIZr3Ug/hB/i0v/7k7bf0dNvuncYELk8aVoal32G48Rf6gVjb8HPakn1VoS/enrEXl8fdfWgfhD/KGWM/4e9aWW1xsBf/FqraytE6lHFd/eZej9HfGH+EMtZ/wND6Y2NTVd0On3qLWJRgsSkLKvpKfXvhCB+EP8oZYt/ujutuqW1jOWZkV9g9zUmNDQlGBuXmmSW1oUza2Q89rjvmW/8w7xh1rp+FvY3W2L2Zb9zjvEHwrxtzC72xazrZCdd4g/1Pzgr73p0NLEX/o73t22mG2l7bxD/KHmA38WZXtzOuDv4f1DSwt/TwdO4gQ/GuIPNRf8ZbY2fgv4e9CzxPA3NFCA+END/KHeXKUtmY3mW+1NKb3th/8zumTY95/R1Af9DYg/NMQf6s0Fj52cDWe51N125HF/6pIgILBv6OFN3N2GhvhDzRV/3Nlw7eq+nov/7sl63H/s6UDq8OAiVPrw4EnIee1xH+5uQ0P8oeaEv6W7ew53t6Eh/lBzxd+S2z2Hu9vQlgn+fjV/hfhbKFVYs5bQ7jnc3Ya2rPAH/6DLG08B/n5ry0QYLYA6vsYFBDS0hcEf/DJX37sE+CtrRfwtgOp6ChF/aGgLgz9IZNp7G0rMyhJz5u37SMB3rY7fzYg/NLSFwR/Y4OCgsaMcCFjWqkQCvks191Xi7jk0tIXE3/Pnz+HjBzEgZME3m0/9du/Eb21kJQT1NlRhzars/AZyXnvch7vn0NAWDH9gw8PDeFQR7p5DQ1uJ+KMxIJ7ahrvn0NBWIv6orfBT23D3HBraysUfGhoaGuIPDQ0NDfGHhoaGhvhDQ0NDQ/yhoaGhIf7Q0NDQEH9oaGhoiD80NDQ0xB8aGhoa4g8NDQ0N8YeGhoaG+ENDQ0ND/KGhoaEh/tDQ0NAQf2hoaGiIPzQ0tJVs/x8Xr6boMQqPwwAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/modules/edit/htdocs/home.png b/modules/edit/htdocs/home/home.png
similarity index 100%
rename from modules/edit/htdocs/home.png
rename to modules/edit/htdocs/home/home.png
Binary files differ
diff --git a/modules/edit/htdocs/home/index.html b/modules/edit/htdocs/home/index.html
new file mode 100644
index 0000000..686bc67
--- /dev/null
+++ b/modules/edit/htdocs/home/index.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+-->
+<div id="bodydiv" class="bodydiv">
+
+<table style="width: 100%;">
+<tr><td><h2><span id="h1"></span></h2></td></tr>
+</table>
+
+<div style="margin-left: auto; margin-right: auto; text-align: center;">
+
+<div id="maintitle" style="font-size: 150%;"></div>
+
+<div id="maindiagram"><div id="diagram" style="width: 320px; height: 280px; padding: 0px; margin: 0px auto;"></div></div>
+
+<input type="button" class="greenbutton" style="font-size: 150%; font-weight: bold; font-style: italic; padding: 10px;" id="getstarted" title="Get Started" value="Get Started"/>
+
+<br/><br/>
+<div>Requires Safari 5+, Chrome 11+, Firefox 4+, IE 9+</div>
+
+</div>
+
+<script type="text/javascript">
+
+// Set page titles
+document.title = ui.windowtitle(location.hostname);
+$('h1').innerHTML = ui.hometitle(location.hostname);
+
+$('maintitle').innerHTML = isNil(config.maintitle)? 'Simple App Builder' : config.maintitle;
+$('getstarted').onclick = function() {
+    return ui.navigate('/#view=store', '_view');
+};
+
+// Display the main diagram
+var diagram = $('diagram');
+diagram.style.background = 'url(\'' + ui.b64img(appcache.get('/home/home.b64')) + '\')';
+var bgpos = 0;
+setInterval(function() {
+    bgpos = bgpos -280;
+    if (bgpos == -2800)
+        bgpos = 0;
+    diagram.style.backgroundPosition = '0px ' + ui.pixpos(bgpos);
+}, 2000);
+
+</script>
+
+</div>
diff --git a/modules/edit/htdocs/index.html b/modules/edit/htdocs/index.html
index 3bc871d..bba70ba 100644
--- a/modules/edit/htdocs/index.html
+++ b/modules/edit/htdocs/index.html
@@ -19,78 +19,417 @@
 -->
 <html manifest="/cache-manifest.cmf">
 <head>
-<title>Home</title>
+<title></title>
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
 <meta name="apple-mobile-web-app-capable" content="yes"/>
 <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
 <link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
+<base href="/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
 </head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-<div id="bodydiv" class="bodydiv">
+<body class="delayed" onload="onload();">
+<div id="mainbodydiv" class="mainbodydiv" style="overflow: visible;">
 
 <div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="menubackground" style="position: absolute; top: 0px; left: 0px; z-index: -1; width: 100%; visibility: hidden;">
+<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr><td class="dtbar">
+<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ltbar"><span class="tbarsmenu">>&nbsp</span></td></tr></table>
+</td></tr></table>
 </div>
 
 <div id="menu"></div>
 
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
-
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
-
-<div id="maintitle" style="font-size: 150%;"></div>
-
-<div id="maindiagram"><div id="diagram" style="width: 320px; height: 280px; background: url(home.png); padding: 0px; margin: 0px auto;"></div></div>
-
-<input type="button" class="greenbutton" style="font-size: 150%; font-weight: bold; font-style: italic; padding: 10px;" id="getstarted" title="Get Started" value="Get Started"/>
-
-<br/><br/>
-<div>Requires Safari 5+, Chrome 11+, Firefox 4+, IE 9+</div>
-
+<div id="content" class="bodydiv" style="overflow: visible;">
+<div id="viewcontainer"></div>
 </div>
 
 <script type="text/javascript">
 
-// On mobile devices, redirect to the last visited page
-if (ui.isMobile() && (document.referrer == null || document.referrer == '')) {
-    var last = ui.lastvisited();
-    if (!isNil(last) && last != document.location)
-        window.open(last, '_self');
+// Set page titles
+document.title = ui.windowtitle(location.hostname);
+
+// Init div variables
+var bdiv = $('mainbodydiv');
+var mdiv = $('menu'); 
+var cdiv = $('content'); 
+var mbgdiv = $('menubackground'); 
+mbgdiv.style.top = ui.pixpos(mdiv.offsetTop);
+var vcontainer = $('viewcontainer');
+vcontainer.className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d';
+
+/**
+ * Pre-fetch app resources.
+ */
+var appresources = [
+    ['/all-min.js'],
+    ['/ui-min.css'],
+    ['/account/', 'flip'],
+    ['/clone/', 'flip'],
+    ['/create/', 'flip'],
+    ['/footconfig.js'],
+    ['/graph/', 'flip'],
+    ['/headconfig.js'],
+    ['/home/', 'right'],
+    ['/home/home.b64'],
+    ['/page/', 'flip'],
+    ['/public/app.b64'],
+    ['/public/grid72.b64'],
+    ['/public/iframe.html'],
+    ['/public/img.b64'],
+    ['/public/user.b64'],
+    ['/stats/', 'flip'],
+    ['/store/', 'left']
+];
+
+/**
+ * Handle application cache events.
+ */
+applicationCache.addEventListener('checking', function(e) {
+    //log('appcache checking', e);
+}, false);
+applicationCache.addEventListener('error', function(e) {
+    //log('appcache error', e);
+}, false);
+applicationCache.addEventListener('noupdate', function(e) {
+    //log('appcache noupdate', e);
+}, false);
+applicationCache.addEventListener('downloading', function(e) {
+    //log('appcache downloading', e);
+}, false);
+applicationCache.addEventListener('progress', function(e) {
+    //log('appcache progress', e);
+}, false);
+applicationCache.addEventListener('updateready', function(e) {
+    //log('appcache updateready', e);
+    applicationCache.swapCache();
+    //log('appcache swapped', e);
+}, false);
+applicationCache.addEventListener('cached', function(e) {
+    //log('appcache cached', e);
+    map(function(res) {
+        appcache.get(res[0]);
+    }, appresources);
+}, false);
+
+/**
+ * Handle network offline/online events.
+ */
+window.addEventListener('offline', function(e) {
+      //log('going offline');
+}, false);
+window.addEventListener('online', function(e) {
+      //log('going online');
+}, false);
+
+//log(navigator.onLine? 'online' : 'offline');
+
+/**
+ * Handle view transitions.
+ */
+
+// Keep track of the current view url and uri
+var viewurl = '';
+var viewuri = '';
+var viewidx = '';
+var viewdiv;
+
+/**
+ * Record which transitions should be applied to each resource.
+ */
+var apptransitions = {};
+map(function(res) {
+    if (res.length > 1)
+        apptransitions[res[0]] = res[1];
+}, appresources);
+
+/**
+ * Return the transition that should be applied to a resource.
+ */
+function viewtransition(uri) {
+    var t = apptransitions[uri];
+    return isNil(t)? 'left' : t;
 }
 
-// Init and display this page
-ui.initbody();
+/**
+ * Create a new view div.
+ */
+function mkviewdiv(cname) {
+    var vdiv = document.createElement('div');
+    vdiv.className = cname;
+    if (!ui.isMobile())
+        return vdiv;
 
-// Set page titles
-document.title = windowtitle(window.location.hostname);
-$('h1').innerHTML = hometitle(window.location.hostname);
+    // Handle view transition end
+    function viewdivtransitionend(e) {
+        if (e.target.className == 'leftviewunloaded3dm' || e.target.className == 'rightviewunloaded3dm' || e.target.className == 'flipviewunloaded3dm')
+            e.target.parentNode.removeChild(e.target);
+    }
+    vdiv.addEventListener('webkitTransitionEnd', viewdivtransitionend, false);
+    vdiv.addEventListener('transitionend', viewdivtransitionend, false);
+    return vdiv;
+}
 
-// Display the menu bar
-displaymenu();
+/**
+ * Return the last visited location.
+ */
+function lastvisited() {
+    var loc = localStorage.getItem('ui.lastvisited')
+    if (!isNil(loc))
+        return loc;
+    return '' + location;
+}
 
-$('maintitle').innerHTML = isNil(config.maintitle)? 'Simple App Builder' : config.maintitle;
-$('getstarted').onclick = function() {
-    return ui.navigate('/store/', '_self');
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv, view, appname) {
+    mdiv.innerHTML = ui.menubar(
+        append(mklist(ui.menu('Home', '/', '_view', view == 'home'), ui.menu('Store', '/#view=store', '_view', view === 'store')),
+                (isNil(appname) || appname == 'undefined')?
+                    mklist() :
+                    mklist(
+                        ui.menu('Stats', '/#view=stats&app=' + appname, '_view', view == 'stats'),
+                        ui.menu('Page', '/#view=page&app=' + appname, '_view', view == 'page'),
+                        ui.menu(isNil(config.compose)? 'Composition' : config.compose, '/#view=graph&app=' + appname, '_view', view == 'graph'))),
+        mklist(ui.menu('Account', '/#view=account', '_view', view == 'account'), ui.menu('Sign out', '/logout/', '_self', false)));
+}
+
+/**
+ * Show a view.
+ */
+function showview(url) {
+    //log('showview', url);
+
+    // Save last visited location
+    localStorage.setItem('ui.lastvisited', url);
+
+    // Determine the view to show
+    var params = ui.fragmentParams(url);
+    var view = isNil(params['view'])? 'home' : params['view'];;
+    var uri = '/' + view + '/';
+    var idx = isNil(params['idx'])? '1' : params['idx'];
+
+    // Determine the transition to use
+    var vt = viewtransition(uri);
+    var ovt = viewtransition(viewuri);
+    var vtransition;
+    if (ovt == 'flip')
+        vtransition = 'flip';
+    else if (uri == viewuri && (vt == 'left' || vt == 'right'))
+        vtransition = idx >= viewidx? 'left' : 'right';
+    else
+        vtransition = vt;
+
+    // Track current view url and uri
+    viewurl = url;
+    viewuri = uri;
+    viewidx = idx;
+
+    // Show the menu bar
+    var appname = params['app'];
+    showmenu(mdiv, view, appname);
+    cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+
+    // Compute the viewport size
+    var iswide = view == 'graph' || view == 'page';
+    var vwidth = iswide? '2500px' : '100%';
+    mbgdiv.style.visibility = iswide? 'visible' : 'hidden';
+    mbgdiv.style.width = vwidth;
+
+    // Start to unload the front view and create a new view
+    if (ui.isMobile()) {
+        // Prepare current view for transition out
+        var ovdiv = viewdiv;
+        if (!isNil(ovdiv)) {
+            ovdiv.skipNode = true;
+            ovdiv.className = 'viewunloading3dm';
+        }
+
+        // Load the requested doc into a new view
+        var vdiv = mkviewdiv(vtransition + 'viewloading3dm');
+        vcontainer.appendChild(vdiv);
+        var vdoc = appcache.get(uri);
+        vdiv.innerHTML = vdoc;
+        map(ui.evalScript, ui.innerScripts(vdiv));
+
+        // Show the document
+        if (document.body.style.visibility != 'visible')
+            document.body.style.visibility = 'visible';
+
+        setTimeout(function() {
+            // Transition the old view out
+            if (!isNil(ovdiv))
+                ovdiv.className = vtransition + 'viewunloaded3dm';
+
+            // Transition the new view in
+            vdiv.className = 'viewloaded3dm';
+        }, 0);
+    } else {
+        // Prepare current view for transition out
+        var ovdiv = viewdiv;
+        if (!isNil(ovdiv))
+            ovdiv.skipNode = true;
+
+        // Load the requested doc into the view
+        var vdiv = mkviewdiv('viewloading3d');
+        vcontainer.appendChild(vdiv);
+        var vdoc = appcache.get(uri);
+        vdiv.innerHTML = vdoc;
+        map(ui.evalScript, ui.innerScripts(vdiv));
+
+        // Show the document
+        if (document.body.style.visibility != 'visible')
+            document.body.style.visibility = 'visible';
+
+        setTimeout(function() {
+            // Transition the new view in
+            vdiv.className = 'viewloaded3d';
+
+            // Transition the old view out
+            if (!isNil(ovdiv))
+                ovdiv.parentNode.removeChild(ovdiv);
+        }, 0);
+    }
+
+    // Track the current visible view
+    viewdiv = vdiv;
+
+    return true;
+}
+
+/**
+ * Handle navigations.
+ */
+window.onnavigate = function(url) {
+    //log('onnavigate', url);
+
+    // Add url to the history
+    if (url != ui.pathandparams(location)) {
+        history.pushState(null, null, url);
+        var f = ui.fragment(url);
+        if (f != '' && f != location.hash) {
+            location.hash = f;
+            //log('hash', f);
+        }
+        //log('pushstate', history.length);
+    }
+
+    // Show the specified view
+    if (url == viewurl)
+        return true;
+    return showview(url);
 };
 
-// Display the main diagram
-var diagram = $('diagram');
-var bgpos = 0;
-setInterval(function() {
-    bgpos = bgpos -280;
-    if (bgpos == -2800)
-        bgpos = 0;
-    diagram.style.backgroundPosition = '0px ' + ui.pixpos(bgpos);
-}, 2000);
+/**
+ * Handle history.
+ */
+window.addEventListener('popstate', function(e) {
+    //log('popstate', history.length);
+    var furl = ui.fragment(location);
+    var url = location.pathname + (furl == ''? '' : '#' + furl);
+
+    // Show the current view
+    if (url == viewurl)
+        return true;
+    return showview(url);
+
+}, false);
+
+window.addEventListener('hashchange', function(e) {
+    //log('onhashchange');
+    var furl = ui.fragment(location);
+    var url = location.pathname + (furl == ''? '' : '#' + furl);
+
+    // Show the current view
+    if (url == viewurl)
+        return true;
+    return showview(url);
+
+}, false);
+
+/**
+ * Document load post processing.
+ */
+function onload() {
+    //log('onload', history.length);
+    var furl = ui.fragment(location);
+    url = location.pathname + (furl == ''? '' : '#' + furl);
+
+    // Show the current view
+    if (url == viewurl)
+        return true;
+    return showview(url);
+}
+
+// Show the last visited view
+//if (ui.isMobile() && (document.referrer == null || document.referrer == '')) {
+    //log('show lastvisited');
+    //showview(lastvisited());
+//} else
+    //showview('/');
+
 </script>
 
 <div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
 </div>
 
 </div>
diff --git a/modules/edit/htdocs/login/index.html b/modules/edit/htdocs/login/index.html
index e51a6ac..2968e9e 100644
--- a/modules/edit/htdocs/login/index.html
+++ b/modules/edit/htdocs/login/index.html
@@ -17,17 +17,17 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
+<html>
 <head>
 <title>Sign in</title>
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
 <meta name="apple-mobile-web-app-capable" content="yes"/>
 <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
+<base href="/login/"/>
 <link rel="stylesheet" type="text/css" href="/ui-min.css"/>
 <script type="text/javascript" src="/all-min.js"></script>
 </head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
+<body class="delayed" onload="onload();">
 <div id="bodydiv" class="bodydiv">
 
 <h1>Sign in</h1>
@@ -44,8 +44,6 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
-
 function queryParams() {
     qp = new Array();
     qs = window.location.search.substring(1).split('&');
@@ -71,11 +69,22 @@
 }
 
 function submitSignin() {
-    var reset = 'TuscanyOpenAuth=;expires=' + new Date(1970,01,01).toGMTString() + ';domain=.' + domainname(window.location.hostname) + ';path=/;secure=TRUE';
+    var reset = 'TuscanyOpenAuth=;expires=' + new Date(1970,01,01).toGMTString() + ';domain=.' + domainname(window.location.hostname) + ';path=/';
     document.cookie = reset;
     document.formSignin.httpd_location.value = oauthReferrer();
     document.formSignin.submit();
 }
+
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 1);
+    window.scrollTo(0, 0);
+    return true;
+}
+
 </script>
 
 </div>
diff --git a/modules/edit/htdocs/logout/index.html b/modules/edit/htdocs/logout/index.html
index c1f7a57..bce5081 100644
--- a/modules/edit/htdocs/logout/index.html
+++ b/modules/edit/htdocs/logout/index.html
@@ -17,17 +17,17 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
+<html>
 <head>
 <title>Sign out</title>
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
 <meta name="apple-mobile-web-app-capable" content="yes"/>
 <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
+<base href="/logout/"/>
 <link rel="stylesheet" type="text/css" href="/ui-min.css"/>
 <script type="text/javascript" src="/all-min.js"></script>
 </head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
+<body class="delayed" onload="onload();">
 <div id="bodydiv" class="bodydiv">
 
 <h1>Sign out</h1>
@@ -37,16 +37,25 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
-
 function submitSignout() {
     // Clear session cookie and local storage
-    var reset = 'TuscanyOpenAuth=; expires=' + new Date(1970,01,01).toGMTString() + '; domain=.' + domainname(window.location.hostname) + '; path=/; secure; version=1';
+    var reset = 'TuscanyOpenAuth=; expires=' + new Date(1970,01,01).toGMTString() + '; domain=.' + domainname(window.location.hostname) + '; path=/';
     document.cookie = reset;
     localStorage.clear();
     document.signout.submit();
     return true;
 }
+
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 1);
+    window.scrollTo(0, 0);
+    return true;
+}
+
 </script>
 
 </div>
diff --git a/modules/edit/htdocs/menu.js b/modules/edit/htdocs/menu.js
deleted file mode 100644
index 42f1853..0000000
--- a/modules/edit/htdocs/menu.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
- */
-
-var editWidget = sca.component("EditWidget");
-var user= sca.defun(sca.reference(editWidget, "user"), "id");
-
-/**
- * Display the current signed in user.
- */
-function userMenu() {
-    function UserMenu() {
-        this.content = function() {
-            var u = user.id()
-            return '<span>' + u + '</span>';
-        };
-    }
-    return new UserMenu();
-}
-
-/**
- * Display the menu bar.
- */
-function displaymenu() {
-    var mdiv = $('menu'); 
-    var name = ui.fragmentParams()['app'];
-
-    mdiv.innerHTML = ui.menubar(
-        append(mklist(ui.menu('Home', '/'), ui.menu('Store', '/store/')),
-                (isNil(name) || name == 'undefined')?
-                    mklist() :
-                    mklist(
-                        ui.menu('Stats', '/stats/#app=' + name),
-                        ui.menu('Page', '/page/#app=' + name),
-                        ui.menu(isNil(config.compose)? 'Composition' : config.compose, '/graph/#app=' + name))),
-        mklist(ui.menu('Account', '/account/'), ui.menu('Sign out', '/logout/')));
-}
-
diff --git a/modules/edit/htdocs/notauth/index.html b/modules/edit/htdocs/notauth/index.html
new file mode 100644
index 0000000..4b6eb7e
--- /dev/null
+++ b/modules/edit/htdocs/notauth/index.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+-->
+<html>
+<head>
+<title>Sorry</title>
+<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, target-densitydpi=devicedpi"/> 
+<meta name="apple-mobile-web-app-capable" content="yes"/>
+<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+<base href="/notauth/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="bodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="menu"></div>
+
+<div id="content" class="viewloaded3d">
+
+<table style="width: 100%;">
+<tr><td><h2><span id="h1"></span></h2></td></tr>
+</table>
+
+<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<div class="hd2">Sorry, you're not authorized to view this page.</div>
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+// Set page title
+$('h1').innerHTML = ui.hometitle(location.hostname);
+
+// Init div variables
+var mdiv = $('menu'); 
+var cdiv = $('content'); 
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+    mdiv.innerHTML = ui.menubar(
+        mklist(ui.menu('Home', '/', '_view', false), ui.menu('Store', '/#view=store', '_view', false)),
+            mklist(ui.menu('Account', '/#view=account', '_view', false), ui.menu('Sign out', '/logout/', '_self', false)));
+}
+
+showmenu($('menu'));
+cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
+
+/**
+ * Load post processing.
+ */
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+    return true;
+}
+
+</script>
+
+<div id="footdiv" class="fsection">
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
+</div>
+
+</div>
+</body>
+</html>
diff --git a/modules/edit/htdocs/notfound/index.html b/modules/edit/htdocs/notfound/index.html
new file mode 100644
index 0000000..4d11a1a
--- /dev/null
+++ b/modules/edit/htdocs/notfound/index.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+-->
+<html>
+<head>
+<title>Page not found</title>
+<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, target-densitydpi=devicedpi"/> 
+<meta name="apple-mobile-web-app-capable" content="yes"/>
+<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+<base href="/notfound/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="bodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="menu"></div>
+
+<div id="content" class="viewloaded3d">
+
+<table style="width: 100%;">
+<tr><td><h2><span id="h1"></span></h2></td></tr>
+</table>
+
+<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<div class="hd2">Sorry, that page was not found.</div>
+<div>You may have clicked an expired link or mistyped the address.</div>
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+// Set page title
+$('h1').innerHTML = ui.hometitle(location.hostname);
+
+// Init div variables
+var mdiv = $('menu'); 
+var cdiv = $('content'); 
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+    mdiv.innerHTML = ui.menubar(
+        mklist(ui.menu('Home', '/', '_view', false), ui.menu('Store', '/#view=store', '_view', false)),
+            mklist(ui.menu('Account', '/#view=account', '_view', false), ui.menu('Sign out', '/logout/', '_self', false)));
+}
+
+showmenu(mdiv);
+div.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
+
+/**
+ * Load post processing.
+ */
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+    return true;
+}
+
+</script>
+
+<div id="footdiv" class="fsection">
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
+</div>
+
+</div>
+</body>
+</html>
diff --git a/modules/edit/htdocs/notyet/index.html b/modules/edit/htdocs/notyet/index.html
new file mode 100644
index 0000000..2f9f775
--- /dev/null
+++ b/modules/edit/htdocs/notyet/index.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+-->
+<html>
+<head>
+<title>Page not found</title>
+<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, target-densitydpi=devicedpi"/> 
+<meta name="apple-mobile-web-app-capable" content="yes"/>
+<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+<base href="/notyet/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="bodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="menu"></div>
+
+<div id="content" class="viewloaded3d">
+
+<table style="width: 100%;">
+<tr><td><h2><span id="h1"></span></h2></td></tr>
+</table>
+
+<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<div class="hd2">Sorry, that page is still under construction.</div>
+<div>Please check back later.</div>
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+// Set page title
+$('h1').innerHTML = ui.hometitle(location.hostname);
+
+// Init div variables
+var mdiv = $('menu'); 
+var cdiv = $('content'); 
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+    mdiv.innerHTML = ui.menubar(
+        mklist(ui.menu('Home', '/', '_view', false), ui.menu('Store', '/#view=store', '_view', false)),
+            mklist(ui.menu('Account', '/#view=account', '_view', false), ui.menu('Sign out', '/logout/', '_self', false)));
+}
+
+showmenu($('menu'));
+cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
+
+/**
+ * Load post processing.
+ */
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+    return true;
+}
+
+</script>
+
+<div id="footdiv" class="fsection">
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
+</div>
+
+</div>
+</body>
+</html>
diff --git a/modules/edit/htdocs/oops/index.html b/modules/edit/htdocs/oops/index.html
new file mode 100644
index 0000000..70fe399
--- /dev/null
+++ b/modules/edit/htdocs/oops/index.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+-->
+<html>
+<head>
+<title>Oops</title>
+<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, target-densitydpi=devicedpi"/> 
+<meta name="apple-mobile-web-app-capable" content="yes"/>
+<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+<base href="/oops/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+    var h = uri.indexOf('#');
+    var u = h == -1? uri : uri.substring(0, h);
+
+    // Get resource from local storage first
+    var item = localStorage.getItem(u);
+    if (item != null && item != '')
+        return item;
+
+    // Get resource from network
+    var http = new XMLHttpRequest();
+    http.open("GET", u, false);
+    http.send(null);
+    if (http.status == 200) {
+        if (http.getResponseHeader("X-Login") != null) {
+            if (log) log('http err', u, 'X-Login');
+            return null;
+        } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+            if (log) log('http err', u, 'No-Content');
+            return null;
+        }
+        localStorage.setItem(u, http.responseText);
+        return http.responseText;
+    }
+    if (log) log('http err', u, http.status, http.statusText);
+    return null;
+};
+
+// Load Javascript and CSS
+(function() {
+    var bootjs = document.createElement('script');
+    bootjs.type = 'text/javascript';
+    bootjs.text = appcache.get('/all-min.js');
+    document.head.appendChild(bootjs);
+    document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="bodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/headconfig.js')));
+})();
+</script>
+</div>
+
+<div id="menu"></div>
+
+<div id="content" class="viewloaded3d">
+
+<table style="width: 100%;">
+<tr><td><h2><span id="h1"></span></h2></td></tr>
+</table>
+
+<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<div class="hd2">Oops, something went wrong...</div>
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+// Set page title
+$('h1').innerHTML = ui.hometitle(location.hostname);
+
+// Init div variables
+var mdiv = $('menu'); 
+var cdiv = $('content'); 
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+    mdiv.innerHTML = ui.menubar(
+        mklist(ui.menu('Home', '/', '_view', false), ui.menu('Store', '/#view=store', '_view', false)),
+            mklist(ui.menu('Account', '/#view=account', '_view', false), ui.menu('Sign out', '/logout/', '_self', false)));
+}
+
+showmenu($('menu'));
+cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
+
+/**
+ * Load post processing.
+ */
+function onload() {
+    // Show the page
+    document.body.style.visibility = 'visible';
+
+    // Scroll to the top and hide the address bar
+    window.scrollTo(0, 0);
+    return true;
+}
+
+</script>
+
+<div id="footdiv" class="fsection">
+<script type="text/javascript">
+(function() {
+$('footdiv').appendChild(ui.declareScript(appcache.get('/footconfig.js')));
+})();
+</script>
+</div>
+
+</div>
+</body>
+</html>
diff --git a/modules/edit/htdocs/page/index.html b/modules/edit/htdocs/page/index.html
index ca2619e..dd33f34 100644
--- a/modules/edit/htdocs/page/index.html
+++ b/modules/edit/htdocs/page/index.html
@@ -17,33 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Page</title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-<script type="text/javascript" src="page.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv" style="overflow: visible;">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menubackground" style="position: absolute; top: 0px; left: 0px; z-index: -1; width: 2500px;">
-<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr><td class="dtbar">
-<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ltbar"><span class="tbarsmenu">&nbsp</span></td></tr></table>
-</td></tr></table>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr>
 <td><h2><span id="appNameHeader"></span></h2></td>
@@ -59,10 +34,6 @@
 
 <table id="widgetValueTable" style="width: 100%;">
 <tr>
-<td class="thr thl" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%;">
-<input id="widgetValue" type="text" value="" title="Widget value" autocapitalize="off" placeholder="Value" style="position: relative; width: 100%;"/>
-</td>
-
 <td class="thl thr" style="text-align: right; padding-right: 2px; vertical-align: top;">
 <span id="deleteWidgetButton" title="Delete a Widget" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">-</span>
 
@@ -72,13 +43,17 @@
 
 <span id="playPageButton" title="View page" class="graybutton" style="font-weight: bold; font-size: 16px; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; middle; text-align: center; margin-left: 0px; margin-right: 0px;">&gt;</span>
 </td>
+
+<td class="thl thr" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%;">
+<input id="widgetValue" type="text" value="" title="Widget value" autocapitalize="off" placeholder="Value" style="position: relative; visibility: hidden; width: 100%;"/>
+</td>
 </tr>
 </table>
 
-<div id="contentdiv" style="margin-top: 4px; width: 2500px">
+<div id="contentdiv" style="margin-top: 4px; width: 2500px;">
 <div id="editdiv" style="visibility: visible; position: relative; top: 0px; left: -2500px; width: 2500px; height: 5000px;">
 
-<div style="position: relative; left: 2500px; top: 0px; right: 0px; height: 5000px; border:1px; border-style: solid; border-color: #a2bae7; background: url(/public/grid72.png);"></div>
+<div style="position: relative; left: 2500px; top: 0px; right: 0px; height: 5000px; border:1px; border-style: solid; border-color: #a2bae7;"><span id="editgrid"></span></div>
 <div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 320px; height: 460px;"></div>
 <div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 480px; height: 300px;"></div>
 <div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 768px; height: 911px;"></div>
@@ -101,7 +76,7 @@
 <span class="link" id="palette:link" style="position: absolute; left: 0px; top: 340px;"><a href="/"><span>link</span></a></span>
 <span class="text" id="palette:text" style="position: absolute; left: 0px; top: 370px;"><span>text</span></span>
 <span class="iframe fakeframe" id="palette:iframe" style="position: absolute; left: 0px; top: 400px; width: 200px;"><a href="/public/iframe.html"><span class="fakeframe"><span>frame ...</span></span></a></span>
-<span class="img" id="palette:img" style="position: absolute; left: 0px; top: 430px;"><img src="/public/img.png"/></span>
+<span class="img" id="palette:img" style="position: absolute; left: 0px; top: 430px;"><img id="imgimg"/></span>
 </div>
 
 <div id="playdiv" style="visibility: hidden; position: absolute; top: 0px; left: 0px; width: 2500px; height: 5000px;">
@@ -112,20 +87,17 @@
 <div id="buffer" style="visibility: hidden; width: 0px; height: 0px"></div>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Get the app name
-var appname = ui.fragmentParams()['app'];
-if (isNil(appname))
-    window.open('/', '_self');
+var appname = ui.fragmentParams(location)['app'];
 
 /**
  * Return the link to an app.
  */
 function applink(appname) {
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/';
@@ -133,12 +105,9 @@
 }
 
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - Page - ' + appname;
+document.title = ui.windowtitle(location.hostname) + ' - Page - ' + appname;
 $('appNameHeader').innerHTML = '<a href=\"' + applink(appname) + '\" target=\"' + '_blank' + '\">' + appname + '</a>';
 
-// Load the menu bar
-displaymenu();
-
 /**
  * Page editor area, widget value field, add, delete and play page buttons.
  */
@@ -152,6 +121,10 @@
 var wcopy = $('copyWidgetButton');
 var pplay = $('playPageButton');
 
+// Set images
+$('editgrid').parentNode.style.background = 'url(\'' + ui.b64img(appcache.get('/public/grid72.b64')) + '\')';
+$('imgimg').src = ui.b64img(appcache.get('/public/img.b64'));
+
 // Position edit and play divs inside the content div
 ediv.style.position = 'absolute';
 ediv.style.top = cdiv.offsetTop + 'px';
@@ -159,10 +132,6 @@
 pdiv.style.top = cdiv.offsetTop + 'px';
 
 // Position background divs
-var mbackground = $('menubackground');
-var menudiv = $('menu');
-mbackground.style.top = ui.pixpos(menudiv.offsetTop);
-
 var wvbackground = $('widgetValueBackground');
 var wvtable = $('widgetValueTable');
 wvbackground.style.top = ui.pixpos(wvtable.offsetTop);
@@ -184,6 +153,569 @@
 var pages = sca.reference(editWidget, "pages");
 
 /**
+ * Page editing functions.
+ */
+var page = {};
+
+/**
+ * Default positions and sizes.
+ */
+page.palcx = 2500;
+
+/**
+ * Init a page editor. Works with all browsers except IE.
+ */
+page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
+
+    // Track element dragging and selection
+    page.dragging = null;
+    page.selected = null;
+    wvalue.disabled = true;
+    wvalue.style.visibility = 'hidden';
+    wcopy.disabled = true;
+    wdelete.disabled = true;
+
+    // Trigger widget select and page change events
+    page.onpagechange = onchange;
+    page.onwidgetselect = onselect;
+
+    /**
+     * Handle a mouse down event.
+     */
+    elem.onmousedown = function(e) {
+
+        // On mouse controlled devices, engage the click component selection
+        // logic right away
+        if (typeof e.touches == 'undefined')
+            elem.onclick(e);
+
+        // Find a draggable element
+        var dragging = page.draggable(e.target, elem);
+        if (dragging == null || dragging != page.selected)
+            return true;
+        page.dragging = dragging;
+
+        // Remember mouse position
+        var pos = typeof e.touches != "undefined"? e.touches[0] : e;
+        page.dragX = pos.screenX;
+        page.dragY = pos.screenY;
+
+        if (e.preventDefault)
+            e.preventDefault();
+        else
+            e.returnValue = false;
+        return true;
+    };
+
+    // Support touch devices
+    elem.ontouchstart = elem.onmousedown;
+
+    /**
+     * Handle a mouse up event.
+     */
+    elem.onmouseup = function(e) {
+        if (page.dragging == null)
+            return true;
+
+        // Snap to grid
+        var newX = page.gridsnap(ui.numpos(page.dragging.style.left));
+        var newY = page.gridsnap(ui.numpos(page.dragging.style.top));
+        page.dragging.style.left = ui.pixpos(newX);
+        page.dragging.style.top = ui.pixpos(newY);
+        page.dragging.cover.style.left = ui.pixpos(newX);
+        page.dragging.cover.style.top = ui.pixpos(newY);
+
+        // Fixup widget style
+        page.fixupwidget(page.dragging);
+
+        // Forget dragged element
+        page.dragging = null;
+
+        // Trigger page change event
+        page.onpagechange(false);
+        return true;
+    };
+
+    // Support touch devices
+    elem.ontouchend = elem.onmouseup;
+
+    /**
+     * Handle a mouse move event.
+     */
+    window.onmousemove = function(e) {
+        if (page.dragging == null)
+            return true;
+
+        // Get the mouse position
+        var pos = typeof e.touches != "undefined"? e.touches[0] : e;
+        if (pos.screenX == page.dragX && pos.screenY == page.dragY)
+            return true;
+
+        // Compute position of dragged element
+        var curX = ui.numpos(page.dragging.style.left);
+        var curY = ui.numpos(page.dragging.style.top);
+        var newX = curX + (pos.screenX - page.dragX);
+        var newY = curY + (pos.screenY - page.dragY);
+        if (newX >= page.palcx)
+            page.dragX = pos.screenX;
+        else
+            newX = page.palcx;
+        if (newY >= 0)
+            page.dragY = pos.screenY;
+        else
+            newY = 0;
+
+        // Move the dragged element
+        page.dragging.style.left = ui.pixpos(newX);
+        page.dragging.style.top = ui.pixpos(newY);
+        page.dragging.cover.style.left = ui.pixpos(newX);
+        page.dragging.cover.style.top = ui.pixpos(newY);
+        return true;
+    };
+
+    // Support touch devices
+    elem.ontouchmove = window.onmousemove;
+
+    /**
+     * Handle a mouse click event.
+     */
+    elem.onclick = function(e) {
+
+        // Find selected element
+        var selected = page.draggable(e.target, elem);
+        if (selected == null) {
+            if (page.selected != null) {
+
+                // Reset current selection
+                page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+                page.selected = null;
+
+                // Trigger widget select event
+                page.onwidgetselect(null);
+            }
+
+            // Dismiss the palette
+            if (ui.numpos(elem.style.left) != (page.palcx * -1))
+                elem.style.left = ui.pixpos(page.palcx * -1);
+
+            return true;
+        }
+
+        // Deselect the previously selected element
+        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+
+        // Clone element dragged from palette
+        if (selected.id.substring(0, 8) == 'palette:') {
+            page.selected = page.clone(selected);
+
+            // Move into the editing area and hide the palette
+            page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + page.palcx);
+            page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + page.palcx);
+            elem.style.left = ui.pixpos(page.palcx * -1);
+        
+            // Bring it to the top
+            page.bringtotop(page.selected);
+
+            // Trigger page change event
+            page.onpagechange(true);
+
+        } else {
+
+            // Bring selected element to the top
+            page.selected = selected;
+            page.bringtotop(page.selected);
+        }
+
+        // Select the element
+        page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
+
+        // Trigger widget select event
+        page.onwidgetselect(page.selected);
+
+        return true;
+    };
+
+    /**
+     * Handle field on change events.
+     */
+    wvalue.onchange = wvalue.onblur = function() {
+        if (page.selected == null)
+            return false;
+        page.settext(page.selected, wvalue.value);
+        page.selected.cover.style.width = ui.pixpos(page.selected.clientWidth + 4);
+        page.selected.cover.style.height = ui.pixpos(page.selected.clientHeight + 4);
+
+        // Trigger page change event
+        page.onpagechange(true);
+        return false;
+    };
+
+    // Handle add widget event.
+    wadd.onclick = function() {
+
+        // Show the palette
+        elem.style.left = ui.pixpos(0);
+        return false;
+    };
+
+    // Handle delete event.
+    wdelete.onclick = function() {
+        if (page.selected == null)
+            return false;
+
+        // Reset current selection
+        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+
+        // Remove selected widget
+        page.selected.parentNode.removeChild(page.selected);
+        page.selected.cover.parentNode.removeChild(page.selected.cover);
+        page.selected = null;
+
+        // Trigger widget select event
+        page.onwidgetselect(null);
+
+        // Trigger page change event
+        page.onpagechange(true);
+        return false;
+    };
+
+    // Handle copy event.
+    wcopy.onclick = function() {
+        if (page.selected == null)
+            return false;
+        if (page.selected.id.substring(0, 8) == 'palette:')
+            return false;
+
+        // Reset current selection
+        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+
+        // Clone selected widget
+        page.selected = page.clone(page.selected);
+
+        // Move 10 pixels down right
+        page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + 10);
+        page.selected.style.top = ui.pixpos(ui.numpos(page.selected.style.top) + 10);
+        page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + 10);
+        page.selected.cover.style.top = ui.pixpos(ui.numpos(page.selected.cover.style.top) + 10);
+    
+        // Bring it to the top
+        page.bringtotop(page.selected);
+
+        // Select the element
+        page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
+
+        // Trigger widget select event
+        page.onwidgetselect(page.selected);
+
+        // Trigger page change event
+        page.onpagechange(true);
+        return false;
+    };
+
+    // Cover child elements with span elements to prevent
+    // any input events to reach them
+    map(page.cover, nodeList(elem.childNodes));
+
+    return elem;
+};
+
+/**
+ * Return the text of a widget.
+ */
+page.text = function(e) {
+    function formula(e) {
+        var f = e.id;
+        if (f.substring(0, 5) != 'page:')
+            return '=' + f;
+        return '';
+    }
+
+    function constant(e, f) {
+        if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') {
+            var t = car(childElements(e)).innerHTML;
+            return t == f? '' : t;
+        }
+        if (e.className == 'button' || e.className == 'checkbox') {
+            var t = car(childElements(e)).value;
+            return t == f? '' : t;
+        }
+        if (e.className == 'entry' || e.className == 'password') {
+            var t = car(childElements(e)).defaultValue;
+            return t == f? '' : t;
+        }
+        if (e.className == 'select') {
+            var t = car(childElements(car(childElements(e)))).value;
+            return t == f? '' : t;
+        }
+        if (e.className == 'link') {
+            var lhr = car(childElements(e)).href;
+            var hr = lhr.substring(0, 5) == 'link:'? lhr.substring(5) : '';
+            var t = car(childElements(car(childElements(e)))).innerHTML;
+            return t == f? hr : (t == hr? hr : (t == ''? hr : hr + ',' + t));
+        }
+        if (e.className == 'img') {
+            var src = car(childElements(e)).src;
+            return src == location.href? '' : src;
+        }
+        if (e.className == 'iframe') {
+            var hr = car(childElements(e)).href;
+            return hr == location.href? '' : hr;
+        }
+        if (e.className == 'list')
+            return '';
+        if (e.className == 'table')
+            return '';
+        return '';
+    }
+
+    var f = formula(e);
+    var c = constant(e, f);
+    return f == ''? c : (c == ''? f : f + ',' + c);
+};
+
+/**
+ * Return true if a widget has editable text.
+ */
+page.hastext = function(e) {
+    if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section')
+        return true;
+    if (e.className == 'button' || e.className == 'checkbox')
+        return true;
+    if (e.className == 'entry' || e.className == 'password')
+        return true;
+    if (e.className == 'select')
+        return false;
+    if (e.className == 'link')
+        return true;
+    if (e.className == 'img')
+        return true;
+    if (e.className == 'iframe')
+        return true;
+    if (e.className == 'list')
+        return false;
+    if (e.className == 'table')
+        return false;
+    return false;
+};
+
+/**
+ * Set the text of a widget.
+ */
+page.settext = function(e, t) {
+    function formula(t) {
+        if (t.length > 1 && t.substring(0, 1) == '=')
+            return car(t.split(','));
+        return '';
+    }
+
+    function constant(t) {
+        return t.length > 1 && t.substring(0, 1) == '='? cdr(t.split(',')) : t.split(',');
+    }
+
+    var f = formula(t);
+    var c = constant(t);
+
+    e.id = f != ''? f.substring(1) : ('page:' + e.className);
+
+    if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') {
+        car(childElements(e)).innerHTML = isNil(c)? f : car(c);
+        return t;
+    }
+    if (e.className == 'button' || e.className == 'entry' || e.className == 'password') {
+        car(childElements(e)).defaultValue = isNil(c)? f : car(c);
+        return t;
+    }
+    if (e.className == 'checkbox') {
+        car(childElements(e)).value = isNil(c)? f : car(c);
+        map(function(n) { if (n.nodeName == "SPAN") n.innerHTML = isNil(c)? f : car(c); return n; }, nodeList(e.childNodes));
+        return t;
+    }
+    if (e.className == 'select') {
+        var ce = car(childElements(car(childElements(e))));
+        ce.value = isNil(c)? f : car(c);
+        ce.innerHTML = isNil(c)? f : car(c);
+        return t;
+    }
+    if (e.className == 'list') {
+        e.innerHTML = '<table class="datatable" style="width: 100%;;"><tr><td class="datatd">' + (isNil(c)? f : car(c)) + '</td></tr><tr><td class="datatd">...</td></tr></table>';
+        return t;
+    }
+    if (e.className == 'table') {
+        e.innerHTML = '<table class="datatable" style="width: 100%;"><tr><td class="datatdl">' + (isNil(c)? f : car(c)) + '</td><td class="datatdr">...</td></tr><tr><td class="datatdl">...</td><td class="datatdr">...</td></tr></table>';
+        return t;
+    }
+    if (e.className == 'link') {
+        var ce = car(childElements(e));
+        ce.href = isNil(c)? 'link:/index.html' : ('link:' + car(c));
+        car(childElements(ce)).innerHTML = isNil(c)? (f != ''? f : '/index.html') : isNil(cdr(c))? (f != ''? f : car(c)) : cadr(c);
+        return t;
+    }
+    if (e.className == 'img') {
+        car(childElements(e)).src = isNil(c)? '/public/img.png' : car(c);
+        return t;
+    }
+    if (e.className == 'iframe') {
+        car(childElements(e)).href = isNil(c)? '/public/iframe.html' : car(c);
+        return t;
+    }
+    return '';
+};
+
+/**
+ * Initial fixup of a widget.
+ */
+page.fixupwidget = function(e) {
+    if (e.className == 'iframe') {
+        var f = car(childElements(e));
+        //e.innerHTML = '<iframe src="' + f.href + '" frameborder="no" scrolling="no"></iframe>';
+        return e;
+    }
+    if (e.className == 'section') {
+        e.style.width = '100%';
+        return e;
+    }
+    if (e.className == 'list') {
+        e.style.width = '100%';
+        car(childElements(e)).style.width = '100%';
+        return e;
+    }
+    if (e.className == 'table') {
+        e.style.width = '100%';
+        car(childElements(e)).style.width = '100%';
+        return e;
+    }
+    if (e.className == 'img') {
+        var i = car(childElements(e));
+        if (i.src != '' && i.src.substring(0, 5) == 'data:')
+            i.src = '/public/img.png';
+        return e;
+    }
+    return e;
+}
+
+/**
+ * Find a draggable element in a hierarchy of elements.
+ */
+page.draggable = function(n, e) {
+    if (n == e)
+        return null;
+    if (n.id != '')
+        return n;
+    if (n.covered)
+        return n.covered;
+    return page.draggable(n.parentNode, e);
+}
+
+/**
+ * Align a pos along a 9pixel grid.
+ */
+page.gridsnap = function(x) {
+    return Math.round(x / 9) * 9;
+}
+
+/**
+ * Bring an element and its parent to the top.
+ */
+page.bringtotop = function(n) {
+    n.parentNode.appendChild(n);
+    n.cover.parentNode.appendChild(n.cover);
+}
+
+/**
+ * Draw widget selection.
+ */
+page.widgetselect = function(n, s, wvalue, wcopy, wdelete) {
+    if (isNil(n) || !s) {
+        // Clear the widget value field
+        wvalue.value = '';
+        wvalue.disabled = true;
+        wvalue.style.visibility = 'hidden';
+        wcopy.disabled = true;
+        wdelete.disabled = true;
+
+        // Clear the widget outline
+        if (!isNil(n))
+            n.cover.style.borderWidth = '0px';
+        return true;
+    }
+
+    // Update the widget value field
+    wvalue.value = page.text(n);
+    wvalue.disabled = false;
+    wvalue.style.visibility = 'visible';
+    wcopy.disabled = false;
+    wdelete.disabled = false;
+
+    // Outline the widget
+    n.cover.style.borderWidth = '2px';
+    return true;
+};
+
+/**
+ * Cover a page element with a <span> element to prevent
+ * any input events to reach it.
+ */
+page.cover = function(e) {
+    if (e.id == '' || isNil(e.style))
+        return e;
+    var cover = document.createElement('div');
+    cover.style.position = 'absolute';
+    cover.style.left = ui.pixpos(ui.numpos(e.style.left) - 2);
+    cover.style.top = ui.pixpos(ui.numpos(e.style.top) - 2);
+    cover.style.width = ui.pixpos(e.clientWidth + 4);
+    cover.style.height = ui.pixpos(e.clientHeight + 4);
+    cover.style.visibility = 'inherit';
+    cover.style.borderStyle = 'solid';
+    cover.style.borderWidth = '0px';
+    cover.style.borderColor = '#598edd';
+    cover.style.padding = '0px';
+    cover.style.margin = '0px';
+    cover.covered = e;
+    e.cover = cover;
+    e.parentNode.appendChild(cover);
+    return e;
+}
+
+/**
+ * Clone a palette element.
+ */
+page.clone = function(e) {
+
+    /**
+     * Clone an element's HTML.
+     */
+    function mkclone(e) {
+        var ne = document.createElement('span');
+
+        // Skip the palette: prefix
+        ne.id = 'page:' + e.id.substr(8);
+
+        // Copy the class and HTML content
+        ne.className = e.className;
+        ne.innerHTML = e.innerHTML;
+
+        // Fixup the widget style
+        page.fixupwidget(ne);
+
+        return ne;
+    }
+
+    /**
+     * Clone an element's position.
+     */
+    function posclone(ne, e) {
+        ne.style.position = 'absolute';
+        ne.style.left = ui.pixpos(ui.numpos(e.style.left));
+        ne.style.top = ui.pixpos(ui.numpos(e.style.top));
+        e.parentNode.appendChild(ne);
+        page.cover(ne);
+        return ne;
+    }
+
+    return posclone(mkclone(e), e);
+};
+
+/**
  * Return the page in an ATOM entry.
  */
 function atompage(doc) {
@@ -373,35 +905,19 @@
 }
 
 /**
- * Return the link to a component value.
- */
-function compvaluelink(appname, cname) {
-    if (cname == '' || isNil(cname))
-        return '';
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
-    if (port == ':80' || port == ':443' || port == ':')
-        port = '';
-    var link = protocol + '//' + appname + '.' + host + port + '/data/#component=' + cname;
-    return link;
-}
-
-/**
  * Handle a widget select event.
  */
 function onwidgetselect(w) {
     if (w == widget)
         return true;
     widget = w;
-    var link = compvaluelink(appname, isNil(w)? '' : w.id);
 
     function updateButton(b, v) {
         b.style.color = v? '#000000' : '#808080';
     }
 
-    updateButton(wdelete, link != '');
-    updateButton(wcopy, link != '');
+    updateButton(wdelete, !isNil(w));
+    updateButton(wcopy, !isNil(w));
     return true;
 }
 
@@ -415,12 +931,14 @@
     page.selected = null;
     wvalue.value = applink(appname);
     pplay.innerHTML = '&lt;';
-    ediv.style.visibility = 'hidden'
     evisible = false;
     pdiv.style.visibility = 'visible';
     pdiv.innerHTML = '';
     pdiv.innerHTML = '<iframe id="playappframe" style="position: relative; height: 5000px; width: 2500px; border: 0px;" scrolling="no" frameborder="0" src="' +
                         applink(appname) + '"></iframe>';
+    setTimeout(function() {
+        ediv.style.visibility = 'hidden'
+    }, 0);
     return true;
 }
 
@@ -431,12 +949,14 @@
     if (evisible)
         return true;
     pplay.innerHTML = '&gt;';
-    pdiv.style.visibility = 'hidden';
-    pdiv.innerHTML = '';
     ediv.style.visibility = 'visible'
     evisible = true;
     page.widgetselect(widget, true, wvalue, wcopy, wdelete);
     page.selected = widget;
+    setTimeout(function() {
+        pdiv.style.visibility = 'hidden';
+        pdiv.innerHTML = '';
+    }, 0);
     return true;
 }
 
@@ -454,13 +974,7 @@
 
 // Get and display the current app page
 getpage(appname, ediv);
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/page/page.js b/modules/edit/htdocs/page/page.js
deleted file mode 100644
index 2fd88c0..0000000
--- a/modules/edit/htdocs/page/page.js
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
- */
-
-/**
- * Page editing functions.
- */
-var page = {};
-
-/**
- * Default positions and sizes.
- */
-var palcx = 2500;
-
-/**
- * Init a page editor. Works with all browsers except IE.
- */
-page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
-
-    // Track element dragging and selection
-    page.dragging = null;
-    page.selected = null;
-    wvalue.disabled = true;
-    wcopy.disabled = true;
-    wdelete.disabled = true;
-
-    // Trigger widget select and page change events
-    page.onpagechange = onchange;
-    page.onwidgetselect = onselect;
-
-    /**
-     * Handle a mouse down event.
-     */
-    elem.onmousedown = function(e) {
-
-        // On mouse controlled devices, engage the click component selection
-        // logic right away
-        if (typeof e.touches == 'undefined')
-            elem.onclick(e);
-
-        // Find a draggable element
-        var dragging = page.draggable(e.target, elem);
-        if (dragging == null || dragging != page.selected)
-            return true;
-        page.dragging = dragging;
-
-        // Remember mouse position
-        var pos = typeof e.touches != "undefined"? e.touches[0] : e;
-        page.dragX = pos.screenX;
-        page.dragY = pos.screenY;
-
-        if (e.preventDefault)
-            e.preventDefault();
-        else
-            e.returnValue = false;
-        return true;
-    };
-
-    // Support touch devices
-    elem.ontouchstart = elem.onmousedown;
-
-    /**
-     * Handle a mouse up event.
-     */
-    elem.onmouseup = function(e) {
-        if (page.dragging == null)
-            return true;
-
-        // Snap to grid
-        var newX = page.gridsnap(ui.numpos(page.dragging.style.left));
-        var newY = page.gridsnap(ui.numpos(page.dragging.style.top));
-        page.dragging.style.left = ui.pixpos(newX);
-        page.dragging.style.top = ui.pixpos(newY);
-        page.dragging.cover.style.left = ui.pixpos(newX);
-        page.dragging.cover.style.top = ui.pixpos(newY);
-
-        // Fixup widget style
-        page.fixupwidget(page.dragging);
-
-        // Forget dragged element
-        page.dragging = null;
-
-        // Trigger page change event
-        page.onpagechange(false);
-        return true;
-    };
-
-    // Support touch devices
-    elem.ontouchend = elem.onmouseup;
-
-    /**
-     * Handle a mouse move event.
-     */
-    window.onmousemove = function(e) {
-        if (page.dragging == null)
-            return true;
-
-        // Get the mouse position
-        var pos = typeof e.touches != "undefined"? e.touches[0] : e;
-        if (pos.screenX == page.dragX && pos.screenY == page.dragY)
-            return true;
-
-        // Compute position of dragged element
-        var curX = ui.numpos(page.dragging.style.left);
-        var curY = ui.numpos(page.dragging.style.top);
-        var newX = curX + (pos.screenX - page.dragX);
-        var newY = curY + (pos.screenY - page.dragY);
-        if (newX >= palcx)
-            page.dragX = pos.screenX;
-        else
-            newX = palcx;
-        if (newY >= 0)
-            page.dragY = pos.screenY;
-        else
-            newY = 0;
-
-        // Move the dragged element
-        page.dragging.style.left = ui.pixpos(newX);
-        page.dragging.style.top = ui.pixpos(newY);
-        page.dragging.cover.style.left = ui.pixpos(newX);
-        page.dragging.cover.style.top = ui.pixpos(newY);
-        return true;
-    };
-
-    // Support touch devices
-    elem.ontouchmove = window.onmousemove;
-
-    /**
-     * Handle a mouse click event.
-     */
-    elem.onclick = function(e) {
-
-        // Find selected element
-        var selected = page.draggable(e.target, elem);
-        if (selected == null) {
-            if (page.selected != null) {
-
-                // Reset current selection
-                page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
-                page.selected = null;
-
-                // Trigger widget select event
-                page.onwidgetselect(null);
-            }
-
-            // Dismiss the palette
-            if (ui.numpos(elem.style.left) != (palcx * -1))
-                elem.style.left = ui.pixpos(palcx * -1);
-
-            return true;
-        }
-
-        // Deselect the previously selected element
-        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
-
-        // Clone element dragged from palette
-        if (selected.id.substring(0, 8) == 'palette:') {
-            page.selected = page.clone(selected);
-
-            // Move into the editing area and hide the palette
-            page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + palcx);
-            page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + palcx);
-            elem.style.left = ui.pixpos(palcx * -1);
-        
-            // Bring it to the top
-            page.bringtotop(page.selected);
-
-            // Trigger page change event
-            page.onpagechange(true);
-
-        } else {
-
-            // Bring selected element to the top
-            page.selected = selected;
-            page.bringtotop(page.selected);
-        }
-
-        // Select the element
-        page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
-
-        // Trigger widget select event
-        page.onwidgetselect(page.selected);
-
-        return true;
-    };
-
-    /**
-     * Handle field on change events.
-     */
-    wvalue.onchange = wvalue.onblur = function() {
-        if (page.selected == null)
-            return false;
-        page.settext(page.selected, wvalue.value);
-        page.selected.cover.style.width = ui.pixpos(page.selected.clientWidth + 4);
-        page.selected.cover.style.height = ui.pixpos(page.selected.clientHeight + 4);
-
-        // Trigger page change event
-        page.onpagechange(true);
-        return false;
-    };
-
-    // Handle add widget event.
-    wadd.onclick = function() {
-
-        // Show the palette
-        elem.style.left = ui.pixpos(0);
-        return false;
-    };
-
-    // Handle delete event.
-    wdelete.onclick = function() {
-        if (page.selected == null)
-            return false;
-
-        // Reset current selection
-        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
-
-        // Remove selected widget
-        page.selected.parentNode.removeChild(page.selected);
-        page.selected.cover.parentNode.removeChild(page.selected.cover);
-        page.selected = null;
-
-        // Trigger widget select event
-        page.onwidgetselect(null);
-
-        // Trigger page change event
-        page.onpagechange(true);
-        return false;
-    };
-
-    // Handle copy event.
-    wcopy.onclick = function() {
-        if (page.selected == null)
-            return false;
-        if (page.selected.id.substring(0, 8) == 'palette:')
-            return false;
-
-        // Reset current selection
-        page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
-
-        // Clone selected widget
-        page.selected = page.clone(page.selected);
-
-        // Move 10 pixels down right
-        page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + 10);
-        page.selected.style.top = ui.pixpos(ui.numpos(page.selected.style.top) + 10);
-        page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + 10);
-        page.selected.cover.style.top = ui.pixpos(ui.numpos(page.selected.cover.style.top) + 10);
-    
-        // Bring it to the top
-        page.bringtotop(page.selected);
-
-        // Select the element
-        page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
-
-        // Trigger widget select event
-        page.onwidgetselect(page.selected);
-
-        // Trigger page change event
-        page.onpagechange(true);
-        return false;
-    };
-
-    // Cover child elements with span elements to prevent
-    // any input events to reach them
-    map(page.cover, nodeList(elem.childNodes));
-
-    return elem;
-};
-
-/**
- * Return the text of a widget.
- */
-page.text = function(e) {
-    function formula(e) {
-        var f = e.id;
-        if (f.substring(0, 5) != 'page:')
-            return '=' + f;
-        return '';
-    }
-
-    function constant(e, f) {
-        if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') {
-            var t = car(childElements(e)).innerHTML;
-            return t == f? '' : t;
-        }
-        if (e.className == 'button' || e.className == 'checkbox') {
-            var t = car(childElements(e)).value;
-            return t == f? '' : t;
-        }
-        if (e.className == 'entry' || e.className == 'password') {
-            var t = car(childElements(e)).defaultValue;
-            return t == f? '' : t;
-        }
-        if (e.className == 'select') {
-            var t = car(childElements(car(childElements(e)))).value;
-            return t == f? '' : t;
-        }
-        if (e.className == 'link') {
-            var lhr = car(childElements(e)).href;
-            var hr = lhr.substring(0, 5) == 'link:'? lhr.substring(5) : '';
-            var t = car(childElements(car(childElements(e)))).innerHTML;
-            return t == f? hr : (t == hr? hr : (t == ''? hr : hr + ',' + t));
-        }
-        if (e.className == 'img') {
-            var src = car(childElements(e)).src;
-            return src == window.location.href? '' : src;
-        }
-        if (e.className == 'iframe') {
-            var hr = car(childElements(e)).href;
-            return hr == window.location.href? '' : hr;
-        }
-        if (e.className == 'list')
-            return '';
-        if (e.className == 'table')
-            return '';
-        return '';
-    }
-
-    var f = formula(e);
-    var c = constant(e, f);
-    return f == ''? c : (c == ''? f : f + ',' + c);
-};
-
-/**
- * Return true if a widget has editable text.
- */
-page.hastext = function(e) {
-    if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section')
-        return true;
-    if (e.className == 'button' || e.className == 'checkbox')
-        return true;
-    if (e.className == 'entry' || e.className == 'password')
-        return true;
-    if (e.className == 'select')
-        return false;
-    if (e.className == 'link')
-        return true;
-    if (e.className == 'img')
-        return true;
-    if (e.className == 'iframe')
-        return true;
-    if (e.className == 'list')
-        return false;
-    if (e.className == 'table')
-        return false;
-    return false;
-};
-
-/**
- * Set the text of a widget.
- */
-page.settext = function(e, t) {
-    function formula(t) {
-        if (t.length > 1 && t.substring(0, 1) == '=')
-            return car(t.split(','));
-        return '';
-    }
-
-    function constant(t) {
-        return t.length > 1 && t.substring(0, 1) == '='? cdr(t.split(',')) : t.split(',');
-    }
-
-    var f = formula(t);
-    var c = constant(t);
-
-    e.id = f != ''? f.substring(1) : ('page:' + e.className);
-
-    if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') {
-        car(childElements(e)).innerHTML = isNil(c)? f : car(c);
-        return t;
-    }
-    if (e.className == 'button' || e.className == 'entry' || e.className == 'password') {
-        car(childElements(e)).defaultValue = isNil(c)? f : car(c);
-        return t;
-    }
-    if (e.className == 'checkbox') {
-        car(childElements(e)).value = isNil(c)? f : car(c);
-        map(function(n) { if (n.nodeName == "SPAN") n.innerHTML = isNil(c)? f : car(c); return n; }, nodeList(e.childNodes));
-        return t;
-    }
-    if (e.className == 'select') {
-        var ce = car(childElements(car(childElements(e))));
-        ce.value = isNil(c)? f : car(c);
-        ce.innerHTML = isNil(c)? f : car(c);
-        return t;
-    }
-    if (e.className == 'list') {
-        e.innerHTML = '<table class="datatable" style="width: 100%;;"><tr><td class="datatd">' + (isNil(c)? f : car(c)) + '</td></tr><tr><td class="datatd">...</td></tr></table>';
-        return t;
-    }
-    if (e.className == 'table') {
-        e.innerHTML = '<table class="datatable" style="width: 100%;"><tr><td class="datatdl">' + (isNil(c)? f : car(c)) + '</td><td class="datatdr">...</td></tr><tr><td class="datatdl">...</td><td class="datatdr">...</td></tr></table>';
-        return t;
-    }
-    if (e.className == 'link') {
-        var ce = car(childElements(e));
-        ce.href = isNil(c)? 'link:/index.html' : ('link:' + car(c));
-        car(childElements(ce)).innerHTML = isNil(c)? (f != ''? f : '/index.html') : isNil(cdr(c))? (f != ''? f : car(c)) : cadr(c);
-        return t;
-    }
-    if (e.className == 'img') {
-        car(childElements(e)).src = isNil(c)? '/public/img.png' : car(c);
-        return t;
-    }
-    if (e.className == 'iframe') {
-        car(childElements(e)).href = isNil(c)? '/public/iframe.html' : car(c);
-        return t;
-    }
-    return '';
-};
-
-/**
- * Initial fixup of a widget.
- */
-page.fixupwidget = function(e) {
-    if (e.className == 'iframe') {
-        var f = car(childElements(e));
-        //e.innerHTML = '<iframe src="' + f.href + '" frameborder="no" scrolling="no"></iframe>';
-        return e;
-    }
-    if (e.className == 'section') {
-        e.style.width = '100%';
-        return e;
-    }
-    if (e.className == 'list') {
-        e.style.width = '100%';
-        car(childElements(e)).style.width = '100%';
-        return e;
-    }
-    if (e.className == 'table') {
-        e.style.width = '100%';
-        car(childElements(e)).style.width = '100%';
-        return e;
-    }
-    return e;
-}
-
-/**
- * Find a draggable element in a hierarchy of elements.
- */
-page.draggable = function(n, e) {
-    if (n == e)
-        return null;
-    if (n.id != '')
-        return n;
-    if (n.covered)
-        return n.covered;
-    return page.draggable(n.parentNode, e);
-}
-
-/**
- * Align a pos along a 9pixel grid.
- */
-page.gridsnap = function(x) {
-    return Math.round(x / 9) * 9;
-}
-
-/**
- * Bring an element and its parent to the top.
- */
-page.bringtotop = function(n) {
-    n.parentNode.appendChild(n);
-    n.cover.parentNode.appendChild(n.cover);
-}
-
-/**
- * Draw widget selection.
- */
-page.widgetselect = function(n, s, wvalue, wcopy, wdelete) {
-    if (isNil(n) || !s) {
-        // Clear the widget value field
-        wvalue.value = '';
-        wvalue.disabled = true;
-        wcopy.disabled = true;
-        wdelete.disabled = true;
-
-        // Clear the widget outline
-        if (!isNil(n))
-            n.cover.style.borderWidth = '0px';
-        return true;
-    }
-
-    // Update the widget value field
-    wvalue.value = page.text(n);
-    wvalue.disabled = false;
-    wcopy.disabled = false;
-    wdelete.disabled = false;
-
-    // Outline the widget
-    n.cover.style.borderWidth = '2px';
-    return true;
-};
-
-/**
- * Cover a page element with a <span> element to prevent
- * any input events to reach it.
- */
-page.cover = function(e) {
-    if (e.id == '' || isNil(e.style))
-        return e;
-    var cover = document.createElement('div');
-    cover.style.position = 'absolute';
-    cover.style.left = ui.pixpos(ui.numpos(e.style.left) - 2);
-    cover.style.top = ui.pixpos(ui.numpos(e.style.top) - 2);
-    cover.style.width = ui.pixpos(e.clientWidth + 4);
-    cover.style.height = ui.pixpos(e.clientHeight + 4);
-    cover.style.visibility = 'inherit';
-    cover.style.borderStyle = 'solid';
-    cover.style.borderWidth = '0px';
-    cover.style.borderColor = '#598edd';
-    cover.style.padding = '0px';
-    cover.style.margin = '0px';
-    cover.covered = e;
-    e.cover = cover;
-    e.parentNode.appendChild(cover);
-    return e;
-}
-
-/**
- * Clone a palette element.
- */
-page.clone = function(e) {
-
-    /**
-     * Clone an element's HTML.
-     */
-    function mkclone(e) {
-        var ne = document.createElement('span');
-
-        // Skip the palette: prefix
-        ne.id = 'page:' + e.id.substr(8);
-
-        // Copy the class and HTML content
-        ne.className = e.className;
-        ne.innerHTML = e.innerHTML;
-
-        // Fixup the widget style
-        page.fixupwidget(ne);
-
-        return ne;
-    }
-
-    /**
-     * Clone an element's position.
-     */
-    function posclone(ne, e) {
-        ne.style.position = 'absolute';
-        ne.style.left = ui.pixpos(ui.numpos(e.style.left));
-        ne.style.top = ui.pixpos(ui.numpos(e.style.top));
-        e.parentNode.appendChild(ne);
-        page.cover(ne);
-        return ne;
-    }
-
-    return posclone(mkclone(e), e);
-};
-
diff --git a/modules/edit/htdocs/public/app.b64 b/modules/edit/htdocs/public/app.b64
new file mode 100644
index 0000000..7ed235a
--- /dev/null
+++ b/modules/edit/htdocs/public/app.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAABGdBTUEAALGPC/xhBQAAAAxQTFRFyN+N+dR1/PCI////6HjE5gAAADJJREFUKM9j+I8EPjBQifeBAQSY6coLBYN6inhaq0Bg6SDn/f//akB466ExTS6P2ukMAKumzarJO/66AAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/delete.b64 b/modules/edit/htdocs/public/delete.b64
new file mode 100644
index 0000000..c8137d7
--- /dev/null
+++ b/modules/edit/htdocs/public/delete.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAPwBBXloXjQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sEFhQaKzNh4PgAAAMKSURBVEjHxZZPTBNBFMa/maVbWjcUi0YiIHIoNBADTUgsqCWgUUFjwkk5CXLUBKIc9KIXjx64oMSDoiggGC8koImCGDWYkADRIiQQgikWCq0WoXW33R0PpYjSLeWP8btN3sv85s17894QrKNeIBng8gFmJSDZgGIAqJeBjQCkH5AHioGZaHsQNUMP+ByKYB0ByVjvIAxsUkHcrRJI9pggXYBWB1pLQUqxQSlg3X4o9WWAqArpAhL04JoIYMQmxQCPD3JlGbCwBtIFaPXgWrcC+AtUEY6Ihg060NrtACyf3KgDrQ2v6e8kbzwH0URBSnvA56xAKIJ1kRzNbS2ZNhYssjodVj41VbPaxqemaqxOh9XGgkXmtpbMyKDQvqQXSKbg2iKGzfPE0v8uV7BYDIuDg95B66FhJkmM8DyxfHifK+TlGRaHhryDBwuHmSSxyBUnn6Ohh6aSQElin86U26XZWVGwWAxZD5tMAGBufmAS8vIMkssl2s+Uj6gBQuLySS/oTQpyONr9GmxHhAMvnltovJZ+73vjTiyyJSmipHw8WTrkfd33Y52385arAr1EAF00R3HqixRwu38mnT61O35/uh4AJq7Ujc0/affEUGsCDfWi9TXX3uEOeDwBABCnp/3OO42uGPuAgQLUG4urueVRlsZo1ACANiVFZ7rTkBFjMXtpqJtGV9q1q3uNJ47vlpd88kTt5VEWCLLk6gtpeyrP74qheY5wlaB6AhSqOSUUFOzIun8vh8RxZKKmZvRrw20X0WjkxCKbceexo0Z3Z+d8wDUXVIeQdgrIA6rFl5DAmVsfZ1MtT+faO5zOxrtzADB1/Ybj28tX85wgxOU8e5pN9XqqHos8QIuBGQY2GTEPD5tM8en79P7x8aWxqurx1bbPZytGRYfDrzOZBHPzA5PanCkGZki4d3GQG7DNksFdLIFkpwBQAsmugHVvJ0AB6w5PypW79EOpZ4BnOwAM8Pih1P/R6gGgDBB9kCu3Clo1GcU1kGXQgg9yxWavTgHrXp6IC///t/Iv/l2/AGa0Qa2X0eC0AAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/grid72.b64 b/modules/edit/htdocs/public/grid72.b64
new file mode 100644
index 0000000..34be13e
--- /dev/null
+++ b/modules/edit/htdocs/public/grid72.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAgMAAAAog1vUAAAABGdBTUEAALGPC/xhBQAAAAlQTFRFwuD84/T+////fj9v9QAAACxJREFUOMtjWLUqa9WsVctWrYQxVjAMCqFQdBDCMOrUUaeOOnXUqYPPqZgAABmg/C7pJC7lAAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/iframe.html b/modules/edit/htdocs/public/iframe.html
index a9a9efc..e2b862d 100644
--- a/modules/edit/htdocs/public/iframe.html
+++ b/modules/edit/htdocs/public/iframe.html
@@ -17,13 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
+<html>
 <head>
-<title>frame</title>
-<meta name="viewport" content="width=device-width user-scalable=no initial-scale=1.0"/>
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
 </head>
 <body style="margin:3px; padding: 0px; background-color: #dcdcdc;">
 
diff --git a/modules/edit/htdocs/public/img.b64 b/modules/edit/htdocs/public/img.b64
new file mode 100644
index 0000000..97dae68
--- /dev/null
+++ b/modules/edit/htdocs/public/img.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAIRQTFRFwdt/w9yEw9+MxN2GxN6NxN+Oxd2Mxd6Nxt6Lx96Lx96Nx9+NyN6MyN+MyN+N8u2I8+2I+NBq+NFr+NFt+NFu+NJz+NN0+dR1+dR3+dZw+dh4+9Fy+9Nz++5++++B+++F/NNz/PCH/PCI/PGW/PKc/fKd/vzp/vzq/v7+/v/z/9Jx////nQZfHwAAAIxJREFUOMtj0CYAGKiiQANdUAPdBAZmFMCIYQUzHwrgpKECblYwYEJ2LYoCHi0FMBCEAmF0E3hkxFGABJICXnYWFhY2aVE4EENTwCWgCARKCCCFoUAJFQw9BYycnBz8eBSA04cqPhNAQIX+CiSFhIRE8CiQ10ROMNgUqKNnHGU5FCCrhqZAg7Z5Ey8AALiBh6brcmloAAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/notauth.html b/modules/edit/htdocs/public/notauth.html
deleted file mode 100644
index 44e68da..0000000
--- a/modules/edit/htdocs/public/notauth.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Sorry</title>
-<meta name="viewport" content="width=device-width user-scalable=no initial-scale=1.0"/>
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-<div id="bodydiv" class="bodydiv">
-
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
-
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
-<div class="hd2">Sorry, you're not authorized to view this page.</div>
-</div>
-
-<form name="signout" action="/public/notauth.html" method="GET">
-</form>
-
-<script type="text/javascript">
-ui.initbody();
-
-// Set page title
-$('h1').innerHTML = hometitle(window.location.hostname);
-
-// Load the menu bar
-if (!issubdomain(window.location.hostname))
-    displaymenu();
-
-// Sign out
-if (window.top.location.pathname != '/public/notauth.html') {
-    function submitSignout() {
-        var reset = 'TuscanyOpenAuth=;expires=' + new Date(1970,01,01).toGMTString() + ';domain=.' + domainname(window.location.hostname) + ';path=/;secure=TRUE';
-        document.cookie = reset;
-        document.signout.submit();
-        return true;
-    }
-
-    submitSignout();
-}
-</script>
-
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
-</div>
-
-</div>
-</body>
-</html>
diff --git a/modules/edit/htdocs/public/notfound.html b/modules/edit/htdocs/public/notfound.html
deleted file mode 100644
index c71f4aa..0000000
--- a/modules/edit/htdocs/public/notfound.html
+++ /dev/null
@@ -1,65 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Page not found</title>
-<meta name="viewport" content="width=device-width user-scalable=no initial-scale=1.0"/>
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed">
-<div id="bodydiv" class="bodydiv" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
-
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
-<div class="hd2">Sorry, that page was not found.</div>
-<div>You may have clicked an expired link or mistyped the address.</div>
-</div>
-
-<script type="text/javascript">
-ui.initbody();
-
-// Set page title
-$('h1').innerHTML = hometitle(window.location.hostname);
-
-// Load the menu bar
-if (!issubdomain(window.location.hostname))
-    displaymenu();
-</script>
-
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
-</div>
-
-</div>
-</body>
-</html>
diff --git a/modules/edit/htdocs/public/notyet.html b/modules/edit/htdocs/public/notyet.html
deleted file mode 100644
index 591d8be..0000000
--- a/modules/edit/htdocs/public/notyet.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Page not found</title>
-<meta name="viewport" content="width=device-width user-scalable=no initial-scale=1.0"/>
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-<div id="bodydiv" class="bodydiv">
-
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-<div id="menu"></div>
-
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
-
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
-<div class="hd2">Sorry, that page is still under construction.</div>
-<div>Please check back later.</div>
-</div>
-
-<script type="text/javascript">
-ui.initbody();
-
-// Set page title
-$('h1').innerHTML = hometitle(window.location.hostname);
-
-// Load the menu bar
-if (!issubdomain(window.location.hostname))
-    displaymenu();
-</script>
-
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
-</div>
-
-</div>
-</body>
-</html>
diff --git a/modules/edit/htdocs/public/oops.html b/modules/edit/htdocs/public/oops.html
deleted file mode 100644
index e85c40c..0000000
--- a/modules/edit/htdocs/public/oops.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.    
--->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Oops</title>
-<meta name="viewport" content="width=device-width user-scalable=no initial-scale=1.0"/>
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
-<div id="bodydiv" class="bodydiv">
-
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
-
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
-<div class="hd2">Oops, something went wrong...</div>
-</div>
-
-<script type="text/javascript">
-ui.initbody();
-
-// Set page title
-$('h1').innerHTML = hometitle(window.location.hostname);
-
-// Load the menu bar
-if (!issubdomain(window.location.hostname))
-    displaymenu();
-</script>
-
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
-</div>
-
-</div>
-</body>
-</html>
diff --git a/modules/edit/htdocs/public/touchicon.b64 b/modules/edit/htdocs/public/touchicon.b64
new file mode 100644
index 0000000..2239f6a
--- /dev/null
+++ b/modules/edit/htdocs/public/touchicon.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAADkAAAA5CAIAAAADehTSAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sDGxMkCJXGmL8AAAHwSURBVGje7ZpNbhNBEIXf625sCzA/QUhkg8SGiGxZcpDcgRux5hLkEjkE7BAS9gIyk+muxyZIsSeOG09bsXHX0p4pfVNdP8/loSTsiTnsj1XWyrrMutVSG+ic/ftNqe1mIMtSjsPUu9EQJ6H/UdvNLr59cgwFWaM1p8dnLx6dFGYF6RhIXzLVGIChB3VX8Fg0DWrPqqyHxTq4MUnKKEEBoNvIN4uxiqTkofUXpgZKsqtMx3Djpb45lNWAxxbfXf6wtdH9+vkKBLLGrFLz4M1HTk+K5gAIgBCVcaTI1gOK/acazqqbw2PdYzE7tdyh9AFJTL0zNDIJMInAZpKPzBzmIZuUnjoa9QQkOBHAyWbigYDaTslybg/59f7Q4+003pqwhqcbqjLH9H2OXw0Ksl6XsWB/a39lhf1rz8vOnKoHKmtlrayVtbLuuc6SFK1Z2hEZkBwAv1us4zA9PT7rDX3v9dPiOeBxT/uY0A+qd6Pbl2Sax/kXDN9LlcrXO3Rk9k/QWluVtbIe2O5toBGwFum3bLH/pEso7RarrPNHH/D8JbBIpsjJqx2Lq3Xu2Xv61yvXJzf6/b3nK2Htyu8WB9P/XltF/wfVllgFxet9azGL+bjMD5IUYbPSMktwT8hRSdalkizcufKcs77vUlkr61bsD5lbwtgOKPT2AAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/user.b64 b/modules/edit/htdocs/public/user.b64
new file mode 100644
index 0000000..7ed235a
--- /dev/null
+++ b/modules/edit/htdocs/public/user.b64
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAABGdBTUEAALGPC/xhBQAAAAxQTFRFyN+N+dR1/PCI////6HjE5gAAADJJREFUKM9j+I8EPjBQifeBAQSY6coLBYN6inhaq0Bg6SDn/f//akB466ExTS6P2ukMAKumzarJO/66AAAAAElFTkSuQmCC
\ No newline at end of file
diff --git a/modules/edit/htdocs/public/user.png b/modules/edit/htdocs/public/user.png
new file mode 100644
index 0000000..1f73274
--- /dev/null
+++ b/modules/edit/htdocs/public/user.png
Binary files differ
diff --git a/modules/edit/htdocs/stats/index.html b/modules/edit/htdocs/stats/index.html
index b7dd464..9fa463e 100644
--- a/modules/edit/htdocs/stats/index.html
+++ b/modules/edit/htdocs/stats/index.html
@@ -17,26 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Stats</title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr>
 <td><h2><span id="h1"></span><span id="appNameHeader"></span></h2></td>
@@ -57,7 +39,7 @@
 <form id="appForm">
 <table style="width: 100%;">
 <tr><tr><td><b>App Icon:</b></td></tr>
-<tr><td><img src="/public/app.png" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
+<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
 <tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
 <tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
@@ -70,20 +52,17 @@
 </form>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Get the app name
-var appname = ui.fragmentParams()['app'];
-if (isNil(appname))
-    window.open('/', '_self');
+var appname = ui.fragmentParams(location)['app'];
 
 /**
  * Return the link to an app.
  */
 function applink(appname) {
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/';
@@ -91,14 +70,14 @@
 }
 
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - Stats - ' + appname;
+document.title = ui.windowtitle(location.hostname) + ' - Stats - ' + appname;
 $('appNameHeader').innerHTML = '<a href=\"' + applink(appname) + '\" target=\"' + '_blank' + '\">' + appname + '</a>';
 var tclone = isNil(config.clone)? 'Clone' : config.clone;
 $('cloneApp').value = tclone;
 $('cloneApp').title = tclone + ' this app';
 
-// Load the menu bar
-displaymenu();
+// Set images
+$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
 
 // Init service references
 var editWidget = sca.component("EditWidget");
@@ -176,18 +155,12 @@
  * Handle Clone button event.
  */
 $('cloneApp').onclick = function() {
-    return ui.navigate('/clone/#app=' + appname, '_self');
+    return ui.navigate('/#view=clone&app=' + appname, '_view');
 }
 
 // Get the current app
 getapp(appname);
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/htdocs/store/index.html b/modules/edit/htdocs/store/index.html
index e63dbdb..1a04b40 100644
--- a/modules/edit/htdocs/store/index.html
+++ b/modules/edit/htdocs/store/index.html
@@ -17,26 +17,8 @@
  * specific language governing permissions and limitations
  * under the License.    
 -->
-<html manifest="/cache-manifest.cmf">
-<head>
-<title>Store</title>
-<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> 
-<meta name="apple-mobile-web-app-capable" content="yes"/>
-<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
-<link rel="apple-touch-icon" href="/public/touchicon.png"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
-<script type="text/javascript" src="/menu.js"></script>
-</head>
-<body class="delayed" onload="ui.onload();" onbeforeunload="ui.onbeforeunload();">
 <div id="bodydiv" class="bodydiv">
 
-<div id="headdiv" class="hsection">
-<script type="text/javascript" src="/headconfig.js"></script>
-</div>
-
-<div id="menu"></div>
-
 <table style="width: 100%;">
 <tr><td><h2><span id="h1"></span></h2></td></tr>
 </table>
@@ -46,17 +28,13 @@
 <div id="apps"></div>
 
 <script type="text/javascript">
-ui.initbody();
 
 // Set page titles
-document.title = windowtitle(window.location.hostname) + ' - Store';
-$('h1').innerHTML = hometitle(window.location.hostname);
-
-// Display the menu bar
-displaymenu();
+document.title = ui.windowtitle(location.hostname) + ' - Store';
+$('h1').innerHTML = ui.hometitle(location.hostname);
 
 // Get the store category
-var category = ui.fragmentParams()['category'];
+var category = ui.fragmentParams(location)['category'];
 if (isNil(category))
     category = 'myapps';
 
@@ -64,18 +42,18 @@
  * Build store menu bar
  */
 function catmenu() {
-    function catmenuitem(name, cat) {
+    function catmenuitem(name, cat, idx) {
         var c = cat == category? 'smenu' : 'amenu';
         return '<th class="thl thr" style="width: 10px; padding-top: 4px; padding-bottom: 4px; padding-right: 6px;">'
-                + ui.ahref('/store/#category=' + cat, '_reload', '<span class="' + c + '">' + name + '</span>') + '</th>';
+                + ui.ahref('/#view=store&category=' + cat + '&idx=' + idx, '_view', '<span class="' + c + '">' + name + '</span>') + '</th>';
     }
 
     var m = '<table style="width: 100%; margin-bottom: 2px;"><tr>';
-    m += catmenuitem('My Apps', 'myapps');
-    m += catmenuitem('New', 'new');
-    m += catmenuitem('Top', 'top');
-    m += catmenuitem('Featured', 'featured');
-    m += catmenuitem('All', 'all');
+    m += catmenuitem('My Apps', 'myapps', '1');
+    m += catmenuitem('New', 'new', '2');
+    m += catmenuitem('Top', 'top', '3');
+    m += catmenuitem('Featured', 'featured', '4');
+    m += catmenuitem('All', 'all', '5');
     if (category == 'myapps') {
         m += '<th class="thl thr" style="width: 100%; padding-top: 0px; padding-bottom: 0px; padding-right: 0px; text-align: right;">';
         m += '<input type="button" class="graybutton" id="createApp" title="Create a new app" style="font-weight: bold; margin-top: 0px; margin-bottom: 0px; height: 24px;" Value="New App"/>';
@@ -100,9 +78,9 @@
  * Return the link to an app.
  */
 function applink(appname) {
-    var protocol = window.location.protocol;
-    var host = window.location.hostname;
-    var port = ':' + window.location.port;
+    var protocol = location.protocol;
+    var host = location.hostname;
+    var port = ':' + location.port;
     if (port == ':80' || port == ':443' || port == ':')
         port = '';
     var link = protocol + '//' + appname + '.' + host + port + '/';
@@ -113,14 +91,14 @@
  * Edit an app.
  */
 function editApp(appname) {
-    return ui.navigate('/page/#app=' + appname, '_self');
+    return ui.navigate('/#view=page&app=' + appname, '_view');
 }
 
 /**
  * View an app.
  */
 function viewApp(appname) {
-    return ui.navigate('/stats/#app=' + appname, '_self');
+    return ui.navigate('/#view=stats&app=' + appname, '_view');
 }
 
 /**
@@ -128,7 +106,7 @@
  */
 if (category == 'myapps') {
     $('createApp').onclick = function() {
-        return ui.navigate('/create/', '_self');
+        return ui.navigate('/#view=create', '_view');
     }
 }
 
@@ -136,6 +114,7 @@
  * Get and display list of apps.
  */
 function getapps(category) {
+    //log('category', category);
     function display(doc) {
 
         // Stop now if we didn't get the apps
@@ -147,6 +126,8 @@
         var aentries = assoc("'entry", cdr(feed));
         var entries = isNil(aentries)? mklist() : isList(car(cadr(aentries)))? cadr(aentries) : mklist(cdr(aentries));
 
+        var appimg = ui.b64img(appcache.get('/public/app.b64'));
+
         function displayentries(entries) {
             if (isNil(entries))
                 return apps;
@@ -159,7 +140,7 @@
             apps += '<div class="box" style="width: 150px; display: inline-block; border: 1px; border-style: solid; border-color: #dcdcdc; border-collapse: collapse; margin: 2px; padding: 2px; vertical-align: top;">'
             apps += '<table><tr>';
             apps += '<td>';
-            apps += '<div>' + ui.ahref('/stats/#app=' + name, '_self', '<img src="/public/app.png" width="50" height="50" style="height: 50px; width: 50px; vertical-align: top; margin: 0px; padding: 0px;"></img>') + '</div>';
+            apps += '<div>' + ui.ahref('/#view=stats&app=' + name, '_view', '<img src="' + appimg + '" width="50" height="50" style="height: 50px; width: 50px; vertical-align: top; margin: 0px; padding: 0px;"></img>') + '</div>';
             apps += '</td>';
             apps += '<td class="tdw">';
             apps += '<div style="font-weight: bold">' + ui.ahref(applink(name), '_blank', name) + '</div>';
@@ -186,13 +167,7 @@
 
 // Get and display the list of apps
 getapps(category);
+
 </script>
 
-<div id="footdiv" class="fsection">
-<script type="text/javascript" src="/footconfig.js"></script>
 </div>
-
-</div>
-</body>
-</html>
-
diff --git a/modules/edit/ssl-start b/modules/edit/ssl-start
index 576fa39..050de3e 100755
--- a/modules/edit/ssl-start
+++ b/modules/edit/ssl-start
@@ -51,18 +51,18 @@
 # Configure error pages
 cat >>tmp/conf/svhost-ssl.conf <<EOF
 # Error pages
-ErrorDocument 404 /public/notfound.html
-ErrorDocument 401 /public/notauth.html
-ErrorDocument 500 /public/oops.html
+ErrorDocument 404 /notfound/
+ErrorDocument 401 /notauth/
+ErrorDocument 500 /oops/
 
 EOF
 
 # Configure app home pages
 cat >>tmp/conf/dvhost-ssl.conf <<EOF
 # App error pages
-ErrorDocument 404 /public/notfound.html
-ErrorDocument 401 /public/notauth.html
-ErrorDocument 500 /public/oops.html
+ErrorDocument 404 /notfound/
+ErrorDocument 401 /notauth/
+ErrorDocument 500 /oops/
 
 # Redirect www to main home page
 RewriteEngine on
@@ -101,7 +101,8 @@
 # Configure main aliases
 cat >>tmp/conf/httpd.conf <<EOF
 
-Alias /home.png $here/htdocs/home.png
+Alias /home/home.png $here/htdocs/home/home.png
+Alias /home/home.b64 $here/htdocs/home/home.b64
 
 EOF
 
@@ -120,7 +121,10 @@
 Alias /index.html $here/htdocs/app/index.html
 Alias /login $here/htdocs/login
 Alias /logout $here/htdocs/logout
-Alias /menu.js $here/htdocs/menu.js
+Alias /notauth $here/htdocs/notauth
+Alias /notfound $here/htdocs/notfound
+Alias /notyet $here/htdocs/notyet
+Alias /oops $here/htdocs/oops
 Alias /public $here/htdocs/public
 Alias /robots.txt $here/htdocs/robots.txt
 
diff --git a/modules/edit/start b/modules/edit/start
index 6125ba0..b9fe595 100755
--- a/modules/edit/start
+++ b/modules/edit/start
@@ -35,18 +35,18 @@
 # Configure error pages
 cat >>tmp/conf/svhost.conf <<EOF
 # Error pages
-ErrorDocument 404 /public/notfound.html
-ErrorDocument 401 /public/notauth.html
-ErrorDocument 500 /public/oops.html
+ErrorDocument 404 /notfound/
+ErrorDocument 401 /notauth/
+ErrorDocument 500 /oops/
 
 EOF
 
 # Configure app home pages
 cat >>tmp/conf/dvhost.conf <<EOF
 # App error pages
-ErrorDocument 404 /public/notfound.html
-ErrorDocument 401 /public/notauth.html
-ErrorDocument 500 /public/oops.html
+ErrorDocument 404 /notfound/
+ErrorDocument 401 /notauth/
+ErrorDocument 500 /oops/
 
 # Redirect www to main home page
 RewriteEngine on
@@ -70,6 +70,14 @@
 
 EOF
 
+# Configure main aliases
+cat >>tmp/conf/httpd.conf <<EOF
+
+Alias /home/home.png $here/htdocs/home/home.png
+Alias /home/home.b64 $here/htdocs/home/home.b64
+
+EOF
+
 # Create app links and sub-directories if needed
 ./mkapplinks
 
@@ -85,7 +93,10 @@
 Alias /index.html $here/htdocs/app/index.html
 Alias /login $here/htdocs/login
 Alias /logout $here/htdocs/logout
-Alias /menu.js $here/htdocs/menu.js
+Alias /notauth $here/htdocs/notauth
+Alias /notfound $here/htdocs/notfound
+Alias /notyet $here/htdocs/notyet
+Alias /oops $here/htdocs/oops
 Alias /public $here/htdocs/public
 Alias /robots.txt $here/htdocs/robots.txt
 
diff --git a/modules/http/conf/mime.types b/modules/http/conf/mime.types
index c0dba0e..430aa95 100644
--- a/modules/http/conf/mime.types
+++ b/modules/http/conf/mime.types
@@ -546,7 +546,7 @@
 text/enriched
 text/html			html htm
 text/parityfec
-text/plain			asc txt
+text/plain			asc txt b64
 text/prs.lines.tag
 text/rfc822-headers
 text/richtext			rtx
diff --git a/modules/http/httpd-conf b/modules/http/httpd-conf
index 44f3cc8..d672b2d 100755
--- a/modules/http/httpd-conf
+++ b/modules/http/httpd-conf
@@ -284,6 +284,11 @@
 # Virtual host configuration
 UseCanonicalName Off
 
+# Enable HTTP reverse proxy
+ProxyRequests Off
+ProxyPreserveHost Off
+ProxyStatus On
+
 EOF
 
 cat >$root/conf/svhost.conf <<EOF
diff --git a/modules/http/httpd-ssl-conf b/modules/http/httpd-ssl-conf
index 77a4898..9de67ff 100755
--- a/modules/http/httpd-ssl-conf
+++ b/modules/http/httpd-ssl-conf
@@ -129,6 +129,18 @@
 LogFormat "[%{%a %b %d %H:%M:%S %Y}t] [sslaccess] %h %l %u %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{SSL_CLIENT_I_DN}x\" \"%{SSL_CLIENT_S_DN}x\" \"%{cookie}n\" %A %V %D %I %O" sslcombined
 CustomLog $root/logs/ssl_access_log sslcombined
 
+# Enable HTTPS reverse proxy
+ProxyRequests Off
+ProxyPreserveHost Off
+ProxyStatus On
+SSLProxyEngine on
+SSLProxyCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
+
+# Verify server certificates
+SSLProxyVerify require
+SSLProxyVerifyDepth 1
+SSLProxyCheckPeerCN Off
+
 EOF
 
 proxycert="server"
@@ -144,8 +156,12 @@
 # Declare SSL certificates used in this virtual host
 SSLCACertificateFile "$root/cert/ca.crt"
 SSLCertificateChainFile "$root/cert/ca.crt"
-SSLCertificateFile "$root/cert/server.crt"
-SSLCertificateKeyFile "$root/cert/server.key"
+SSLCertificateFile "$root/cert/vhost.crt"
+SSLCertificateKeyFile "$root/cert/vhost.key"
+
+# Declare proxy SSL client certificates
+SSLProxyCACertificateFile "$root/cert/ca.crt"
+SSLProxyMachineCertificateFile "$root/cert/$proxycert.pem"
 
 EOF
 
diff --git a/modules/http/mod-openauth.cpp b/modules/http/mod-openauth.cpp
index 9fd6579..c2791b5 100644
--- a/modules/http/mod-openauth.cpp
+++ b/modules/http/mod-openauth.cpp
@@ -199,6 +199,7 @@
     if (!dc.enabled)
         return DECLINED;
     const char* atype = ap_auth_type(r);
+    debug(atype, "modopenauth::checkAuthn::auth_type");
     if (atype == NULL || strcasecmp(atype, "Open"))
         return DECLINED;
 
diff --git a/modules/http/proxy-conf b/modules/http/proxy-conf
index 8bca7cd..9094996 100755
--- a/modules/http/proxy-conf
+++ b/modules/http/proxy-conf
@@ -24,11 +24,6 @@
 
 cat >>$root/conf/vhost.conf <<EOF
 # Generated by: proxy-conf $*
-# Enable HTTP reverse proxy
-ProxyRequests Off
-ProxyPreserveHost On
-ProxyStatus On
-
 # Enable load balancing
 ProxyPass / balancer://cluster/
 
diff --git a/modules/http/proxy-ssl-conf b/modules/http/proxy-ssl-conf
index af8fce8..6897a0f 100755
--- a/modules/http/proxy-ssl-conf
+++ b/modules/http/proxy-ssl-conf
@@ -24,17 +24,6 @@
 
 cat >>$root/conf/vhost-ssl.conf <<EOF
 # Generated by: proxy-ssl-conf $*
-# Enable HTTPS reverse proxy
-ProxyRequests Off
-ProxyPreserveHost On
-ProxyStatus On
-SSLProxyEngine on
-SSLProxyCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
-
-# Verify server certificates
-SSLProxyVerify require
-SSLProxyVerifyDepth 1
-
 # Enable load balancing
 ProxyPass /balancer-manager !
 ProxyPass / balancer://sslcluster/
@@ -63,7 +52,6 @@
 
 cat >>$root/conf/dvhost-ssl.conf <<EOF
 # Generated by: proxy-ssl-conf $*
-
 # Declare proxy SSL client certificates
 SSLProxyCACertificateFile "$root/cert/ca.crt"
 SSLProxyMachineCertificateFile "$root/cert/proxy.pem"
diff --git a/modules/js/htdocs/all-min.js b/modules/js/htdocs/all-min.js
index 921abf1..b214be0 100644
--- a/modules/js/htdocs/all-min.js
+++ b/modules/js/htdocs/all-min.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
 function cons(car,cdr){var a=new Array();a.push(car);return a.concat(cdr);}
 function car(l){return l[0];}
 function first(l){return l[0];}
@@ -66,10 +84,6 @@
 a.push(p);return a;}
 function domainname(host){var h=reverse(host.split('.'));return reverse(mklist(car(h),cadr(h))).join('.');}
 function issubdomain(host){return host.split('.').length>2;}
-function hometitle(host){if(!isNil(config.hometitle))
-return config.hometitle;var h=reverse(host.split('.'));var d=isNil(cdr(h))?car(h):cadr(h);return d.substr(0,1).toUpperCase()+d.substr(1);}
-function windowtitle(host){if(!isNil(config.windowtitle))
-return config.windowtitle;var h=reverse(host.split('.'));var d=isNil(cdr(h))?car(h):cadr(h);return d.substr(0,1).toUpperCase()+d.substr(1);}
 function format(){var i=0;var s='';for(a in arguments){s=i==0?arguments[a]:s.replace('{'+a+'}',arguments[a]);i++;}
 return s;}
 function setcar(l,v){l[0]=v;return l;}
@@ -231,56 +245,34 @@
 var turi=targetURI();if(isNil(turi))
 return turi;return car(tokens(turi));};scdl.properties=function(l){return namedElementChildren("'property",l);};scdl.propertyValue=function(l){if(!elementHasValue(l))
 return'';return elementValue(l);};scdl.nameToElementAssoc=function(l){if(isNil(l))
-return l;return cons(mklist(scdl.name(car(l)),car(l)),scdl.nameToElementAssoc(cdr(l)));};var ui={};ui.ahref=function(loc,target,html){if(target=='_blank')
-return'<a href="'+loc+'" target="_blank">'+html+'</a>';return'<a href="javascript:void(0)" onclick="ui.navigate(\''+loc+'\', \''+target+'\');">'+html+'</a>';};ui.menu=function(name,href,target){function Menu(n,h,t){this.name=n;this.href=h;this.target=isNil(t)?'_parent':t;this.content=function(){function complete(uri){var h=uri.indexOf('#');if(h!=-1)
-return complete(uri.substr(0,h));var q=uri.indexOf('?');if(q!=-1)
-return complete(uri.substr(0,q));if(uri.match('.*\.html$'))
-return uri;if(uri.match('.*/$'))
-return uri+'index.html';return uri+'/index.html';}
-if(complete(this.href)!=complete(window.top.location.pathname))
-return ui.ahref(this.href,this.target,'<span class="tbaramenu">'+this.name+'</span>');return ui.ahref(this.href,this.target,'<span class="tbarsmenu">'+this.name+'</span>');};}
-return new Menu(name,href,target);};ui.menubar=function(left,right){var bar='<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr>'+'<td class="dtbar"><table border="0" cellspacing="0" cellpadding="0"><tr>';for(i in left)
+return l;return cons(mklist(scdl.name(car(l)),car(l)),scdl.nameToElementAssoc(cdr(l)));};var ui={};ui.elementByID=function(node,id){if(node.skipNode==true)
+return null;for(var i in node.childNodes){var child=node.childNodes[i];if(child.id==id)
+return child;var gchild=ui.elementByID(child,id);if(gchild!=null)
+return gchild;}
+return null;};function $(id){if(id==document)
+return document;return ui.elementByID($(document),id);};ui.query=function(url){var u=''+url;var q=u.indexOf('?');return q>=0?u.substring(q+1):'';};ui.fragment=function(url){var u=''+url;var h=u.indexOf('#');return h>=0?u.substring(h+1):'';};ui.pathandparams=function(url){var u=''+url;var ds=u.indexOf('//');var u2=ds>0?u.substring(ds+2):u;var s=u2.indexOf('/');return s>0?u2.substring(s):'';};ui.queryParams=function(url){var qp=new Array();var qs=ui.query(url).split('&');for(var i=0;i<qs.length;i++){var e=qs[i].indexOf('=');if(e>0)
+qp[qs[i].substring(0,e)]=unescape(qs[i].substring(e+1));}
+return qp;};ui.fragmentParams=function(url){var qp=new Array();var qs=ui.fragment(url).split('&');for(var i=0;i<qs.length;i++){var e=qs[i].indexOf('=');if(e>0)
+qp[qs[i].substring(0,e)]=unescape(qs[i].substring(e+1));}
+return qp;};ui.b64img=function(b64){return'data:image/png;base64,'+b64;};ui.declareCSS=function(s){var e=document.createElement('style');e.type='text/css';e.textContent=s;return e;};ui.declareScript=function(s){var e=document.createElement('script');e.type='text/javascript';e.text=s;return e;};ui.innerScripts=function(e){return map(function(s){return s.text;},nodeList(e.getElementsByTagName('script')));};ui.evalScript=function(s){return eval('(function() {\n'+s+'\n})();');};ui.includeScript=function(s){log('include',s);return eval(s);};ui.mobiledetected=false;ui.mobile=false;ui.isMobile=function(){if(ui.mobiledetected)
+return ui.mobile;var ua=navigator.userAgent;if(ua.match(/iPhone/i)||ua.match(/iPad/i)||ua.match(/Android/i)||ua.match(/Blackberry/i)||ua.match(/WebOs/i))
+ui.mobile=true;ui.mobiledetected=true;return ui.mobile;};ui.hometitle=function(host){if(!isNil(window.top.config.hometitle))
+return window.top.config.hometitle;var h=reverse(host.split('.'));var d=isNil(cdr(h))?car(h):cadr(h);return d.substr(0,1).toUpperCase()+d.substr(1);};ui.windowtitle=function(host){if(!isNil(window.top.config.windowtitle))
+return window.top.config.windowtitle;var h=reverse(host.split('.'));var d=isNil(cdr(h))?car(h):cadr(h);return d.substr(0,1).toUpperCase()+d.substr(1);};ui.numpos=function(p){return p==''?0:Number(p.substr(0,p.length-2));};ui.pixpos=function(p){return p+'px';};ui.onorientationchange=function(){window.location.reload();return true;}
+ui.navigate=function(url,win){log('navigate',url,win);if(win=='_blank')
+return window.top.open(url,win);if(win=='_self')
+return window.top.open(url,win);if(win=='_reload'){window.top.location=url;return window.top.location.reload();}
+if(win=='_view'){if(!window.top.onnavigate)
+return window.top.open(url,'_self');return window.top.onnavigate(url);}
+return window.top.open(url,win);}
+ui.ahref=function(loc,target,html){if(target=='_blank')
+return'<a href="'+loc+'" target="_blank">'+html+'</a>';return'<a href="javascript:void(0)" onclick="ui.navigate(\''+loc+'\', \''+target+'\');">'+html+'</a>';};ui.menu=function(name,href,target,hilight){function Menu(){this.content=function(){if(hilight)
+return ui.ahref(href,target,'<span class="tbarsmenu">'+name+'</span>');return ui.ahref(href,target,'<span class="tbaramenu">'+name+'</span>');};}
+return new Menu();};ui.menubar=function(left,right){var bar='<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr>'+'<td class="dtbar"><table border="0" cellspacing="0" cellpadding="0"><tr>';for(i in left)
 bar=bar+'<td class="ltbar">'+left[i].content()+'</td>'
 bar=bar+'</tr></table></td>'+'<td class="dtbar"><table border="0" cellpadding="0" cellspacing="0" align="right"><tr>';for(i in right)
 bar=bar+'<td class="'+(i==0?'dtbar':'rtbar')+'">'+right[i].content()+'</td>'
-bar=bar+'</tr></table></td></tr></table>';return bar;};ui.selectSuggestion=function(node,value){for(;;){node=node.parentNode;if(node.tagName.toLowerCase()=='div')
-break;}
-node.selectSuggestion(value);};ui.hilightSuggestion=function(node,over){if(over)
-node.className='suggestHilighted';node.className='suggestItem';};ui.suggest=function(input,suggestFunction){input.suggest=suggestFunction;input.selectSuggestion=function(value){this.hideSuggestDiv();this.value=value;}
-input.hideSuggestDiv=function(){if(this.suggestDiv!=null){this.suggestDiv.style.visibility='hidden';}}
-input.showSuggestDiv=function(){if(this.suggestDiv==null){this.suggestDiv=document.createElement('div');this.suggestDiv.input=this;this.suggestDiv.className='suggest';input.parentNode.insertBefore(this.suggestDiv,input);this.suggestDiv.style.visibility='hidden';this.suggestDiv.style.zIndex='99';this.suggestDiv.selectSuggestion=function(value){this.input.selectSuggestion(value);}}
-var values=this.suggest();var items='';for(var i=0;i<values.length;i++){if(values[i].indexOf(this.value)==-1)
-continue;if(items.length==0)
-items+='<table class="suggestTable">';items+='<tr><td class="suggestItem" '+'onmouseover="ui.hilightSuggestion(this, true)" onmouseout="ui.hilightSuggestion(this, false)" '+'onmousedown="ui.selectSuggestion(this, \''+values[i]+'\')">'+values[i]+'</td></tr>';}
-if(items.length!=0)
-items+='</table>';this.suggestDiv.innerHTML=items;if(items.length!=0){var node=input;var left=0;var top=0;for(;;){left+=node.offsetLeft;top+=node.offsetTop;node=node.offsetParent;if(node.tagName.toLowerCase()=='body')
-break;}
-this.suggestDiv.style.left=left;this.suggestDiv.style.top=top+input.offsetHeight;this.suggestDiv.style.visibility='visible';}else
-this.suggestDiv.style.visibility='hidden';}
-input.onkeydown=function(event){this.showSuggestDiv();};input.onkeyup=function(event){this.showSuggestDiv();};input.onmousedown=function(event){this.showSuggestDiv();};input.onblur=function(event){setTimeout(function(){input.hideSuggestDiv();},50);};};ui.elementByID=function(node,id){for(var i in node.childNodes){var child=node.childNodes[i];if(child.id==id)
-return child;var gchild=ui.elementByID(child,id);if(gchild!=null)
-return gchild;}
-return null;};function $(id){if(id==document){if(!isNil(document.widget))
-return document.widget;return document;}
-return ui.elementByID($(document),id);};ui.queryParams=function(){var qp=new Array();var qs=window.location.search.substring(1).split('&');for(var i=0;i<qs.length;i++){var e=qs[i].indexOf('=');if(e>0)
-qp[qs[i].substring(0,e)]=unescape(qs[i].substring(e+1));}
-return qp;};ui.fragmentParams=function(){var qp=new Array();var qs=window.location.hash.substring(1).split('&');for(var i=0;i<qs.length;i++){var e=qs[i].indexOf('=');if(e>0)
-qp[qs[i].substring(0,e)]=unescape(qs[i].substring(e+1));}
-return qp;};ui.mobiledetected=false;ui.mobile=false;ui.isMobile=function(){if(ui.mobiledetected)
-return ui.mobile;var ua=navigator.userAgent;if(ua.match(/iPhone/i)||ua.match(/iPad/i)||ua.match(/Android/i)||ua.match(/Blackberry/i)||ua.match(/WebOs/i))
-ui.mobile=true;ui.mobiledetected=true;return ui.mobile;};ui.pagetransitions=false;ui.initbody=function(){if(ui.isMobile()){if(ui.pagetransitions){var bdiv=$('bodydiv');if(!isNil(bdiv)){bdiv.className='bodydivloading';}}
-document.body.onorientationchange=ui.onorientationchange;}
-return true;}
-ui.onorientationchange=function(){window.open(window.location,'_self');return true;}
-ui.onload=function(){var path=document.location.pathname;if(path.indexOf('/login/')!=0&&path.indexOf('/logout/')!=0)
-localStorage.setItem('ui.lastvisited',''+document.location);document.body.style.visibility='visible';if(ui.pagetransitions&&ui.isMobile()){setTimeout(function(){var bdiv=$('bodydiv');if(!isNil(bdiv)){function transitionend(e){bdiv.removeEventListener('webkitTransitionEnd',transitionend,false);bdiv.removeEventListener('transitionend',transitionend,false);bdiv.className='bodydiv';};bdiv.addEventListener('webkitTransitionEnd',transitionend,false);bdiv.addEventListener('transitionend',transitionend,false);bdiv.className='bodydivloaded';}},0);}
-return true;};ui.navigate=function(url,win){function opendoc(url,win){if(win=='_reload'){window.location=url;return window.location.reload();}
-return window.open(url,win);}
-if(ui.pagetransitions&&ui.isMobile()&&win!='_blank'){var bdiv=$('bodydiv');if(!isNil(bdiv)){function transitionend(e){bdiv.removeEventListener('webkitTransitionEnd',transitionend,false);bdiv.removeEventListener('transitionend',transitionend,false);return opendoc(url,win);};bdiv.addEventListener('webkitTransitionEnd',transitionend,false);bdiv.addEventListener('transitionend',transitionend,false);bdiv.className='bodydivunloaded';return true;}}
-return opendoc(url,win);}
-ui.onbeforeunload=function(){if(ui.pagetransitions&&ui.isMobile()){var bdiv=$('bodydiv');if(!isNil(bdiv))
-bdiv.className='bodydivunloaded';}};ui.lastvisited=function(){return localStorage.getItem('ui.lastvisited');}
-ui.numpos=function(p){return p==''?0:Number(p.substr(0,p.length-2));};ui.pixpos=function(p){return p+'px';};ui.datatable=function(l){function indent(i){if(i==0)
+bar=bar+'</tr></table></td></tr></table>';return bar;};ui.datatable=function(l){function indent(i){if(i==0)
 return'';return'&nbsp;&nbsp;'+indent(i-1);}
 function rows(l,i){if(isNil(l))
 return'';var e=car(l);if(!isList(e))
@@ -296,7 +288,21 @@
 return rows(expandElementValues(elementName(e),v),i)+rows(cdr(l),i);}
 return rows(elementChildren(e),i+1)+rows(cdr(l),i);}
 return'<table class="datatable '+(window.name=='dataFrame'?' databg':'')+'" style="width: 100%;">'+rows(l,0)+'</table>';}
-var JSONClient={};JSONClient.escapeJSONChar=function(c){if(c=="\""||c=="\\")return"\\"+c;if(c=="\b")return"\\b";if(c=="\f")return"\\f";if(c=="\n")return"\\n";if(c=="\r")return"\\r";if(c=="\t")return"\\t";var hex=c.charCodeAt(0).toString(16);if(hex.length==1)return"\\u000"+hex;if(hex.length==2)return"\\u00"+hex;if(hex.length==3)return"\\u0"+hex;return"\\u"+hex;};JSONClient.escapeJSONString=function(s){var parts=s.split("");for(var i=0;i<parts.length;i++){var c=parts[i];if(c=='"'||c=='\\'||c.charCodeAt(0)<32||c.charCodeAt(0)>=128)
+ui.selectSuggestion=function(node,value){for(;;){node=node.parentNode;if(node.tagName.toLowerCase()=='div')
+break;}
+node.selectSuggestion(value);};ui.hilightSuggestion=function(node,over){if(over)
+node.className='suggestHilighted';node.className='suggestItem';};ui.suggest=function(input,suggestFunction){input.suggest=suggestFunction;input.selectSuggestion=function(value){this.hideSuggestDiv();this.value=value;}
+input.hideSuggestDiv=function(){if(this.suggestDiv!=null){this.suggestDiv.style.visibility='hidden';}}
+input.showSuggestDiv=function(){if(this.suggestDiv==null){this.suggestDiv=document.createElement('div');this.suggestDiv.input=this;this.suggestDiv.className='suggest';input.parentNode.insertBefore(this.suggestDiv,input);this.suggestDiv.style.visibility='hidden';this.suggestDiv.style.zIndex='99';this.suggestDiv.selectSuggestion=function(value){this.input.selectSuggestion(value);}}
+var values=this.suggest();var items='';for(var i=0;i<values.length;i++){if(values[i].indexOf(this.value)==-1)
+continue;if(items.length==0)
+items+='<table class="suggestTable">';items+='<tr><td class="suggestItem" '+'onmouseover="ui.hilightSuggestion(this, true)" onmouseout="ui.hilightSuggestion(this, false)" '+'onmousedown="ui.selectSuggestion(this, \''+values[i]+'\')">'+values[i]+'</td></tr>';}
+if(items.length!=0)
+items+='</table>';this.suggestDiv.innerHTML=items;if(items.length!=0){var node=input;var left=0;var top=0;for(;;){left+=node.offsetLeft;top+=node.offsetTop;node=node.offsetParent;if(node.tagName.toLowerCase()=='body')
+break;}
+this.suggestDiv.style.left=left;this.suggestDiv.style.top=top+input.offsetHeight;this.suggestDiv.style.visibility='visible';}else
+this.suggestDiv.style.visibility='hidden';}
+input.onkeydown=function(event){this.showSuggestDiv();};input.onkeyup=function(event){this.showSuggestDiv();};input.onmousedown=function(event){this.showSuggestDiv();};input.onblur=function(event){setTimeout(function(){input.hideSuggestDiv();},50);};};var JSONClient={};JSONClient.escapeJSONChar=function(c){if(c=="\""||c=="\\")return"\\"+c;if(c=="\b")return"\\b";if(c=="\f")return"\\f";if(c=="\n")return"\\n";if(c=="\r")return"\\r";if(c=="\t")return"\\t";var hex=c.charCodeAt(0).toString(16);if(hex.length==1)return"\\u000"+hex;if(hex.length==2)return"\\u00"+hex;if(hex.length==3)return"\\u0"+hex;return"\\u"+hex;};JSONClient.escapeJSONString=function(s){var parts=s.split("");for(var i=0;i<parts.length;i++){var c=parts[i];if(c=='"'||c=='\\'||c.charCodeAt(0)<32||c.charCodeAt(0)>=128)
 parts[i]=JSONClient.escapeJSONChar(parts[i]);}
 return"\""+parts.join("")+"\"";};JSONClient.toJSON=function(o){if(o==null)
 return"null";if(o.constructor==String)
@@ -308,7 +314,7 @@
 var v=[];for(attr in o){if(o[attr]==null)
 v.push("\""+attr+"\": null");else if(typeof o[attr]=="function");else
 v.push(JSONClient.escapeJSONString(attr)+": "+JSONClient.toJSON(o[attr]));}
-return"{"+v.join(", ")+"}";};function HTTPBindingClient(name,uri){this.name=name;this.uri=uri;this.apply=this.createApplyMethod();}
+return"{"+v.join(", ")+"}";};function HTTPBindingClient(name,uri,domain){this.name=name;this.domain=domain;this.uri=uri;this.apply=this.createApplyMethod();}
 HTTPBindingClient.jsonrpcID=1;HTTPBindingClient.prototype.createApplyMethod=function(){var fn=function(){var methodName=arguments[0];var args=[];for(var i=1;i<arguments.length;i++)
 args.push(arguments[i]);var cb=null;if(typeof args[args.length-1]=="function")
 cb=args.pop();var req=HTTPBindingClient.makeJSONRequest(methodName,args,cb);return fn.client.jsonApply(req);};fn.client=this;return fn;};HTTPBindingClient.makeJSONRequest=function(methodName,args,cb){var req={};req.id=HTTPBindingClient.jsonrpcID++;if(cb)
@@ -320,14 +326,14 @@
 throw new HTTPBindingClient.Exception(obj.error.code,obj.error.msg);var res=obj.result;return res;};HTTPBindingClient.prototype.jsonApply=function(req){var http=HTTPBindingClient.getHTTPRequest();var hascb=req.cb?true:false;http.open("POST",this.uri,hascb);http.setRequestHeader("Content-Type","application/json-rpc");if(hascb){http.onreadystatechange=function(){if(http.readyState==4){if(http.status==200){var res=null;try{res=HTTPBindingClient.jsonResult(http);try{req.cb(res);}catch(cbe){}}catch(e){try{req.cb(null,e);}catch(cbe){}}}else
 try{req.cb(null,HTTPBindingClient.Exception(http.status,http.statusText));}catch(cbe){}}};http.send(req.data);return req.id;}
 http.send(req.data);if(http.status==200)
-return HTTPBindingClient.jsonResult(http);throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.prototype.get=function(id,cb){var u=this.uri+'/'+id;var hascb=cb?true:false;var item=localStorage.getItem(u);if(item!=null&&item!=''){if(!hascb)
+return HTTPBindingClient.jsonResult(http);throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.prototype.get=function(id,cb){var u=id?(this.uri?this.uri+'/'+id:id):this.uri;var hascb=cb?true:false;var item=localStorage.getItem(u);if(item!=null&&item!=''){if(!hascb)
 return item;try{cb(item);}catch(cbe){}}
 var http=HTTPBindingClient.getHTTPRequest();http.open("GET",u,hascb);if(hascb){http.onreadystatechange=function(){if(http.readyState==4){if(http.status==200){if(http.getResponseHeader("X-Login")!=null){try{cb(null,new HTTPBindingClient.Exception(403,'X-Login'));}catch(cbe){}}else if(http.responseText==''||http.getResponseHeader("Content-Type")==null){try{cb(null,new HTTPBindingClient.Exception(403,'No-Content'));}catch(cbe){}}else{if(item==null||http.responseText!=item){if(http.responseText!=null){localStorage.setItem(u,http.responseText);}
 try{cb(http.responseText);}catch(cbe){}}}}
 else{if(item==null){try{cb(null,new HTTPBindingClient.Exception(http.status,http.statusText));}catch(cbe){}}}}};http.send(null);return true;}
 http.send(null);if(http.status==200){if(http.getResponseHeader("X-Login")!=null){throw new HTTPBindingClient.Exception(403,'X-Login');}else if(http.responseText==''||http.getResponseHeader("Content-Type")==null){throw new HTTPBindingClient.Exception(403,'No-Content');}
 return http.responseText;}
-throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.prototype.getnocache=function(id,cb){var u=this.uri+'/'+id;var hascb=cb?true:false;var http=HTTPBindingClient.getHTTPRequest();http.open("GET",u,hascb);if(hascb){http.onreadystatechange=function(){if(http.readyState==4){if(http.status==200){if(http.getResponseHeader("X-Login")!=null){try{return cb(null,new HTTPBindingClient.Exception(403,'X-Login'));}catch(cbe){}}else if(http.responseText==''||http.getResponseHeader("Content-Type")==null){try{return cb(null,new HTTPBindingClient.Exception(403,'No-Content'));}catch(cbe){}}else{try{cb(http.responseText);}catch(cbe){}}}else{try{cb(null,new HTTPBindingClient.Exception(http.status,http.statusText));}catch(cbe){}}}};http.send(null);return true;}
+throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.prototype.getnocache=function(id,cb){var u=id?(this.uri?this.uri+'/'+id:id):this.uri;var hascb=cb?true:false;var http=HTTPBindingClient.getHTTPRequest();http.open("GET",u,hascb);if(hascb){http.onreadystatechange=function(){if(http.readyState==4){if(http.status==200){if(http.getResponseHeader("X-Login")!=null){try{return cb(null,new HTTPBindingClient.Exception(403,'X-Login'));}catch(cbe){}}else if(http.responseText==''||http.getResponseHeader("Content-Type")==null){try{return cb(null,new HTTPBindingClient.Exception(403,'No-Content'));}catch(cbe){}}else{try{cb(http.responseText);}catch(cbe){}}}else{try{cb(null,new HTTPBindingClient.Exception(http.status,http.statusText));}catch(cbe){}}}};http.send(null);return true;}
 http.send(null);if(http.status==200){if(http.getResponseHeader("X-Login")!=null){throw new HTTPBindingClient.Exception(403,'X-Login');}else if(http.responseText==''||http.getResponseHeader("Content-Type")==null){throw new HTTPBindingClient.Exception(403,'No-Content');}
 return http.responseText;}
 throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.prototype.post=function(entry,cb){var http=HTTPBindingClient.getHTTPRequest();var hascb=cb?true:false;http.open("POST",this.uri,hascb);http.setRequestHeader("Content-Type","application/atom+xml");if(hascb){http.onreadystatechange=function(){if(http.readyState==4){if(http.status==201){try{cb(http.responseText);}catch(cbe){}}
@@ -341,7 +347,9 @@
 return true;throw new HTTPBindingClient.Exception(http.status,http.statusText);};HTTPBindingClient.Exception=function(code,message){this.name="HTTPBindingClientException";this.code=code;this.message=message;};HTTPBindingClient.Exception.prototype=new Error();HTTPBindingClient.Exception.prototype.toString=function(){return this.name+": "+this.message;};HTTPBindingClient.msxmlNames=["MSXML2.XMLHTTP.5.0","MSXML2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];HTTPBindingClient.getHTTPRequest=function(){if(HTTPBindingClient.httpFactory)
 return HTTPBindingClient.httpFactory();try{HTTPBindingClient.httpFactory=function(){return new XMLHttpRequest();};return HTTPBindingClient.httpFactory();}catch(e){}
 for(var i=0;i<HTTPBindingClient.msxmlNames.length;i++){try{HTTPBindingClient.httpFactory=function(){return new ActiveXObject(HTTPBindingClient.msxmlNames[i]);};return HTTPBindingClient.httpFactory();}catch(e){}}
-HTTPBindingClient.httpFactory=null;throw new HTTPBindingClient.Exception(0,"Can't create XMLHttpRequest object");};var sca={};sca.httpclient=function(name,uri){return new HTTPBindingClient(name,uri);};sca.component=function(name){return new HTTPBindingClient(name,'/components/'+name);};sca.reference=function(comp,rname){return new HTTPBindingClient(comp.name+'/'+rname,"/references/"+comp.name+"/"+rname);};sca.defun=function(ref){function defapply(name){return function(){var args=new Array();args[0]=name;for(i=0,n=arguments.length;i<n;i++)
+HTTPBindingClient.httpFactory=null;throw new HTTPBindingClient.Exception(0,"Can't create XMLHttpRequest object");};var sca={};sca.httpclient=function(name,uri,domain){return new HTTPBindingClient(name,uri,domain);};sca.component=function(name,domain){if(!domain)
+return new HTTPBindingClient(name,'/c/'+name,domain);return new HTTPBindingClient(name,'/a/'+domain+'/c/'+name,domain);};sca.reference=function(comp,rname){if(!comp.domain)
+return new HTTPBindingClient(comp.name+'/'+rname,'/r/'+comp.name+'/'+rname,comp.domain);return new HTTPBindingClient(comp.name+'/'+rname,'/a/'+comp.domain+'/r/'+comp.name+'/'+rname,comp.domain);};sca.defun=function(ref){function defapply(name){return function(){var args=new Array();args[0]=name;for(i=0,n=arguments.length;i<n;i++)
 args[i+1]=arguments[i];return this.apply.apply(this,args);};}
 for(f=1;f<arguments.length;f++){var fn=arguments[f];ref[fn]=defapply(fn);}
-return ref;};
\ No newline at end of file
+return ref;};
diff --git a/modules/js/htdocs/component.js b/modules/js/htdocs/component.js
index 742e7be..95cad7c 100644
--- a/modules/js/htdocs/component.js
+++ b/modules/js/htdocs/component.js
@@ -102,8 +102,9 @@
 /**
  * Construct an HTTPBindingClient.
  */
-function HTTPBindingClient(name, uri) {
+function HTTPBindingClient(name, uri, domain) {
     this.name = name;
+    this.domain = domain;
     this.uri = uri;
     this.apply = this.createApplyMethod();
 }
@@ -230,10 +231,10 @@
 
 
 /**
- * REST ATOMPub GET method.
+ * REST GET method.
  */
 HTTPBindingClient.prototype.get = function(id, cb) {
-    var u = this.uri + '/' + id;
+    var u = id? (this.uri? this.uri + '/' + id : id) : this.uri;
     var hascb = cb? true : false;
 
     // Get from local storage first
@@ -321,10 +322,10 @@
 };
 
 /**
- * REST ATOMPub GET method, does not use the local cache.
+ * REST GET method, does not use the local cache.
  */
 HTTPBindingClient.prototype.getnocache = function(id, cb) {
-    var u = this.uri + '/' + id;
+    var u = id? (this.uri? this.uri + '/' + id : id) : this.uri;
     var hascb = cb? true : false;
 
     // Connect to the service
@@ -386,7 +387,7 @@
 };
 
 /**
- * REST ATOMPub POST method.
+ * REST POST method.
  */
 HTTPBindingClient.prototype.post = function (entry, cb) {
 
@@ -426,7 +427,7 @@
 };
 
 /**
- * REST ATOMPub PUT method.
+ * REST PUT method.
  */
 HTTPBindingClient.prototype.put = function (id, entry, cb) {
     var u = this.uri + '/' + id;
@@ -470,7 +471,7 @@
 };
 
 /**
- * REST ATOMPub DELETE method.
+ * REST DELETE method.
  */
 HTTPBindingClient.prototype.del = function (id, cb) {       
     var u = this.uri + '/' + id;
@@ -569,22 +570,26 @@
 /**
  * Return an HTTP client proxy.
  */
-sca.httpclient = function(name, uri) {
-    return new HTTPBindingClient(name, uri);
+sca.httpclient = function(name, uri, domain) {
+    return new HTTPBindingClient(name, uri, domain);
 };
 
 /**
  * Return a component proxy.
  */
-sca.component = function(name) {
-    return new HTTPBindingClient(name, '/components/' + name);
+sca.component = function(name, domain) {
+    if (!domain)
+        return new HTTPBindingClient(name, '/c/' + name, domain);
+    return new HTTPBindingClient(name, '/a/' + domain + '/c/' + name, domain);
 };
 
 /**
  * Return a reference proxy.
  */
 sca.reference = function(comp, rname) {
-    return new HTTPBindingClient(comp.name + '/' + rname, "/references/" + comp.name + "/" + rname);
+    if (!comp.domain)
+        return new HTTPBindingClient(comp.name + '/' + rname, '/r/' + comp.name + '/' + rname, comp.domain);
+    return new HTTPBindingClient(comp.name + '/' + rname, '/a/' + comp.domain + '/r/' + comp.name + '/' + rname, comp.domain);
 };
 
 /**
diff --git a/modules/js/htdocs/ui-min.css b/modules/js/htdocs/ui-min.css
index c879858..d8d8863 100644
--- a/modules/js/htdocs/ui-min.css
+++ b/modules/js/htdocs/ui-min.css
@@ -1,10 +1,38 @@
-body{margin-top:0px;margin-bottom:2px;margin-left:2px;margin-right:2px;font-family:"Helvetica Neue", Helvetica;font-style:normal;font-variant:normal;font-size:13px;-webkit-text-size-adjust:none;-webkit-touch-callout:none;-webkit-user-select:none;}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *   
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+body{margin-top:0px;margin-bottom:2px;margin-left:2px;margin-right:2px;font-family:"Helvetica Neue", Helvetica;font-style:normal;font-variant:normal;font-size:13px;-webkit-text-size-adjust:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-user-select:none;}
 .delayed{visibility:hidden;}
 .devicewidth{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;}
+.mainbodydiv{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;}
 .bodydiv{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;}
-.bodydivloading{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;-webkit-transform:translate(100%, 0px);-webkit-backface-visibility:hidden;-moz-transform:translate(100%, 0px);-ms-transform:translate(100%, 0px);transform:translate(100%, 0px);}
-.bodydivloaded{-webkit-transition:-webkit-transform 0.4s linear;-moz-transition:-moz-transform 0.4s linear;-ms-transition:-ms-transform 0.4s linear;transition:transform 0.4s linear;position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;-webkit-transform:translate3d(0px, 0px, 0px);-webkit-backface-visibility:hidden;-moz-transform:translate(0px, 0px);-ms-transform:translate(0px, 0px);transform:translate(0px, 0px);}
-.bodydivunloaded{-webkit-transition:-webkit-transform 0.4s linear;-moz-transition:-moz-transform 0.4s linear;-ms-transition:-ms-transform 0.4s linear;transition:transform 0.4s linear;position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:hidden;-webkit-transform:translate3d(-100%, 0px, 0px);-webkit-backface-visibility:hidden;-moz-transform:translate(-100%, 0px);-ms-transform:translate(-100%, 0px);transform:translate(-100%, 0px);}
+.viewcontainer3dm{-webkit-perspective:1000;}
+.viewcontainer3d{}
+.leftviewloading3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transform:translate3d(100%, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(100%, 0px);-ms-transform:translate(100%, 0px);transform:translate(100%, 0px);}
+.rightviewloading3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transform:translate3d(-100%, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(-100%, 0px);-ms-transform:translate(-100%, 0px);transform:translate(-100%, 0px);}
+.flipviewloading3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transform:translate3d(0px, 0px, 0) rotateY(180deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(100%, 0px);-ms-transform:translate(100%, 0px);transform:translate(100%, 0px);}
+.viewloading3d{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;visibility:hidden;-webkit-transform:translate3d(100%, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(100%, 0px);-ms-transform:translate(100%, 0px);transform:translate(100%, 0px);}
+.viewloaded3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transition:-webkit-transform 0.5s ease-in-out;-moz-transition:-moz-transform 0.5s ease-in-out;-ms-transition:-ms-transform 0.5s ease-in-out;transition:transform 0.5s ease-in-out;-webkit-transform:translate3d(0px, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(0px, 0px);-ms-transform:translate(0px, 0px);transform:translate(0px, 0px);}
+.viewloaded3d{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transform:translate3d(0px, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(0px, 0px);-ms-transform:translate(0px, 0px);transform:translate(0px, 0px);}
+.viewunloading3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transform:translate3d(0px, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(0px, 0px);-ms-transform:translate(0px, 0px);transform:translate(0px, 0px);}
+.leftviewunloaded3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transition:-webkit-transform 0.5s ease-in-out;-moz-transition:-moz-transform 0.5s ease-in-out;-ms-transition:-ms-transform 0.5s ease-in-out;transition:transform 0.5s ease-in-out;-webkit-transform:translate3d(-100%, 0px, 0px) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(-100%, 0px);-ms-transform:translate(-100%, 0px);transform:translate(-100%, 0px);}
+.rightviewunloaded3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transition:-webkit-transform 0.5s ease-in-out;-moz-transition:-moz-transform 0.5s ease-in-out;-ms-transition:-ms-transform 0.5s ease-in-out;transition:transform 0.5s ease-in-out;-webkit-transform:translate3d(100%, 0px, 0) rotateY(0deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(100%, 0px);-ms-transform:translate(100%, 0px);transform:translate(100%, 0px);}
+.flipviewunloaded3dm{position:absolute;top:0px;left:0px;width:100%;height:5000px;overflow:visible;-webkit-transition:-webkit-transform 0.5s ease-in-out;-moz-transition:-moz-transform 0.5s ease-in-out;-ms-transition:-ms-transform 0.5s ease-in-out;transition:transform 0.5s ease-in-out;-webkit-transform:translate3d(0px, 0px, 0) rotateY(-180deg);-webkit-backface-visibility:hidden;background-color:#ffffff;-moz-transform:translate(0px, 0px);-ms-transform:translate(0px, 0px);transform:translate(0px, 0px);}
 table{border:0px;border-collapse:collapse;border-color:#a2bae7;border-style:solid;font-family:"Helvetica Neue", Helvetica;font-style:normal;font-variant:normal;font-size:13px;overflow:visible;}
 .trb{border-bottom:1px;border-bottom-style:solid;border-color:#dcdcdc;}
 th{font-weight:bold;background-color:#d4e6fc;color:#000000;height:18px;text-align:left;padding-left:2px;padding-right:8px;padding-top:0px;padding-bottom:0px;vertical-align:middle;white-space:nowrap;border-top:1px;border-bottom:1px;border-left:1px;border-right:1px;border-style:solid;border-top-color:#a2bae7;border-bottom-color:#d1d3d4;border-left-color:#a2bae7;border-right-color:#a2bae7;overflow:hidden;}
@@ -21,6 +49,7 @@
 .tdw{padding-left:2px;padding-top:2px;padding-right:8px;white-space:normal;vertical-align:middle;}
 .datatd{border-top:1px;border-bottom:1px;border-style:solid;border-color:#dcdcdc;width:10px;vertical-align:middle;}
 .datatdl{border-right:1px;border-top:1px;border-bottom:1px;border-style:solid;border-color:#dcdcdc;width:10px;vertical-align:middle;}
+.datatdltop{border-right:1px;border-top:1px;border-bottom:1px;border-style:solid;border-color:#dcdcdc;width:10px;vertical-align:top;}
 .datatdr{border-left:1px;border-top:1px;border-bottom:1px;border-style:solid;border-color:#dcdcdc;vertical-align:middle;}
 .datatable{border-top:1px;border-bottom:1px;border-style:solid;border-color:#dcdcdc;overflow:visible;}
 .databg{opacity:.6;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";filter:alpha(opacity=60);}
@@ -51,10 +80,10 @@
 .ltbar{padding-left:2px;padding-right:6px;padding-top:3px;padding-bottom:4px;white-space:nowrap;vertical-align:middle;}
 .dtbar{padding-left:0px;padding-right:0px;padding-top:3px;padding-bottom:4px;white-space:nowrap;vertical-align:middle;text-align:right;}
 .rtbar{padding-left:6px;padding-right:2px;padding-top:3px;padding-bottom:4px;white-space:nowrap;vertical-align:middle;text-align:right;}
-.tbaramenu{color:#cccccc;text-decoration:none;white-space:nowrap;}
-.tbarsmenu{font-weight:bold;color:#ffffff;text-decoration:none;white-space:nowrap;}
+.tbaramenu{color:#cccccc;text-decoration:none;white-space:nowrap;vertical-align:middle;}
+.tbarsmenu{font-weight:bold;color:#ffffff;text-decoration:none;white-space:nowrap;vertical-align:middle;}
 .suggest{background-color:#d4e6fc;color:#598edd;border-top:1px;border-bottom:1px;border-left:1px;border-right:1px;border-style:solid;border-top-color:#a2bae7;border-bottom-color:#d1d3d4;border-left-color:#d1d3d4;border-right-color:#d1d3d4;position:absolute;overflow:auto;overflow-x:hidden;padding:0px;margin:0px;cursor:default;}
 .suggestTable{border:0px;border-collapse:separate;padding-left:5px;padding-right:5px;padding-top:2px;padding-bottom:2px;margin:0px;}
 .suggestItem{padding-left:2px;padding-top:0px;padding-bottom:0px;padding-right:2px;vertical-align:middle;background-color:#d4e6fc;color:#598edd;}
 .suggestHilighted{padding-left:2px;padding-top:0px;padding-bottom:0px;padding-right:2px;vertical-align:middle;background-color:#598edd;color:#d4e6fc;}
-.svgtitle{margin:0px;padding:0px;font-family:"Helvetica Neue", Helvetica;font-style:normal;font-variant:normal;font-size:10px;cursor:default;}
\ No newline at end of file
+.svgtitle{margin:0px;padding:0px;font-family:"Helvetica Neue", Helvetica;font-style:normal;font-variant:normal;font-size:10px;cursor:default;}
diff --git a/modules/js/htdocs/ui.css b/modules/js/htdocs/ui.css
index b4c27a5..42d89b8 100644
--- a/modules/js/htdocs/ui.css
+++ b/modules/js/htdocs/ui.css
@@ -22,6 +22,7 @@
 font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px;
 -webkit-text-size-adjust: none;
 -webkit-touch-callout: none;
+-webkit-tap-highlight-color: rgba(0,0,0,0);
 -webkit-user-select: none;
 }
 
@@ -33,42 +34,128 @@
 position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
 }
 
+.mainbodydiv {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
+}
+
 .bodydiv {
 position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
 }
 
-.bodydivloading {
-position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
--webkit-transform: translate(100%, 0px); -webkit-backface-visibility: hidden;
+.viewcontainer3dm {
+-webkit-perspective: 1000;
+}
+
+.viewcontainer3d {
+}
+
+.leftviewloading3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
 -moz-transform: translate(100%, 0px);
 -ms-transform: translate(100%, 0px);
 transform: translate(100%, 0px);
 }
 
-.bodydivloaded {
--webkit-transition: -webkit-transform 0.4s linear;
--moz-transition: -moz-transform 0.4s linear;
--ms-transition: -ms-transform 0.4s linear;
-transition: transform 0.4s linear;
-position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
--webkit-transform: translate3d(0px, 0px, 0px); -webkit-backface-visibility: hidden;
+.rightviewloading3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transform: translate3d(-100%, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(-100%, 0px);
+-ms-transform: translate(-100%, 0px);
+transform: translate(-100%, 0px);
+}
+
+.flipviewloading3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transform: translate3d(0px, 0px, 0) rotateY(180deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(100%, 0px);
+-ms-transform: translate(100%, 0px);
+transform: translate(100%, 0px);
+}
+
+.viewloading3d {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+visibility: hidden;
+-webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(100%, 0px);
+-ms-transform: translate(100%, 0px);
+transform: translate(100%, 0px);
+}
+
+.viewloaded3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transition: -webkit-transform 0.5s ease-in-out;
+-moz-transition: -moz-transform 0.5s ease-in-out;
+-ms-transition: -ms-transform 0.5s ease-in-out;
+transition: transform 0.5s ease-in-out;
+-webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
 -moz-transform: translate(0px, 0px);
 -ms-transform: translate(0px, 0px);
 transform: translate(0px, 0px);
 }
 
-.bodydivunloaded {
--webkit-transition: -webkit-transform 0.4s linear;
--moz-transition: -moz-transform 0.4s linear;
--ms-transition: -ms-transform 0.4s linear;
-transition: transform 0.4s linear;
-position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden;
--webkit-transform: translate3d(-100%, 0px, 0px); -webkit-backface-visibility: hidden;
+.viewloaded3d {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(0px, 0px);
+-ms-transform: translate(0px, 0px);
+transform: translate(0px, 0px);
+}
+
+.viewunloading3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(0px, 0px);
+-ms-transform: translate(0px, 0px);
+transform: translate(0px, 0px);
+}
+
+.leftviewunloaded3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transition: -webkit-transform 0.5s ease-in-out;
+-moz-transition: -moz-transform 0.5s ease-in-out;
+-ms-transition: -ms-transform 0.5s ease-in-out;
+transition: transform 0.5s ease-in-out;
+-webkit-transform: translate3d(-100%, 0px, 0px) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
 -moz-transform: translate(-100%, 0px);
 -ms-transform: translate(-100%, 0px);
 transform: translate(-100%, 0px);
 }
 
+.rightviewunloaded3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transition: -webkit-transform 0.5s ease-in-out;
+-moz-transition: -moz-transform 0.5s ease-in-out;
+-ms-transition: -ms-transform 0.5s ease-in-out;
+transition: transform 0.5s ease-in-out;
+-webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(100%, 0px);
+-ms-transform: translate(100%, 0px);
+transform: translate(100%, 0px);
+}
+
+.flipviewunloaded3dm {
+position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible;
+-webkit-transition: -webkit-transform 0.5s ease-in-out;
+-moz-transition: -moz-transform 0.5s ease-in-out;
+-ms-transition: -ms-transform 0.5s ease-in-out;
+transition: transform 0.5s ease-in-out;
+-webkit-transform: translate3d(0px, 0px, 0) rotateY(-180deg);
+-webkit-backface-visibility: hidden; background-color: #ffffff;
+-moz-transform: translate(0px, 0px);
+-ms-transform: translate(0px, 0px);
+transform: translate(0px, 0px);
+}
+
 table {
 border: 0px; border-collapse: collapse; border-color: #a2bae7; border-style: solid;
 font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px;
@@ -143,6 +230,10 @@
 border-right: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc; width: 10px; vertical-align: middle;
 }
 
+.datatdltop {
+border-right: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc; width: 10px; vertical-align: top;
+}
+
 .datatdr {
 border-left: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc; vertical-align: middle;
 }
@@ -335,11 +426,11 @@
 }
 
 .tbaramenu {
-color: #cccccc; text-decoration: none; white-space: nowrap;
+color: #cccccc; text-decoration: none; white-space: nowrap; vertical-align: middle;
 }
 
 .tbarsmenu {
-font-weight: bold; color: #ffffff; text-decoration: none; white-space: nowrap;
+font-weight: bold; color: #ffffff; text-decoration: none; white-space: nowrap; vertical-align: middle;
 }
 
 .suggest {
diff --git a/modules/js/htdocs/ui.js b/modules/js/htdocs/ui.js
index 7c079e3..d8628f6 100644
--- a/modules/js/htdocs/ui.js
+++ b/modules/js/htdocs/ui.js
@@ -24,6 +24,227 @@
 var ui = {};
 
 /**
+ * Return a child element of a node with the given id.
+ */
+ui.elementByID = function(node, id) {
+    if (node.skipNode == true)
+        return null;
+    for (var i in node.childNodes) {
+        var child = node.childNodes[i];
+        if (child.id == id)
+            return child;
+        var gchild = ui.elementByID(child, id);
+        if (gchild != null)
+            return gchild;
+    }
+    return null;
+};
+
+/**
+ * Return the current document, or a child element with the given id.
+ */
+function $(id) {
+    if (id == document)
+        return document;
+    return ui.elementByID($(document), id);
+};
+
+/**
+ * Return the query string of a URL.
+ */
+ui.query = function(url) {
+    var u = '' + url;
+    var q = u.indexOf('?');
+    return q >= 0? u.substring(q + 1) : '';
+};
+
+/**
+ * Return the fragment part of a URL.
+ */
+ui.fragment = function(url) {
+    var u = '' + url;
+    var h = u.indexOf('#');
+    return h >= 0? u.substring(h + 1) : '';
+};
+
+/**
+ * Return the path and parameters of a URL.
+ */
+ui.pathandparams = function(url) {
+    var u = '' + url;
+    var ds = u.indexOf('//');
+    var u2 = ds > 0? u.substring(ds + 2) : u;
+    var s = u2.indexOf('/');
+    return s > 0? u2.substring(s) : '';
+};
+
+/**
+ * Return a dictionary of query parameters in a URL.
+ */
+ui.queryParams = function(url) {
+    var qp = new Array();
+    var qs = ui.query(url).split('&');
+    for (var i = 0; i < qs.length; i++) {
+        var e = qs[i].indexOf('=');
+        if (e > 0)
+            qp[qs[i].substring(0, e)] = unescape(qs[i].substring(e + 1));
+    }
+    return qp;
+};
+
+/**
+ * Return a dictionary of fragment parameters in a URL.
+ */
+ui.fragmentParams = function(url) {
+    var qp = new Array();
+    var qs = ui.fragment(url).split('&');
+    for (var i = 0; i < qs.length; i++) {
+        var e = qs[i].indexOf('=');
+        if (e > 0)
+            qp[qs[i].substring(0, e)] = unescape(qs[i].substring(e + 1));
+    }
+    return qp;
+};
+
+/**
+ * Convert a base64-encoded image to a data URL.
+ */
+ui.b64img = function(b64) {
+    return 'data:image/png;base64,' + b64;
+};
+
+/**
+ * Declare a CSS stylesheet.
+ */
+ui.declareCSS = function(s) {
+    var e = document.createElement('style');
+    e.type = 'text/css';
+    e.textContent = s;
+    return e;
+};
+
+/**
+ * Declare a script.
+ */
+ui.declareScript = function(s) {
+    var e = document.createElement('script');
+    e.type = 'text/javascript';
+    e.text = s;
+    return e;
+};
+
+/**
+ * Return the scripts elements under a given element.
+ */
+ui.innerScripts = function(e) {
+    return map(function(s) { return s.text; },  nodeList(e.getElementsByTagName('script')));
+};
+
+/**
+ * Evaluate a script.
+ */
+ui.evalScript = function(s) {
+    return eval('(function() {\n' + s + '\n})();');
+};
+
+/**
+ * Include a script.
+ */
+ui.includeScript = function(s) {
+    log('include', s);
+    return eval(s);
+};
+
+/**
+ * Return true if the client is a mobile device.
+ */
+ui.mobiledetected = false;
+ui.mobile = false;
+ui.isMobile = function() {
+    if (ui.mobiledetected)
+        return ui.mobile;
+    var ua = navigator.userAgent;
+    if (ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/Android/i) || ua.match(/Blackberry/i) || ua.match(/WebOs/i))
+        ui.mobile = true;
+    ui.mobiledetected = true;
+    return ui.mobile;
+};
+
+/**
+ * Convert a host name to a home page title.
+ */
+ui.hometitle = function(host) {
+    if (!isNil(window.top.config.hometitle))
+        return window.top.config.hometitle;
+    var h = reverse(host.split('.'));
+    var d = isNil(cdr(h))? car(h) : cadr(h);
+    return d.substr(0, 1).toUpperCase() + d.substr(1);
+};
+
+/**
+ * Convert a host name to a window title.
+ */
+ui.windowtitle = function(host) {
+    if (!isNil(window.top.config.windowtitle))
+        return window.top.config.windowtitle;
+    var h = reverse(host.split('.'));
+    var d = isNil(cdr(h))? car(h) : cadr(h);
+    return d.substr(0, 1).toUpperCase() + d.substr(1);
+};
+
+/**
+ * Convert a CSS position to a numeric position.
+ */
+ui.numpos = function(p) {
+    return p == ''? 0 : Number(p.substr(0, p.length - 2));
+};
+
+/**
+ * Convert a numeric position to a CSS pixel position.
+ */
+ui.pixpos = function(p) {
+    return p + 'px';
+};
+
+/**
+ * Reload the current document when orientation changes.
+ */
+ui.onorientationchange = function() {
+    window.location.reload();
+    return true;
+}
+
+/**
+ * Navigate to a new document.
+ */
+ui.navigate = function(url, win) {
+    log('navigate', url, win);
+
+    // Open a new window
+    if (win == '_blank')
+        return window.top.open(url, win);
+
+    // Open a new document in the current window
+    if (win == '_self')
+        return window.top.open(url, win);
+
+    // Reload the current window
+    if (win == '_reload') {
+        window.top.location = url;
+        return window.top.location.reload();
+    }
+
+    // Let the current top window handle the navigation
+    if (win == '_view') {
+        if (!window.top.onnavigate)
+            return window.top.open(url, '_self');
+        return window.top.onnavigate(url);
+    }
+
+    return window.top.open(url, win);
+}
+
+/**
  * Build a portable <a href> tag.
  */
 ui.ahref = function(loc, target, html) {
@@ -35,33 +256,15 @@
 /**
  * Build a menu bar.
  */ 
-ui.menu = function(name, href, target) {
-    function Menu(n, h, t) {
-        this.name = n;
-        this.href = h;
-        this.target = isNil(t)? '_parent' : t;
-
+ui.menu = function(name, href, target, hilight) {
+    function Menu() {
         this.content = function() {
-            function complete(uri) {
-                var h = uri.indexOf('#');
-                if (h != -1)
-                    return complete(uri.substr(0, h));
-                var q = uri.indexOf('?');
-                if (q != -1)
-                    return complete(uri.substr(0, q));
-                if (uri.match('.*\.html$'))
-                    return uri;
-                if (uri.match('.*/$'))
-                    return uri + 'index.html';
-                return uri + '/index.html';
-            }
-
-            if (complete(this.href) != complete(window.top.location.pathname))
-                return ui.ahref(this.href, this.target, '<span class="tbaramenu">' + this.name + '</span>');
-            return ui.ahref(this.href, this.target, '<span class="tbarsmenu">' + this.name + '</span>');
+            if (hilight)
+                return ui.ahref(href, target, '<span class="tbarsmenu">' + name + '</span>');
+            return ui.ahref(href, target, '<span class="tbaramenu">' + name + '</span>');
         };
     }
-    return new Menu(name, href, target);
+    return new Menu();
 };
 
 ui.menubar = function(left, right) {
@@ -80,6 +283,88 @@
 };
  
 /**
+ * Convert a list of elements to an HTML table.
+ */
+ui.datatable = function(l) {
+
+    function indent(i) {
+        if (i == 0)
+            return '';
+        return '&nbsp;&nbsp;' + indent(i - 1);
+    }
+
+    function rows(l, i) {
+        if (isNil(l))
+            return '';
+        var e = car(l);
+
+        // Convert a list of simple values into a list of name value pairs
+        if (!isList(e))
+            return rows(expandElementValues("'value", l), i);
+
+        // Convert a list of complex values into a list of name value pairs
+        if (isList(car(e)))
+            return rows(expandElementValues("'value", l), i);
+
+        // Generate table row for a simple element value
+        if (elementHasValue(e)) {
+            var v = elementValue(e);
+            if (!isList(v)) {
+                return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
+                    '<td class="datatdr tdw">' + (v != null? v : '') + '</td></tr>' +
+                    rows(cdr(l), i);
+            }
+
+            return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i);
+        }
+
+        // Generate table row for an element with children
+        return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
+            '<td class="datatdr tdw">' + '</td></tr>' +
+            rows(elementChildren(e), i + 1) +
+            rows(cdr(l), i);
+    }
+
+    return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>';
+}
+
+/**
+ * Convert a list of elements to an HTML single column table.
+ */
+ui.datalist = function(l) {
+
+    function rows(l, i) {
+        if (isNil(l))
+            return '';
+        var e = car(l);
+
+        // Convert a list of simple values into a list of name value pairs
+        if (!isList(e))
+            return rows(expandElementValues("'value", l), i);
+
+        // Convert a list of complex values into a list of name value pairs
+        if (isList(car(e)))
+            return rows(expandElementValues("'value", l), i);
+
+        // Generate table row for a simple element value
+        if (elementHasValue(e)) {
+            var v = elementValue(e);
+            if (!isList(v)) {
+                return '<tr><td class="datatd tdw">' + (v != null? v : '') + '</td></tr>' +
+                    rows(cdr(l), i);
+            }
+
+            return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i);
+        }
+
+        // Generate rows for an element's children
+        return rows(elementChildren(e), i + 1) + rows(cdr(l), i);
+    }
+
+    return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>';
+}
+
+/**
  * Autocomplete / suggest support for input fields
  * To use it declare a 'suggest' function as follows:
  * function suggestItems() {
@@ -181,295 +466,3 @@
     };
 };
 
-/**
- * Return a child element of a node with the given id.
- */
-ui.elementByID = function(node, id) {
-    for (var i in node.childNodes) {
-        var child = node.childNodes[i];
-        if (child.id == id)
-            return child;
-        var gchild = ui.elementByID(child, id);
-        if (gchild != null)
-            return gchild;
-    }
-    return null;
-};
-
-/**
- * Return the current document, or a child element with the given id.
- */
-function $(id) {
-    if (id == document) {
-        if (!isNil(document.widget))
-            return document.widget;
-        return document;
-    }
-    return ui.elementByID($(document), id);
-};
-
-/**
- * Return a dictionary of the query parameters.
- */
-ui.queryParams = function() {
-    var qp = new Array();
-    var qs = window.location.search.substring(1).split('&');
-    for (var i = 0; i < qs.length; i++) {
-        var e = qs[i].indexOf('=');
-        if (e > 0)
-            qp[qs[i].substring(0, e)] = unescape(qs[i].substring(e + 1));
-    }
-    return qp;
-};
-
-/**
- * Return a dictionary of the fragment parameters.
- */
-ui.fragmentParams = function() {
-    var qp = new Array();
-    var qs = window.location.hash.substring(1).split('&');
-    for (var i = 0; i < qs.length; i++) {
-        var e = qs[i].indexOf('=');
-        if (e > 0)
-            qp[qs[i].substring(0, e)] = unescape(qs[i].substring(e + 1));
-    }
-    return qp;
-};
-
-/**
- * Return true if the client is a mobile device.
- */
-ui.mobiledetected = false;
-ui.mobile = false;
-ui.isMobile = function() {
-    if (ui.mobiledetected)
-        return ui.mobile;
-    var ua = navigator.userAgent;
-    if (ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/Android/i) || ua.match(/Blackberry/i) || ua.match(/WebOs/i))
-        ui.mobile = true;
-    ui.mobiledetected = true;
-    return ui.mobile;
-};
-
-/**
- * Initialize a document's body.
- */
-ui.pagetransitions = false;
-
-ui.initbody = function() {
-    if (ui.isMobile()) {
-        //log('init', window.location);
-
-        // Position the main body div off screen
-        if (ui.pagetransitions) {
-            var bdiv = $('bodydiv');
-            if (!isNil(bdiv)) {
-                bdiv.className = 'bodydivloading';
-            }
-        }
-
-        // Install orientation handler
-        document.body.onorientationchange = ui.onorientationchange;
-    }
-    return true;
-}
-
-/**
- * Reload the current document when orientation changes.
- */
-ui.onorientationchange = function() {
-    window.open(window.location, '_self');
-    return true;
-}
-
-/**
- * Post process a document after it's loaded.
- */
-ui.onload = function() {
-
-    // Save the current page location in local storage
-    // (except for login and logout pages)
-    var path = document.location.pathname;
-    if (path.indexOf('/login/') != 0 && path.indexOf('/logout/') != 0)
-        localStorage.setItem('ui.lastvisited', '' + document.location);
-
-    // Make the document body visible
-    //log('visible', $('bodydiv').className);
-    document.body.style.visibility = 'visible';
-
-    if (ui.pagetransitions && ui.isMobile()) {
-        //log('onload', window.location);
-        
-        // Slide the main body div in
-        setTimeout(function() {
-            var bdiv = $('bodydiv');
-            if (!isNil(bdiv)) { 
-                function transitionend(e) {
-                    bdiv.removeEventListener('webkitTransitionEnd', transitionend, false);
-                    bdiv.removeEventListener('transitionend', transitionend, false);
-                    bdiv.className = 'bodydiv';
-                    //log('loadtransitionend', window.location);
-                };
-                bdiv.addEventListener('webkitTransitionEnd', transitionend, false);
-                bdiv.addEventListener('transitionend', transitionend, false);
-                //log('loadtransitionstart', window.location);
-                bdiv.className = 'bodydivloaded';
-            }
-        }, 0);
-    }
-    return true;
-};
-
-/**
- * Navigate to a new document.
- */
-ui.navigate = function(url, win) {
-
-    function opendoc(url, win) {
-        if (win == '_reload') {
-            window.location = url;
-            return window.location.reload();
-        }
-        return window.open(url, win);
-    }
-
-    if (ui.pagetransitions && ui.isMobile() && win != '_blank') {
-
-        // Slide the main body div out, then open the new document
-        var bdiv = $('bodydiv');
-        if (!isNil(bdiv)) {
-            function transitionend(e) {
-                bdiv.removeEventListener('webkitTransitionEnd', transitionend, false);
-                bdiv.removeEventListener('transitionend', transitionend, false);
-                //log('navigatetransitionend', window.location);
-                return opendoc(url, win);
-            };
-            bdiv.addEventListener('webkitTransitionEnd', transitionend, false);
-            bdiv.addEventListener('transitionend', transitionend, false);
-            //log('navigatetransitionstart', window.location);
-            bdiv.className = 'bodydivunloaded';
-            return true;
-        }
-    }
-
-    return opendoc(url, win); 
-}
-
-/**
- * Pre process a document just before it's unloaded.
- */
-ui.onbeforeunload = function() {
-
-    if (ui.pagetransitions && ui.isMobile()) {
-        
-        // Slide the main body div out
-        var bdiv = $('bodydiv');
-        if (!isNil(bdiv))
-            bdiv.className = 'bodydivunloaded';
-    }
-};
-
-
-/**
- * Return the last visited page.
- */
-ui.lastvisited = function() {
-    return localStorage.getItem('ui.lastvisited');
-}
-
-/**
- * Convert a CSS position to a numeric position.
- */
-ui.numpos = function(p) {
-    return p == ''? 0 : Number(p.substr(0, p.length - 2));
-};
-
-/**
- * Convert a numeric position to a CSS pixel position.
- */
-ui.pixpos = function(p) {
-    return p + 'px';
-};
-
-/**
- * Convert a list of elements to an HTML table.
- */
-ui.datatable = function(l) {
-
-    function indent(i) {
-        if (i == 0)
-            return '';
-        return '&nbsp;&nbsp;' + indent(i - 1);
-    }
-
-    function rows(l, i) {
-        if (isNil(l))
-            return '';
-        var e = car(l);
-
-        // Convert a list of simple values into a list of name value pairs
-        if (!isList(e))
-            return rows(expandElementValues("'value", l), i);
-
-        // Convert a list of complex values into a list of name value pairs
-        if (isList(car(e)))
-            return rows(expandElementValues("'value", l), i);
-
-        // Generate table row for a simple element value
-        if (elementHasValue(e)) {
-            var v = elementValue(e);
-            if (!isList(v)) {
-                return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
-                    '<td class="datatdr tdw">' + (v != null? v : '') + '</td></tr>' +
-                    rows(cdr(l), i);
-            }
-
-            return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i);
-        }
-
-        // Generate table row for an element with children
-        return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
-            '<td class="datatdr tdw">' + '</td></tr>' +
-            rows(elementChildren(e), i + 1) +
-            rows(cdr(l), i);
-    }
-
-    return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>';
-}
-
-/**
- * Convert a list of elements to an HTML single column table.
- */
-ui.datalist = function(l) {
-
-    function rows(l, i) {
-        if (isNil(l))
-            return '';
-        var e = car(l);
-
-        // Convert a list of simple values into a list of name value pairs
-        if (!isList(e))
-            return rows(expandElementValues("'value", l), i);
-
-        // Convert a list of complex values into a list of name value pairs
-        if (isList(car(e)))
-            return rows(expandElementValues("'value", l), i);
-
-        // Generate table row for a simple element value
-        if (elementHasValue(e)) {
-            var v = elementValue(e);
-            if (!isList(v)) {
-                return '<tr><td class="datatd tdw">' + (v != null? v : '') + '</td></tr>' +
-                    rows(cdr(l), i);
-            }
-
-            return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i);
-        }
-
-        // Generate rows for an element's children
-        return rows(elementChildren(e), i + 1) + rows(cdr(l), i);
-    }
-
-    return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>';
-}
-
diff --git a/modules/js/htdocs/util.js b/modules/js/htdocs/util.js
index fd7005e..86e17c4 100644
--- a/modules/js/htdocs/util.js
+++ b/modules/js/htdocs/util.js
@@ -327,28 +327,6 @@
 }
 
 /**
- * Convert a host name to a home page title.
- */
-function hometitle(host) {
-    if (!isNil(config.hometitle))
-        return config.hometitle;
-    var h = reverse(host.split('.'));
-    var d = isNil(cdr(h))? car(h) : cadr(h);
-    return d.substr(0, 1).toUpperCase() + d.substr(1);
-}
-
-/**
- * Convert a host name to a window title.
- */
-function windowtitle(host) {
-    if (!isNil(config.windowtitle))
-        return config.windowtitle;
-    var h = reverse(host.split('.'));
-    var d = isNil(cdr(h))? car(h) : cadr(h);
-    return d.substr(0, 1).toUpperCase() + d.substr(1);
-}
-
-/**
  * Format a string like Python format.
  */
 function format() {
diff --git a/modules/server/mod-wiring.cpp b/modules/server/mod-wiring.cpp
index e56cee1..cb2f44c 100644
--- a/modules/server/mod-wiring.cpp
+++ b/modules/server/mod-wiring.cpp
@@ -186,6 +186,38 @@
 }
 
 /**
+ * Route an /apps/app-name/... request to the target app domain.
+ */
+int translateDomain(request_rec *r) {
+    httpdDebugRequest(r, "modwiring::translateDomain::input");
+    debug(r->uri, "modwiring::translateDomain::uri");
+
+    // Extract the requested app name
+    const list<value> apath(pathValues(r->uri));
+    if (isNil(cdr(apath)))
+        return HTTP_NOT_FOUND;
+
+    // Compute the target uri in the target app domain
+    ostringstream turi;
+    turi << httpd::scheme(r) << "://" << string(cadr(apath)) << "." << httpd::hostName(r) << ":" << httpd::port(r) << string(path(cddr(apath))) << (r->args != NULL? string("?") + r->args : string(""));
+    debug(str(turi), "modwiring::translateDomain::appuri");
+    
+    // Route to an absolute target URI using mod_proxy or an HTTP client redirect
+    if (useModProxy) {
+        r->filename = apr_pstrdup(r->pool, c_str(string("proxy:") + str(turi)));
+        debug(r->filename, "modwiring::translateDomain::filename");
+        r->proxyreq = PROXYREQ_REVERSE;
+        r->handler = "proxy-server";
+        apr_table_setn(r->notes, "proxy-nocanon", "1");
+        return OK;
+    }
+
+    debug(str(turi), "modwiring::translateDomain::location");
+    r->handler = "mod_tuscany_wiring";
+    return httpd::externalRedirect(str(turi), r);
+}
+
+/**
  * Read the components declared in a composite.
  */
 const failable<list<value> > readComponents(const string& path) {
@@ -315,6 +347,10 @@
     // Create a scoped memory pool
     gc_scoped_pool pool(r->pool);
 
+    // Translate an app domain request
+    if (!strncmp(r->uri, "/apps/", 6) || !strncmp(r->uri, "/a/", 3))
+        return translateDomain(r);
+
     // Get the server configuration
     const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_wiring);