diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index 26b041f209795872126d7eb0b6759798ae00eec6..ccc7dddaf53e1c329bf736f623f4a9c966c1856a 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -17,34 +17,68 @@ siteId:
     validation: required id onblur
     description: "The id used to refer to this site (also used to generate database ids). The Site ID must start with a letter and contain only letters, numbers and underscores. It should be a short, one-word name or acronym which describes your site. No spaces or non-alphanumeric characters."
 
-siteDescriptionType:
-    kind: panel.display
-    id: siteDescriptionType
-    name: siteDescriptionType
-    label: Site Description
-    value: Select a site description option below
-    before:
-        - "<script type=\"text/javascript\" src=\"../../scripts/xnat/admin/siteInfo.js\"></script>"
-    after:
-        - "<span style=\"position: relative; top: -15px;left: 270px;\"><input type=\"radio\" name=\"siteDescriptionType\" id=\"siteDescriptionTypePage\" value=\"Page\">&nbsp;&nbsp;Page&nbsp;&nbsp;&nbsp;&nbsp;<input type=\"radio\" name=\"siteDescriptionType\" id=\"siteDescriptionTypeText\" value=\"Text\">&nbsp;&nbsp;Text (Markdown)</span>"
-
 siteDescriptionPage:
-    kind: panel.input.text
-    id: siteDescriptionPage
-    name: siteDescriptionPage
-    label: " "
-    description: "Specify a velocity template file to display on the login page"
+    tag: input
+    element:
+        type: text
+        id: siteDescriptionPage
+        name: siteDescriptionPage
+        size: 50
+    after: "<p>Specify a velocity template file to display on the login page</p>"
 
 siteDescriptionText:
-    kind: panel.textarea
-    id: siteDescriptionText
-    name: siteDescriptionText
-    label: " "
-    description: "Specify a simple text description of this site"
+    tag: textarea
     element:
-        style:
-            width: 300px
-            height: 150px
+        id: siteDescriptionText
+        name: siteDescriptionText
+        rows: 8
+    after: "<p>Specify a simple text description of this site.</p>"
+
+siteDescriptionType:
+    kind: panel.element
+    #id: siteDescriptionType
+    #name: siteDescriptionType
+    label: Site Description
+    contents:
+        info:
+            tag: p
+            element:
+                style: "margin: 3px 0 8px 0;"
+            content: Select a site description option below
+        siteDescriptionTypePage:
+            tag: input|data-value=Page
+            element:
+                type: radio
+                name: siteDescriptionType
+                id: siteDescriptionTypePage
+                value: Page
+            after:
+                label:
+                    tag: label.pad5h|for=siteDescriptionTypePage
+                    content: Page
+        siteDescriptionTypeText:
+            tag: input|data-value=Text
+            element:
+                type: radio
+                name: siteDescriptionType
+                id: siteDescriptionTypeText
+                value: Text
+            after:
+                label:
+                    tag: label.pad5h|for=siteDescriptionTypeText
+                    content: Text (Markdown)
+        pagePath:
+            tag: div.input-bundle.page
+            contents:
+                ${siteDescriptionPage}
+        pageText:
+            tag: div.input-bundle.text
+            contents:
+                ${siteDescriptionText}
+        siteInfoJs:
+            tag: script
+            element:
+                src: ~/scripts/xnat/admin/siteInfo.js
 
 siteLoginLanding:
     kind: panel.input.text
@@ -100,6 +134,8 @@ archivePath:
     label: Archive Path
     validation: required path
     description: ""
+    element:
+        disabled: true
 cachePath:
     kind: panel.input.text
     id: cachePath
@@ -223,9 +259,7 @@ userLoginsSessionControls:
             id: sessionTimeout
             name: sessionTimeout
             label: Session Timeout
