diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php
index 115ab89d08455c7a825d01d6f440adfa79aca97e..2b9a24e6d4241df5fd07d941f9d9582a28da9859 100644
--- a/include/staff/dynamic-form.inc.php
+++ b/include/staff/dynamic-form.inc.php
@@ -4,21 +4,24 @@ $info=array();
 if($form && $_REQUEST['a']!='add') {
     $title = 'Update custom form section';
     $action = 'update';
+    $url = "?id=".urlencode($_REQUEST['id']);
     $submit_text='Save Changes';
     $info = $form->ht;
     $newcount=2;
 } else {
     $title = 'Add new custom form section';
     $action = 'add';
+    $url = '?a=add';
     $submit_text='Add Form';
     $newcount=4;
 }
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 
 ?>
-<form id="manage-form" action="?id=<?php echo urlencode($_REQUEST['id']); ?>" method="post">
+<form class="manage-form" action="<?php echo $url ?>" method="post" id="save">
     <?php csrf_token(); ?>
     <input type="hidden" name="do" value="<?php echo $action; ?>">
+    <input type="hidden" name="a" value="<?php echo $action; ?>">
     <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
     <h2>Custom Form</h2>
     <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
@@ -263,7 +266,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 </div>
 
 <script type="text/javascript">
-$('#manage-form').on('submit.inline', function() {
+$('form.manage-form').on('submit.inline', function() {
     var formObj = this, deleted = $('input.delete-box:checked', this);
     if (deleted.length) {
         $('#overlay').show();
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index dcea698c45c229134aac1459b87c9c936e8b4402..931d7fb46511bdfc2b99a5c0e0b7efaef52e6a36 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -22,8 +22,8 @@
     <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/redactor-fonts.js"></script>
     <script type="text/javascript" src="./js/bootstrap-typeahead.js"></script>
     <script type="text/javascript" src="./js/scp.js"></script>
-    <link rel="stylesheet" href="<?php echo ROOT_PATH ?>css/thread.css" media="screen">
-    <link rel="stylesheet" href="./css/scp.css" media="screen">
+    <link rel="stylesheet" href="<?php echo ROOT_PATH ?>css/thread.css" media="all">
+    <link rel="stylesheet" href="./css/scp.css" media="all">
     <link rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/redactor.css" media="screen">
     <link rel="stylesheet" href="./css/typeahead.css" media="screen">
     <link type="text/css" href="<?php echo ROOT_PATH; ?>css/ui-lightness/jquery-ui-1.10.3.custom.min.css"
diff --git a/scp/forms.php b/scp/forms.php
index 18303449ea47b4be66e13baa7db94a79b3299a77..1df21d3693c8947dec353559e1823a563e60d62a 100644
--- a/scp/forms.php
+++ b/scp/forms.php
@@ -10,6 +10,7 @@ if($_POST) {
     $fields = array('title', 'notes', 'instructions');
     $required = array('title');
     $max_sort = 0;
+    $form_fields = array();
     switch(strtolower($_POST['do'])) {
         case 'update':
             foreach ($fields as $f)
@@ -53,7 +54,7 @@ if($_POST) {
                 if ($field->get('name'))
                     $names[] = $field->get('name');
                 if ($field->isValid())
-                    $field->save();
+                    $form_fields[] = $field;
                 else
                     # notrans (not shown)
                     $errors["field-$id"] = 'Field has validation errors';
@@ -62,11 +63,14 @@ if($_POST) {
             }
             break;
         case 'add':
-            $form = DynamicForm::create(array(
-                'title'=>$_POST['title'],
-                'instructions'=>$_POST['instructions'],
-                'notes'=>$_POST['notes']));
-            $form->save(true);
+            $form = DynamicForm::create();
+            foreach ($fields as $f) {
+                if (in_array($f, $required) && !$_POST[$f])
+                    $errors[$f] = sprintf('%s is required',
+                        mb_convert_case($f, MB_CASE_TITLE));
+                elseif (isset($_POST[$f]))
+                    $form->set($f, $_POST[$f]);
+            }
             break;
 
         case 'mass_process':
@@ -98,7 +102,6 @@ if($_POST) {
             if (!$_POST["label-new-$i"])
                 continue;
             $field = DynamicFormField::create(array(
-                'form_id'=>$form->get('id'),
                 'sort'=>$_POST["sort-new-$i"] ? $_POST["sort-new-$i"] : ++$max_sort,
                 'label'=>$_POST["label-new-$i"],
                 'type'=>$_POST["type-new-$i"],
@@ -108,13 +111,19 @@ if($_POST) {
             ));
             $field->setForm($form);
             if ($field->isValid())
-                $field->save();
+                $form_fields[] = $field;
             else
                 $errors["new-$i"] = $field->errors();
         }
         // XXX: Move to an instrumented list that can handle this better
-        if (!$errors)
+        if (!$errors) {
             $form->_dfields = $form->_fields = null;
+            $form->save(true);
+            foreach ($form_fields as $field) {
+                $field->set('form_id', $form->get('id'));
+                $field->save();
+            }
+        }
     }
     if ($errors)
         $errors['err'] = 'Unable to commit form. Check validation errors';
diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php
index 9ddde333f885193258f5d1b4eba1175d8ee1f0e8..06d1a20428a9d012c0544d01e6c539cc900f163a 100644
--- a/setup/cli/modules/deploy.php
+++ b/setup/cli/modules/deploy.php
@@ -87,6 +87,46 @@ class Deployment extends Unpacker {
         }
     }
 
+    function copyFile($src, $dest) {
+        static $short = false;
+        static $version = false;
+
+        if (substr($src, -4) != '.php')
+            return parent::copyFile($src, $dest);
+
+        if (!$short) {
+            $hash = exec('git rev-parse HEAD');
+            $short = substr($hash, 0, 7);
+        }
+
+        if (!$version)
+            $version = exec('git describe');
+
+        if (!$short || !$version)
+            return parent::copyFile($src, $dest);
+
+        $source = file_get_contents($src);
+        $source = preg_replace(':<script(.*) src="(.*).js"></script>:',
+            '<script$1 src="$2.js?'.$short.'"></script>',
+            $source);
+        $source = preg_replace(':<link(.*) href="(.*).css"([^/>]*)/?>:', # <?php
+            '<link$1 href="$2.css?'.$short.'"$3/>',
+            $source);
+        // Set THIS_VERSION
+        $source = preg_replace("/^(\s*)define\s*\(\s*'THIS_VERSION'.*$/m",
+            "$1define('THIS_VERSION', '".$version."'); // Set by installer",
+            $source);
+        // Disable error display
+        $source = preg_replace("/^(\s*)ini_set\s*\(\s*'(display_errors|display_startup_errors)'.*$/m",
+            "$1ini_set('$2', '0'); // Set by installer",
+            $source);
+
+        if (!file_put_contents($dest, $source))
+            die("Unable to apply rewrite rules to ".$dest);
+
+        return true;
+    }
+
     function run($args, $options) {
         $this->destination = $args['install-path'];
         if (!is_dir($this->destination))
@@ -122,7 +162,6 @@ class Deployment extends Unpacker {
         if (!$options['dry-run']) {
             if ($include != "{$this->destination}/include/")
                 $this->change_include_dir($include);
-            $this->touch_version();
         }
 
         if ($options['clean']) {
@@ -134,30 +173,6 @@ class Deployment extends Unpacker {
                 "*/.htaccess"));
         }
     }
-
-    function touch_version($version=false) {
-        if (!$version)
-            $version = exec('git describe');
-        if (!$version)
-            return false;
-
-        $bootstrap_php = $this->destination . '/bootstrap.php';
-        $lines = explode("\n", file_get_contents($bootstrap_php));
-        # Find the line that defines INCLUDE_DIR
-        $match = array();
-        foreach ($lines as &$line) {
-            // TODO: Change THIS_VERSION inline to be current `git describe`
-            if (preg_match("/(\s*)define\s*\(\s*'THIS_VERSION'/", $line, $match)) {
-                # Replace the definition with the new locatin
-                $line = $match[1] . "define('THIS_VERSION', '"
-                    . $version
-                    . "'); // Set by installer";
-                break;
-            }
-        }
-        if (!file_put_contents($bootstrap_php, implode("\n", $lines)))
-            die("Unable to write version information to bootstrap.php\n");
-    }
 }
 
 Module::register('deploy', 'Deployment');
diff --git a/setup/cli/modules/unpack.php b/setup/cli/modules/unpack.php
index f200538ff5dcc5c911f8410e769b5aecfd6d01ef..ecc6823d85bcd00b9314d477c61d1b434795617f 100644
--- a/setup/cli/modules/unpack.php
+++ b/setup/cli/modules/unpack.php
@@ -88,6 +88,10 @@ class Unpacker extends Module {
         return false;
     }
 
+    function copyFile($src, $dest) {
+        return copy($src, $dest);
+    }
+
     /**
      * Copy from source to desination, perhaps recursing up to n folders.
      * Exclusions are also permitted. If any files match an MD5 sum, they
@@ -120,7 +124,7 @@ class Unpacker extends Module {
                 if ($verbose)
                     $this->stdout->write($target."\n");
                 if (!$dryrun)
-                    copy($file, $target);
+                    $this->copyFile($file, $target);
             }
         }
         if ($recurse) {
diff --git a/setup/cli/package.php b/setup/cli/package.php
index 10fa537644dde35388e29a488aa19961dbdcf68e..6a1c68e6afd72508111ecff90ec410a0e62ff3a4 100755
--- a/setup/cli/package.php
+++ b/setup/cli/package.php
@@ -134,6 +134,8 @@ if(($mds = glob("$stage_path/*.md"))) {
 
 # Make an archive of the stage folder
 $version = exec('git describe');
+$hash = exec('git rev-parse HEAD');
+$short = substr($hash, 0, 7);
 
 $pwd = getcwd();
 chdir($stage_path);
@@ -143,6 +145,10 @@ chdir($stage_path);
 shell_exec("sed -ri -e \"
     s/( *)define\('THIS_VERSION'.*/\\1define('THIS_VERSION', '$version');/
     \" upload/bootstrap.php");
+shell_exec("find upload -name '*.php' -print0 | xargs -0 sed -i -e '
+    s:<script\(.*\) src=\"\(.*\).js\"></script>:<script\\1 src=\"\\2.js?$short\"></script>:
+    s:<link\(.*\) href=\"\(.*\).css\"\(.*\)*/*>:<link\\1 href=\"\\2.css?$short\"\\3>:
+   '");
 shell_exec("find upload -name '*.php' -print0 | xargs -0 sed -i -e \"
     s/\( *\)ini_set( *'display_errors'[^])]*);/\\1ini_set('display_errors', 0);/
     s/\( *\)ini_set( *'display_startup_errors'[^])]*);/\\1ini_set('display_startup_errors', 0);/