diff --git a/delegator/delegator.admin.inc b/delegator/delegator.admin.inc
index 30c3e70911b765c89021913b905814f997a16c40..9a9f294609b830ce74af523b4247bec7a96a764f 100644
--- a/delegator/delegator.admin.inc
+++ b/delegator/delegator.admin.inc
@@ -23,13 +23,17 @@ define('DGA_CHANGED_DELETED', 0x04);
 
 /**
  * Page callback to administer a particular task.
- *
- * Since not all tasks actually have subtask ids, the subtask id is
- * optional. However, the menu system should ensure that both of these
- * arguments are filled so that additional url sections do not end up
- * appearing as subtask ids.
  */
-function delegator_administer_task($task_id, $subtask_id = '') {
+function delegator_administer_task($task_name) {
+  // Determine if the task id came in the form of TASK-SUBTASK or just TASK
+  if (strpos($task_name, '-') !== FALSE) {
+    list($task_id, $subtask_id) = explode('-', $task_name, 2);
+  }
+  else {
+    $task_id = $task_name;
+    $subtask_id = NULL;
+  }
+
   $task = delegator_get_task($task_id);
   if (!$task) {
     return drupal_not_found();
@@ -585,7 +589,16 @@ function delegator_admin_list_form_config($form, &$form_state) {
 /**
  * Entry point to edit a task handler.
  */
-function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $form_id) {
+function delegator_administer_task_handler_edit($task_name, $handler_id, $name, $form_id) {
+  // Determine if the task id came in the form of TASK-SUBTASK or just TASK
+  if (strpos($task_name, '-') !== FALSE) {
+    list($task_id, $subtask_id) = explode('-', $task_name, 2);
+  }
+  else {
+    $task_id = $task_name;
+    $subtask_id = NULL;
+  }
+
   $handler = delegator_admin_get_task_handler_cache($name);
   if (!$handler) {
     $handler = delegator_load_task_handler($name);
@@ -608,9 +621,6 @@ function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $f
     return drupal_not_found();
   }
 
-  // @todo FIXME
-  $subtask_id = '';
-
   $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id);
   drupal_set_title(t('Edit task handler "@title"', array('@title' => $title)));
 
@@ -638,20 +648,35 @@ function delegator_administer_task_handler_edit($task_id, $handler_id, $name, $f
   );
 
   if (!empty($plugin['forms'][$form_id]['alternate next'])) {
-    $form_state['next'] = "admin/build/delegator/$task_id/$handler_id/$name/" . $plugin['forms'][$form_id]['alternate next'];
+    $form_state['next'] = "admin/build/delegator/$task_name/$handler_id/$name/" . $plugin['forms'][$form_id]['alternate next'];
   }
   elseif ($next_info) {
-    $form_state['next'] = "admin/build/delegator/$task_id/$handler_id/$name/$next_info[key]";
+    $form_state['next'] = "admin/build/delegator/$task_name/$handler_id/$name/$next_info[key]";
   }
 
   ctools_include('form');
-  return ctools_build_form('delegator_admin_edit_task_handler', $form_state);
+  $output = ctools_build_form('delegator_admin_edit_task_handler', $form_state);
+  if ($output && !empty($plugin['forms'][$form_id]['no blocks'])) {
+    print theme('page', $output, FALSE);
+  }
+  else {
+    return $output;
+  }
 }
 
 /**
  * Entry point to add a task handler.
  */
 function delegator_administer_task_handler_add($task_id, $name, $form_id) {
+  // Determine if the task id came in the form of TASK-SUBTASK or just TASK
+  if (strpos($task_name, '-') !== FALSE) {
+    list($task_id, $subtask_id) = explode('-', $task_name, 2);
+  }
+  else {
+    $task_id = $task_name;
+    $subtask_id = NULL;
+  }
+
   $handler = delegator_admin_get_task_handler_cache($name);
 
   if (!$handler) {
@@ -670,9 +695,6 @@ function delegator_administer_task_handler_add($task_id, $name, $form_id) {
     return drupal_not_found();
   }
 
-  // @todo FIXME
-  $subtask_id = '';
-
   $title = delegator_get_handler_title($plugin, $handler, $task, $subtask_id);
   drupal_set_title(t('Add task handler "@title"', array('@title' => $title)));
 
@@ -702,7 +724,7 @@ function delegator_administer_task_handler_add($task_id, $name, $form_id) {
     else {
       $class = 'delegator-next';
       if (!isset($form_state['next'])) {
-        $form_state['next'] = "admin/build/delegator/$task_id/add/$name/$id";
+        $form_state['next'] = "admin/build/delegator/$task_name/add/$name/$id";
       }
     }
     $crumbs[] = '<span class="' . $class . '">' . $title . '</span>';
diff --git a/delegator/delegator.module b/delegator/delegator.module
index 56600afa48dd6610ef14b64616d3ca70cfd386ce..64f3abdb80ea00fa88be4e0ca5d7ff761c675db6 100644
--- a/delegator/delegator.module
+++ b/delegator/delegator.module
@@ -72,6 +72,8 @@ function delegator_menu() {
     $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access';
     $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator');
 
+    // @todo -- support subtasks
+
     if (isset($task['admin title'])) {
       $items['admin/build/delegator/' . $id] = array(
         'title' => $task['admin title'],
@@ -226,8 +228,13 @@ function _delegator_sort_task_handlers($a, $b) {
 /**
  * Write a task handler to the database.
  */
-function delegator_save_task_handler($handler) {
+function delegator_save_task_handler(&$handler) {
   $update = (isset($handler->did)) ? array('did') : array();
+  // Let the task handler respond to saves:
+  if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'save')) {
+    $function($handler, $update);
+  }
+
   drupal_write_record('delegator_handlers', $handler, $update);
   return $handler;
 }
@@ -306,7 +313,7 @@ function delegator_get_handler_title($plugin, $handler, $task, $subtask_id) {
 }
 
 /**
- * Implementation of hook_ctools_plugin_directory() to let the system know
+ * Implementation of hook_ctools_plugin_dierctory() to let the system know
  * we implement task and task_handler plugins.
  */
 function delegator_ctools_plugin_directory($plugin) {
diff --git a/delegator/help/api-task-handler.html b/delegator/help/api-task-handler.html
index 0370c0d8cbee4295469a9b5aa92aba9a7d0a16a7..6904079bacb28a57ef4d7a48a2f62141fc741d58 100644
--- a/delegator/help/api-task-handler.html
+++ b/delegator/help/api-task-handler.html
@@ -8,6 +8,7 @@ task handler definition:
     params: $handler, $task, $subtask_id
   default conf -- either an array() of default conf data or a callback that returns an array.
     params: $handler, $task, $subtask_id
+  save -- callback to call just prior to the task handler being saved so it can adjust its data.
 
   forms => array(
     'id' => array(
@@ -17,6 +18,7 @@ task handler definition:
        'include' => an optional file to include to get functionality for this form. Must include full path.
        'no return' => hide the 'return' button, meaning that the form requires extra steps if submitted
        'alternate next' => an alternate next form. Used for hidden edit forms that don't have tabs.
+       'no blocks' => if TRUE, use Drupal's mechanism to not render blocks for this form.
      )
    )
   ),
diff --git a/delegator/help/api-task.html b/delegator/help/api-task.html
index a9b1eb3a4a220624d3c6394bee77048d97862592..50e3a81aab096ed812347cbddf64df7ced29b43b 100644
--- a/delegator/help/api-task.html
+++ b/delegator/help/api-task.html
@@ -9,3 +9,5 @@ task definition:
   type -- The type of the task, used to determine which handlers can service it.
   subtasks -- can be TRUE in which case it supports subtasks with the default
     configuration or a string (array?) with callbacks to fetch subtask data.
+
+task names must not contain a - as that is used to separate the task name from the subtask ID.
\ No newline at end of file
diff --git a/delegator/plugins/tasks/node_view.inc b/delegator/plugins/tasks/node_view.inc
index f2a7ce8d7c7917c6d5dc0719f4957b57812cc221..380081e5cfd0fd4bd33e55b0f1d5d94d1283343d 100644
--- a/delegator/plugins/tasks/node_view.inc
+++ b/delegator/plugins/tasks/node_view.inc
@@ -50,6 +50,12 @@ function delegator_node_view($node) {
     if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) {
       $output = $function($handler, $node);
       if ($output) {
+        // TRUE is a special return used to let us know that it handled the
+        // task but does not wish us to render anything, as it already did.
+        // This is needed for the 'no blocks' functionality.
+        if ($output === TRUE) {
+          return;
+        }
         return $output;
       }
     }
diff --git a/includes/css.inc b/includes/css.inc
new file mode 100644
index 0000000000000000000000000000000000000000..39cceac0a7cf0a5fc87eeb9d6683b557205ca08b
--- /dev/null
+++ b/includes/css.inc
@@ -0,0 +1,440 @@
+<?php
+/* $Id$ */
+// Original author: Dmitri Gaskin
+// Most of the rest of the work: Sam Boyer
+
+/*
+ * @file
+ * CSS filtering functions. Contains a disassembler, filter, compressor, and
+ * decompressor.
+ *
+ * The general usage of this tool is:
+ *
+ * To simply filter CSS:
+ * @code
+ *   $filtered_css = ctools_css_filter($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is TRUE, the returned CSS will
+ * be compressed. Otherwise it will be returned in a well formatted
+ * syntax.
+ *
+ * To cache unfiltered CSS in a file, which will be filtered:
+ *
+ * @code
+ *   $filename = ctools_css_cache($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is FALSE, the CSS will not be filtered.
+ *
+ * This file will be cached within the Drupal files system. This system cannot
+ * detect when this file changes, so it is YOUR responsibility to remove and
+ * re-cache this file when the CSS is changed. Your system should also contain
+ * a backup method of re-generating the CSS cache in case it is removed, so
+ * that it is easy to force a re-cache by simply deleting the contents of the
+ * directory.
+ */
+
+/**
+ * Write a chunk of CSS to a temporary cache file and return the file name.
+ *
+ * This function optionally filters the CSS (always compressed, if so) and
+ * generates a unique filename based upon md5. It returns that filename that
+ * can be used with drupal_add_css. Note that as a cache file, technically
+ * this file is volatile so it should be checked before it is used to ensure
+ * that it exists.
+ *
+ * You can use file_exists() to test for the file and file_delete() to remove
+ * it if it needs to be cleared.
+ *
+ * @param $css
+ *   A chunk of well-formed CSS text to cache.
+ * @param $filter
+ *   If TRUE the css will be filtered. If FALSE the text will be cached
+ *   as-is.
+ */
+function ctools_css_cache($css, $filter = TRUE) {
+  if ($filter) {
+    $css = ctools_css_filter($css);
+  }
+
+  // Create the css/ within the files folder.
+  $path = file_create_path('ctools/css');
+  if (!$path) {
+    $path = file_directory_path() . '/ctools';
+    file_check_directory($path, FILE_CREATE_DIRECTORY);
+    $path .= '/css';
+    file_check_directory($path, FILE_CREATE_DIRECTORY);
+  }
+
+  // @todo Is this slow? DOes it matter if it is?
+  $filename = $path . '/' . md5($css) . '.css';
+
+  // This will do renames if the file already exists, ensuring we don't
+  // accidentally overwrite other files who share the same md5. Yes this
+  // is a very miniscule chance but it's safe.
+  $filename = file_save_data($css, $filename);
+
+  return $filename;
+}
+
+/**
+ * Filter a chunk of CSS text.
+ *
+ * This function disassembles the CSS into a raw format that makes it easier
+ * for our tool to work, then runs it through the filter and reassembles it.
+ * If you find that you want the raw data for some reason or another, you
+ * can use the disassemble/assemble functions yourself.
+ *
+ * @param $css
+ *   The CSS text to filter.
+ * @param $compressed
+ *   If true, generate compressed output; if false, generate pretty output.
+ *   Defaults to TRUE.
+ */
+function ctools_css_filter($css, $compressed = TRUE) {
+  $css_data = ctools_css_disassemble($css);
+
+  // Note: By using this function yourself you can control the allowed
+  // properties and values list.
+  $filtered = ctools_css_filter_css_data($css_data);
+
+  return $compressed ? ctools_css_compress($filtered) : ctools_css_assemble($filtered);
+}
+
+/**
+ * Re-assemble a css string and format it nicely.
+ *
+ * @param array $css_data
+ *   An array of css data, as produced by @see ctools_css_disassemble()
+ *   disassembler and the @see ctools_css_filter_css_data() filter.
+ * @return string $css
+ *   css optimized for human viewing.
+ */
+function ctools_css_assemble($css_data) {
+  // Initialize the output.
+  $css = '';
+  // Iterate through all the statements.
+  foreach ($css_data as $selector_str => $declaration) {
+    // Add the selectors, separating them with commas and line feeds.
+    $css .= strpos($selector_str, ',') === FALSE ? $selector_str : preg_replace(", ", ",\n", $selector_str);
+    // Add the opening curly brace.
+    $css .= " {\n";
+    // Iterate through all the declarations.
+    foreach ($declaration as $property => $value) {
+      $css .= "  " . $property . ": " . $value . ";\n";
+    }
+    // Add the closing curly brace.
+    $css .= "}\n\n";
+  }
+  // Return the output.
+  return $css;
+}
+
+/**
+ * Compress css data (filter it first!) to optimize for use on view.
+ *
+ * @param array $css_data
+ *   An array of css data, as produced by @see ctools_css_disassemble()
+ *   disassembler and the @see ctools_css_filter_css_data() filter.
+ * @return string $css
+ *   css optimized for use.
+ */
+function ctools_css_compress($css_data) {
+  // Initialize the output.
+  $css = '';
+  // Iterate through all the statements.
+  foreach ($css_data as $selector_str => $declaration) {
+    if (empty($declaration)) {
+      // Skip this statement if filtering removed all parts of the declaration.
+      continue;
+    }
+    // Add the selectors, separating them with commas.
+    $css .= $selector_str;
+    // And, the opening curly brace.
+    $css .= "{";
+    // Iterate through all the statement properties.
+    foreach ($declaration as $property => $value) {
+      $css .= $property . ':' . $value . ';';
+    }
+    // Add the closing curly brace.
+    $css .= "}";
+  }
+  // Return the output.
+  return $css;
+}
+
+/**
+ * Disassemble the css string.
+ *
+ * Strip the css of irrelevant characters, invalid/malformed selectors and
+ * declarations, and otherwise prepare it for processing.
+ *
+ * @param string $css
+ *   A string containing the css to be disassembled.
+ * @return array $disassembled_css
+ *   An array of disassembled, slightly cleaned-up/formatted css statements.
+ */
+function ctools_css_disassemble($css) {
+  $disassembled_css = array();
+  // Remove comments.
+  $css = preg_replace("/\/\*(.*)?\*\//Usi", "", $css);
+  // Split out each statement
+  $statements = explode("}", $css);
+  // If we have any statements, parse them.
+  if (!empty($statements)) {
+    // Iterate through all of the statements.
+    foreach ($statements as $statement) {
+      // Get the selector(s) and declaration.
+      if (empty($statement) || !strpos($statement, '{')) {
+        continue;
+      }
+
+      list($selector_str, $declaration) = explode('{', $statement);
+
+      // If the selector exists, then disassemble it, check it, and regenerate
+      // the selector string.
+      $selector_str = empty($selector_str) ? FALSE : _ctools_css_disassemble_selector($selector_str);
+      if (empty($selector_str)) {
+        // No valid selectors. Bomb out and start the next item.
+        continue;
+      }
+
+      // Disassemble the declaration, check it and tuck it into an array.
+      $disassembled_css[$selector_str] = _ctools_css_disassemble_declaration(strtolower($declaration));
+    }
+  }
+  return $disassembled_css;
+}
+
+function _ctools_css_disassemble_selector($selector_str) {
+  // Get all selectors individually.
+  $selectors = explode(",", trim($selector_str));
+  // Iterate through all the selectors, sanity check them and return if they
+  // pass. Note that this handles 0, 1, or more valid selectors gracefully.
+  foreach ($selectors as $key => $selector) {
+    // Replace un-needed characters and do a little cleanup.
+    $selector = preg_replace("/[\n|\t|\\|\s]+/", ' ', strtolower(trim($selector)));
+    // Make sure this is still a real selector after cleanup.
+    if (!empty($selector)) {
+      $selectors[$key] = $selector;
+    }
+    else {
+      // Selector is no good, so we scrap it.
+      unset ($selectors[$key]);
+    }
+  }
+    // Check for malformed selectors; if found, we skip this declaration.
+  if (empty($selectors)) {
+    return FALSE;
+  }
+  return implode(', ', $selectors);
+}
+
+
+function _ctools_css_disassemble_declaration($declaration) {
+  $formatted_statement = array();
+  $propval_pairs = explode(";", $declaration);
+  // Make sure we actually have some properties to work with.
+  if (!empty($propval_pairs)) {
+    // Iterate through the remains and parse them.
+    foreach ($propval_pairs as $key => $propval_pair) {
+      // Check that we have a ':', otherwise it's an invalid pair.
+      if (strpos($propval_pair, ':') === FALSE) {
+        continue;
+      }
+      // Clean up the current property-value pair.
+      $propval_pair =  preg_replace("/[\n|\t|\\|\s]+/", ' ', strtolower(trim($propval_pair)));
+      // Explode the remaining fragements some more, but clean them up first.
+      list($property, $value) = explode(':', $propval_pair);
+      // If the property survived, toss it onto the stack.
+      if(!empty($property)) {
+        $formatted_statement[trim($property)] = trim($value);
+      }
+    }
+  }
+  return $formatted_statement;
+}
+
+/**
+ * Run disassembled $css through the filter.
+ *
+ * @param $css
+ *   CSS code disassembled by ctools_dss_disassemble().
+ * @param $allowed_properties
+ *   A list of properties that are allowed by the filter. If empty
+ *   ctools_css_filter_default_allowed_properties() will provide the
+ *   list.
+ * @param $allowed_values
+ *   A list of values that are allowed by the filter. If empty
+ *   ctools_css_filter_default_allowed_values() will provide the
+ *   list.
+ *
+ * @return
+ *   An array of disassembled, filtered CSS.
+ */
+function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+//  function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+  // Retrieve the default list of allowed properties if none is provided.
+  $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties();
+  // Retrieve the default list of allowed values if none is provided.
+  $allowed_values = !empty($allowed_values) ? $allowed_values : ctools_css_filter_default_allowed_values();
+  // Define allowed values regex if none is provided.
+  $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/';
+  // Define disallowed url() value contents, if none is provided.
+  // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/';
+  $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/';
+
+  foreach ($css as $selector_str => $declaration) {
+    foreach ($declaration as $property => $value) {
+      if (!in_array($property, $allowed_properties)) {
+        // $filtered['properties'][$selector_str][$property] = $value;
+        unset($css[$selector_str][$property]);
+        continue;
+      }
+      $value = str_replace('!important', '', $value);
+      if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) {
+        // $filtered['values'][$selector_str][$property] = $value;
+        unset($css[$selector_str][$property]);
+        continue;
+      }
+    }
+  }
+  return $css;
+}
+
+/**
+ * Provide a deafult list of allowed properties by the filter.
+ */
+function ctools_css_filter_default_allowed_properties() {
+  return array(
+    'azimuth',
+    'background',
+    'background-color',
+    'background-image',
+    'background-repeat',
+    'background-attachment',
+    'background-position',
+    'border',
+    'border-top-width',
+    'border-right-width',
+    'border-bottom-width',
+    'border-left-width',
+    'border-width',
+    'border-top-color',
+    'border-right-color',
+    'border-bottom-color',
+    'border-left-color',
+    'border-color',
+    'border-top-style',
+    'border-right-style',
+    'border-bottom-style',
+    'border-left-style',
+    'border-style',
+    'border-top',
+    'border-right',
+    'border-bottom',
+    'border-left',
+    'clear',
+    'color',
+    'cursor',
+    'direction',
+    'display',
+    'elevation',
+    'float',
+    'font',
+    'font-family',
+    'font-size',
+    'font-style',
+    'font-variant',
+    'font-weight',
+    'height',
+    'letter-spacing',
+    'line-height',
+    'margin',
+    'margin-top',
+    'margin-right',
+    'margin-bottom',
+    'margin-left',
+    'overflow',
+    'padding',
+    'padding-top',
+    'padding-right',
+    'padding-bottom',
+    'padding-left',
+    'pause',
+    'pause-after',
+    'pause-before',
+    'pitch',
+    'pitch-range',
+    'richness',
+    'speak',
+    'speak-header',
+    'speak-numeral',
+    'speak-punctuation',
+    'speech-rate',
+    'stress',
+    'text-align',
+    'text-decoration',
+    'text-indent',
+    'unicode-bidi',
+    'vertical-align',
+    'voice-family',
+    'volume',
+    'white-space',
+    'width',
+    'fill',
+    'fill-opacity',
+    'fill-rule',
+    'stroke',
+    'stroke-width',
+    'stroke-linecap',
+    'stroke-linejoin',
+    'stroke-opacity',
+  );
+}
+
+/**
+ * Provide a default list of allowed values by the filter.
+ */
+function ctools_css_filter_default_allowed_values() {
+  return array(
+    'auto',
+    'aqua',
+    'black',
+    'block',
+    'blue',
+    'bold',
+    'both',
+    'bottom',
+    'brown',
+    'center',
+    'collapse',
+    'dashed',
+    'dotted',
+    'fuchsia',
+    'gray',
+    'green',
+    'italic',
+    'left',
+    'lime',
+    'maroon',
+    'medium',
+    'navy',
+    'normal',
+    'nowrap',
+    'olive',
+    'pointer',
+    'purple',
+    'red',
+    'right',
+    'solid',
+    'silver',
+    'teal',
+    'top',
+    'transparent',
+    'underline',
+    'white',
+    'yellow',
+  );
+}
diff --git a/includes/form.inc b/includes/form.inc
index 479999a6f58672361faac8e322bd7f2280ccf5cf..48be9f141d1650b00e472c19fac3cbf26167e4d2 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -3,8 +3,15 @@
 
 /**
  * @file
- * ctools' replacements for Drupal's form functions.
+ * CTools' replacements for Drupal's form functions.
  *
+ * Primarily, ctools_build_form is meant to be a replacement for drupal_get_form().
+ *
+ * Instead of sending arguments through to the form builder, a form state array
+ * is passed through. This form_state can contain any arguments needed, and it can
+ * also be used to return data to the calling function.
+ *
+ * This can allow cleaner separation of the form from the storage mechanism.
  */
 
 /**