-            description: >
-              Interval for timing out user sessions. Uses
-              <a target="_blank" href="http://www.postgresql.org/docs/9.0/static/functions-datetime.html">PostgreSQL interval notation</a>
+            description: "Interval for timing out user sessions. Uses <a target=\"_blank\" href=\"http://www.postgresql.org/docs/9.0/static/functions-datetime.html\">PostgreSQL interval notation</a>"
         aliasTokenTimeout:
             kind: panel.input.text
             id: aliasTokenTimeout
@@ -617,7 +651,9 @@ themeManagement:
     action: /xapi/theme
     contents:
         themeScript:
-            tag: script|type=text/javascript;src=../../scripts/xnat/admin/themeManagement.js
+            tag: script
+            element:
+                src: ~/scripts/xnat/admin/themeManagement.js
         themeStyle:
             tag: style
             element:
@@ -1011,8 +1047,11 @@ fileSystem:
     contentType: json
     action: /xapi/siteConfig/batch
     load: ?? XNAT.data.siteConfig
-    after:
-        - "<script type=\"text/javascript\">$('#archivePath').attr('disabled', 'disabled');</script>"
+#    after:
+#        disableArchivePath:
+#            tag: script
+#            element:
+#                html: "$$('id=archivePath').attr('disabled', 'disabled')"
     contents:
         ${archivePath}
         ${cachePath}
@@ -1057,7 +1096,7 @@ misc:
                 link:
                     tag: a.link
                     element:
-                        href: */page/admin/spawner/
+                        href: ~/page/admin/spawner/
                         target: _blank
                         html: Manage The Spawner
         swagger:
@@ -1067,27 +1106,11 @@ misc:
                 link:
                     tag: a.link
                     element:
-                        href: */xapi/swagger-ui.html
+                        href: ~/xapi/swagger-ui.html
                         target: _blank
                         html: Swagger
-development:
-    kind: panel
-    name: development
-    label: Development Utilities
-    contents:
-        manageSpawner:
-            kind: panel.element
-            name: manageSpawner
-            label: Manage Spawner Elements
-            element:
-                html: >
-                    <a href="/page/admin/spawner">Manage Spawner Elements</a>
-
 
 
-
-            
-
 #################################################
 ####  Root Site Admin Spawner Config Object  ####
 #################################################
diff --git a/src/main/webapp/page/admin/spawner/spawner-admin.js b/src/main/webapp/page/admin/spawner/spawner-admin.js
index b9556b8ed925c278bc0481c243776e6d86b15152..b1feb5b87b151b58db2f5872a19f741404a8a830 100644
--- a/src/main/webapp/page/admin/spawner/spawner-admin.js
+++ b/src/main/webapp/page/admin/spawner/spawner-admin.js
@@ -129,12 +129,12 @@ XNAT.xhr.getJSON({
                                         url: elementUrl,
                                         data: obj.$modal.find('textarea.yaml').val(),
                                         contentType: 'text/x-yaml',
-                                        // processData:  false,
+                                        processData:  false,
                                         success: function(){
                                             xmodal.message('Data saved.')
                                         },
-                                        error: function(e){
-                                            xmodal.message('An error occured: ' + e)
+                                        error: function(e, f, g){
+                                            xmodal.message(['<b>Error:</b>', e, f, g].join('<br>'))
                                         }
                                     });
                                 }
diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css
index 2c1ae87e51252afebe1b0ce12a99ac02360d2031..b2f6ed2c5a2fd671c8ddd044639a1ef7612e3652 100644
--- a/src/main/webapp/page/admin/style.css
+++ b/src/main/webapp/page/admin/style.css
@@ -92,6 +92,8 @@ body.xnat .panel-default { border: 1px solid #c8c8c8; }
 .panel .panel-element span.after { margin-left: 10px; }
 .panel .panel-element label.small { font-weight: normal; }
 
+.panel .input-bundle { margin-top: 5px; }
+
 .panel .panel-subhead {
     padding: 2px; margin: 10px;
     font-size: 13px;
diff --git a/src/main/webapp/scripts/xnat/admin/siteInfo.js b/src/main/webapp/scripts/xnat/admin/siteInfo.js
index 10c0ede2fe4560605ef96616e12a2cd4e97b9682..070fe46d03d318ae06b89a8bb1d42c68b9ebc837 100644
--- a/src/main/webapp/scripts/xnat/admin/siteInfo.js
+++ b/src/main/webapp/scripts/xnat/admin/siteInfo.js
@@ -1,31 +1,45 @@
-var sdtPage, sdtText;
-setTimeout(function(){
-  sdtPage = $('#siteDescriptionTypePage');
-  sdtText = $('#siteDescriptionTypeText');
-  sdtPage.click(changeSiteDescriptionType);
-  sdtText.click(changeSiteDescriptionType);
-  changeSiteDescriptionType(XNAT.data.siteConfig.siteDescriptionType);
-}, 100);
-function changeSiteDescriptionType(eventOrValue){
-    var value = eventOrValue;
-    if(typeof eventOrValue === 'object'){
-      if(eventOrValue.target.id == "siteDescriptionTypeText"){
-        value = 'Text';
-      } else {
-        value = 'Page';
-      }
+// interractions with 'Site Info' section of admin ui
+(function(){
+
+    var sdtPage, sdtText;
+
+    setTimeout(function(){
+        sdtPage = $('#siteDescriptionTypePage');
+        sdtText = $('#siteDescriptionTypeText');
+        sdtPage.click(changeSiteDescriptionType);
+        sdtText.click(changeSiteDescriptionType);
+        changeSiteDescriptionType(XNAT.data.siteConfig.siteDescriptionType);
+    }, 100);
+
+    function changeSiteDescriptionType(eventOrValue){
+
+        var value = eventOrValue;
+
+        if (typeof eventOrValue === 'object') {
+            if (eventOrValue.target.id == "siteDescriptionTypeText") {
+                value = 'Text';
+            }
+            else {
+                value = 'Page';
+            }
+        }
+
+        sdtText.val(value);
+        sdtPage.val(value);
+
+        var text = $('div.input-bundle.text');
+        var page = $('div.input-bundle.page');
+
+        if (value == 'Text') {
+            sdtText.prop('checked', true);
+            text.show();
+            page.hide();
+        }
+        else {
+            sdtPage.prop('checked', true);
+            page.show();
+            text.hide();
+        }
     }
-    sdtText.val(value);
-    sdtPage.val(value);
-    var text = $('#siteDescriptionText').parent().parent();
-    var page = $('#siteDescriptionPage').parent().parent();
-    if(value == 'Text'){
-        sdtText.prop('checked',true);
-        text.show();
-        page.hide();
-    } else {
-        sdtPage.prop('checked',true);
-        page.show();
-        text.hide();
-    }
-};
\ No newline at end of file
+
+})();
diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
index 6918f517f609d071ac0ecf4d6ab035efbb7e91ed..dc4efb747ccd04985d9735b0d5d363c5e66ffdf3 100644
--- a/src/main/webapp/scripts/xnat/spawner.js
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -34,7 +34,7 @@ var XNAT = getObject(XNAT);
     spawner.notSpawned = [];
 
     function setRoot(url){
-        url = url.replace(/^([*.]\/+)/, '/');
+        url = url.replace(/^([*~.]\/+)/, '/');
         return XNAT.url.rootUrl(url)
     }
 
@@ -50,7 +50,7 @@ var XNAT = getObject(XNAT);
             var kind, methodName, method, spawnedElement, $spawnedElement;
             
             // save the config properties in a new object
-            prop = getObject(prop);
+            prop = cloneObject(prop);
 
             prop.config = prop.config || prop.element || {};
 
@@ -79,9 +79,14 @@ var XNAT = getObject(XNAT);
             // do a raw spawn() if 'kind' is 'element'
             // or if there's a tag property
             if (kind === 'element' || prop.tag || prop.config.tag) {
+
+                // pass 'content' (not contentS) property to add
+                // stuff directly to spawned element
+                prop.content = prop.content || prop.children || prop.inner || '';
+
                 try {
                     spawnedElement =
-                        spawn(prop.tag || prop.config.tag || 'div', prop.config);
+                        spawn(prop.tag || prop.config.tag || 'div', prop.config, prop.content);
                     // jQuery's .append() method is
                     // MUCH more robust and forgiving
                     // than element.appendChild()
@@ -141,8 +146,8 @@ var XNAT = getObject(XNAT);
             // spawn child elements from...
             // 'contents' or 'content' or 'children' or
             // a property matching the value of either 'contains' or 'kind'
-            if (prop.contains || prop.contents || prop.content || prop.children || prop[prop.kind]) {
-                prop.contents = prop[prop.contains] || prop.contents || prop.content || prop.children || prop[prop.kind];
+            if (prop.contains || prop.contents || prop[prop.kind]) {
+                prop.contents = prop[prop.contains] || prop.contents || prop[prop.kind];
                 // if there's a 'target' property, put contents in there
                 if (spawnedElement.target || spawnedElement.inner) {
                     $spawnedElement = $(spawnedElement.target || spawnedElement.inner);
@@ -150,19 +155,43 @@ var XNAT = getObject(XNAT);
                 else {
                     $spawnedElement = $(spawnedElement.element);
                 }
-                $spawnedElement.append(_spawn(prop.contents).get());
+
+                // if a string, number, or boolean is passed as 'contents'
+                // just append that as-is (as a string)
+                if (stringable(prop.contents)) {
+                    $spawnedElement.append(prop.contents+'');
+                }
+                else {
+                    $spawnedElement.append(_spawn(prop.contents).get());
+                }
             }
             
+            // Treat 'before' and 'after' just like 'contents'
+            // but insert the items 'before' or 'after' the main
+            // spawned (outer) element. This may have unintended
+            // consequences depending on the HTML structure of the
+            // spawned widget that has things 'before' or 'after' it.
+
             if (prop.after) {
-                $frag.append(prop.after)
+                if (stringable(prop.after) || Array.isArray(prop.after)) {
+                    $frag.append(prop.after)
+                }
+                else if (isPlainObject(prop.after)) {
+                    $frag.append(_spawn(prop.after).get())
+                }
             }
-            
+
             if (prop.before) {
-                $frag.prepend(prop.before)
+                if (stringable(prop.before) || Array.isArray(prop.before)) {
+                    $frag.prepend(prop.before)
+                }
+                else if (isPlainObject(prop.before)) {
+                    $frag.prepend(_spawn(prop.before).get())
+                }
             }
-            
+
             // if there's a .load() method, fire that
-            if (isFunction(spawnedElement.load)) {
+            if (isFunction(spawnedElement.load||null)) {
                 spawnedElement.load();
             }
 
diff --git a/src/main/webapp/style/app.css b/src/main/webapp/style/app.css
index ee76cc001dcf71ffac5478b61acb9eca2dbdcaeb..5dc84a0f99fa7620b895d41badee3e6560811af0 100644
--- a/src/main/webapp/style/app.css
+++ b/src/main/webapp/style/app.css
@@ -3568,3 +3568,13 @@ textarea.xml-formatted {
     display: block; padding: 10px; margin: 10px; width: 95%; height: 95%;
     overflow-x: scroll; overflow-y: scroll; border: 1px solid #ccc;
 }
+
+/* utility classes */
+.pad1 { padding: 1px; }
+.pad5 { padding: 5px; }
+.pad10 { padding: 10px; }
+.pad20 { padding: 20px; }
+.pad1h { padding-left: 1px; padding-right: 1px; }
+.pad5h { padding-left: 5px; padding-right: 5px; }
+.pad10h { padding-left: 10px; padding-right: 10px; }
+.pad20h { padding-left: 20px; padding-right: 20px; }