diff --git a/ctools.module b/ctools.module
index dc46173457c363d60cd841362c7f471f8c99419c..92315a64edcecee516aa4284aa77ba9fce7574f7 100644
--- a/ctools.module
+++ b/ctools.module
@@ -106,3 +106,76 @@ function ctools_get_roles() {
 
   return $roles;
 }
+
+/**
+ * Determine if the current user has access via a plugin.
+ *
+ * This function is meant to be embedded in the Drupal menu system, and
+ * therefore is in the .module file since sub files can't be loaded, and
+ * takes arguments a little bit more haphazardly than ctools_access().
+ *
+ * @param $plugin_name
+ *   The access plugin to use. If empty or 'none' then no access control
+ *   is being used and this function returns TRUE. If the plugin can't be
+ *   found otherwise, this function automatically returns FALSE.
+ * @param $settings
+ *   An array of settings theoretically set by the user.
+ * @param ...
+ *   zero or more context arguments generated from argument plugins. These
+ *   contexts must have an 'id' attached to them so that they can be
+ *   properly associated. The argument plugin system should set this, but
+ *   if the context is coming from elsewhere it will need to be set manually.
+ *
+ * @return
+ *   TRUE if access is granted, false if otherwise.
+ */
+function ctools_access_menu($plugin_name, $settings) {
+  $contexts = array();
+  foreach (func_get_args() as $arg) {
+    if (is_object($arg) && get_class($arg) == 'ctools_context') {
+      $contexts[$arg->id] = $arg;
+    }
+  }
+
+  global $user;
+  return ctools_access($plugin_name, $settings, $contexts, $user);
+}
+
+/**
+ * Determine if the current user has access via  plugin.
+ *
+ * @param $plugin_name
+ *   The access plugin to use. If empty or 'none' then no access control
+ *   is being used and this function returns TRUE. If the plugin can't be
+ *   found otherwise, this function automatically returns FALSE.
+ * @param $settings
+ *   An array of settings theoretically set by the user.
+ * @param $contexts
+ *   An array of zero or more contexts that may be used to determine if
+ *   the user has access.
+ * @param $account
+ *   The account to test against. If NULL the logged in user will be tested.
+ *
+ * @return
+ *   TRUE if access is granted, false if otherwise.
+ */
+function ctools_access($plugin_name, $settings, $contexts = array(), $account = NULL) {
+  if (!$account) {
+    global $user;
+    $account = $user;
+  }
+
+  if (empty($plugin_name) || $plugin_name == 'none') {
+    return TRUE;
+  }
+
+  ctools_include('context');
+  $plugin = ctools_get_access_plugin($plugin_name);
+  if (!$plugin) {
+    return FALSE;
+  }
+
+  if ($function = ctools_plugin_get_function($plugin, 'callback')) {
+    return $function($settings, $contexts, $account);
+  }
+}
diff --git a/delegator/css/page-task.css b/delegator/css/page-task.css
new file mode 100644
index 0000000000000000000000000000000000000000..f45792353fbaefb1f4a1a25092a3b4c0386eaa4d
--- /dev/null
+++ b/delegator/css/page-task.css
@@ -0,0 +1,10 @@
+/* $Id$ */
+
+.delegator-page-operations {
+  text-align: right;
+}
+
+td.delegator-page-operations {
+  width: 175px;
+}
+
diff --git a/delegator/delegator.module b/delegator/delegator.module
index 4eda9a649deaa1a951bf14a83446dde0f040428c..213ac7f7d3c187dc133c4a7c82a555befa2dfad3 100644
--- a/delegator/delegator.module
+++ b/delegator/delegator.module
@@ -539,3 +539,12 @@ function delegator_make_task_name($task_id, $subtask_id) {
   }
 }
 
+/**
+ * Delegator for arg load function because menu system will not load extra
+ * files for these; they must be in a .module.
+ */
+function dp_arg_load($value, $subtask, $argument) {
+  require_once './' . drupal_get_path('module', 'delegator') . '/plugins/tasks/page.inc';
+  return _dp_arg_load($value, $subtask, $argument);
+}
+
diff --git a/delegator/help/api-task-type.html b/delegator/help/api-task-type.html
new file mode 100644
index 0000000000000000000000000000000000000000..144846e0f6b152ccf2d5099df30fcb5c70122831
--- /dev/null
+++ b/delegator/help/api-task-type.html
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+
+defines a task type, grouping tasks together and providing a common UI for them.
\ No newline at end of file
diff --git a/delegator/help/page-task-type.html b/delegator/help/page-task-type.html
new file mode 100644
index 0000000000000000000000000000000000000000..c382c76ff2e76fde8653bc3ea72304e55ee92cba
--- /dev/null
+++ b/delegator/help/page-task-type.html
@@ -0,0 +1,4 @@
+
+Additional 'task' keys support:
+
+operations -- a list of operations suitable for theme('links')
\ No newline at end of file
diff --git a/delegator/plugins/task_types/page.admin.inc b/delegator/plugins/task_types/page.admin.inc
new file mode 100644
index 0000000000000000000000000000000000000000..79ca9f38fdbc5f7d2b5acb07c0f98718085dc956
--- /dev/null
+++ b/delegator/plugins/task_types/page.admin.inc
@@ -0,0 +1,88 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Administrative functions for the page system
+ */
+function delegator_page_type_list() {
+  $tasks = delegator_get_tasks_by_type('page');
+
+  $tables = array(
+    'singles' => array(
+      'title' => t('System pages'),
+      'description' => t('Pages provided by the system that can be overridden to provide alternate content or layout.'),
+      'rows' => array(),
+    ),
+    'page' => array(), // give this one special weighting,
+  );
+
+  delegator_page_type_sort_tasks($tasks, $tables, 'singles');
+
+  $output = '';
+  $header = array(
+    array('data' => t('Title'), 'class' => 'delegator-page-title'),
+    array('data' => t('Path'), 'class' => 'delegator-page-path'),
+    array('data' => t('Operations'), 'class' => 'delegator-page-operations'),
+  );
+
+  foreach ($tables as $bucket => $info) {
+    if (!empty($info['rows'])) {
+      $output .= '<h3>' . check_plain($info['title']) . '</h3>';
+      $output .= '<div class="description">' . check_plain($info['description']) . '</div>';
+      if (isset($info['operations'])) {
+        $info['rows'][] = array('data' => array(array('data' => theme('links', $info['operations']), 'colspan' => 3)), 'class' => 'delegator-page-operations');
+      }
+    }
+
+    $output .= theme('table', $header, $info['rows']);
+  }
+
+  drupal_add_css(drupal_get_path('module', 'delegator') . '/css/page-task.css');
+  return $output;
+}
+
+/**
+ * Sort tasks into buckets based upon whether or not they have subtasks.
+ */
+function delegator_page_type_sort_tasks($tasks, &$tables, $bucket, $task_id = NULL) {
+  foreach ($tasks as $id => $task) {
+    // If a type has subtasks, add its subtasks in its own table.
+    if (!empty($task['subtasks'])) {
+      $tables[$id]['title'] = $task['title'];
+      $tables[$id]['description'] = $task['description'];
+      if (isset($task['operations'])) {
+        $tables[$id]['operations'] = $task['operations'];
+      }
+
+      delegator_page_type_sort_tasks(delegator_get_task_subtasks($task), $tables, $id, $task['name']);
+      continue;
+    }
+
+    if (isset($task_id)) {
+      $task_name = delegator_make_task_name($task_id, $task['name']);
+    }
+    else {
+      $task_name = $task['name'];
+    }
+
+    $row = array('data' => array(), 'class' => 'page-task-' . $id);
+    $row['data'][] = array('data' => $task['admin title'], 'class' => 'delegator-page-title');
+    $row['data'][] = array('data' => $task['admin path'], 'class' => 'delegator-page-path');
+    if (isset($task['operations'])) {
+      $operations = $task['operations'];
+    }
+    else {
+      $operations = array(
+        array(
+          'title' => t('Task handlers'),
+          'href' => "admin/build/delegator/$task_name"
+        ),
+      );
+    }
+    $row['data'][] = array('data' => theme('ctools_dropdown', t('Operations'), $operations), 'class' => 'delegator-page-operations');
+
+    $tables[$bucket]['rows'][] = $row;
+  }
+}
+
diff --git a/delegator/plugins/task_types/page.inc b/delegator/plugins/task_types/page.inc
new file mode 100644
index 0000000000000000000000000000000000000000..5425933303a449ae14d6deb26f6a94c28cd807df
--- /dev/null
+++ b/delegator/plugins/task_types/page.inc
@@ -0,0 +1,56 @@
+<?php
+// $Id$
+/**
+ * @file
+ * Groups tasks that provide pages and provides a common administrative UI for them.
+ *
+ * The page task supports any task that defines itself as type 'page' and it also
+ * includes special handling for the 'page' task to provide a nice location to
+ * place administer the list of pages, including adding removing and editing
+ * the handlers of pages alongside the less generic tasks such as node and
+ * user viewing.
+ */
+
+/**
+ * Specialized implementation of hook_delegator_tasks(). See api-task.html for
+ * more information.
+ */
+function delegator_page_delegator_task_types() {
+  return array(
+    'page' => array(
+      'title' => t('Pages'),
+      'admin path' => 'admin/build/pages',
+      'hook menu' => 'delegator_page_type_menu',
+    ),
+  );
+}
+
+/**
+ * Delegated implementation of hook_menu().
+ */
+function delegator_page_type_menu(&$items, $task) {
+  // Set up access permissions.
+  $access_callback = isset($task['admin access callback']) ? $task['admin access callback'] : 'user_access';
+
+  // @todo use 'administer pages' perm instead?
+  $access_arguments = isset($task['admin access arguments']) ? $task['admin access arguments'] : array('administer delegator');
+
+  $base = array(
+    'access callback' => $access_callback,
+    'access arguments' => $access_arguments,
+    'file' => 'plugins/task_types/page.admin.inc',
+  );
+
+  $items['admin/build/pages'] = array(
+    'title' => 'Pages',
+    'description' => 'Add, edit and remove overridden system pages and user defined pages from the system.',
+    'page callback' => 'delegator_page_type_list',
+  ) + $base;
+
+  $items['admin/build/pages/list'] = array(
+    'title' => 'List',
+    'page callback' => 'delegator_page_type_list',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+}
diff --git a/delegator/plugins/tasks/node_view.inc b/delegator/plugins/tasks/node_view.inc
index 059bbd0b0feab0ac36f138b52b824cebcbbcbcd4..a5d03e1f932ed6ac8cfcdcc8b7cb181bb6ae5be5 100644
--- a/delegator/plugins/tasks/node_view.inc
+++ b/delegator/plugins/tasks/node_view.inc
@@ -53,6 +53,10 @@ function delegator_node_view($node) {
     if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) {
       $output = $function($handler, $node);
       if ($output) {
+        // Since we're not using node_show() we need to emulate what it used to do.
+        // Update the history table, stating that this user viewed this node.
+        node_tag_new($node->nid);
+
         // 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.
diff --git a/delegator/plugins/tasks/page.admin.inc b/delegator/plugins/tasks/page.admin.inc
index e4c1fb6baf3ca353dbb7a01d8bd28572c0aa38fc..07881067e134d7b2e90b579d23242babd02db2e6 100644
--- a/delegator/plugins/tasks/page.admin.inc
+++ b/delegator/plugins/tasks/page.admin.inc
@@ -9,6 +9,161 @@
  * only when needed.
  */
 
+/**
+ * Delegated implementation of hook_menu().
+ */
+function delegator_page_menu(&$items, $task) {
+  // Set up access permissions.
+  $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');
+
+  $base = array(
+    'access callback' => $access_callback,
+    'access arguments' => $access_arguments,
+    'file' => 'plugins/tasks/page.admin.inc',
+  );
+
+  $items['admin/build/pages/add'] = array(
+    'title' => 'Add page',
+    'description' => 'Add a delegator page subtask.',
+    'page callback' => 'delegator_page_add_subtask',
+    'type' => MENU_LOCAL_TASK,
+  ) + $base;
+
+  $form_info = delegator_page_edit_form_info();
+  $default_task = FALSE;
+  $weight = 0;
+  foreach ($form_info['order'] as $form_id => $form_title) {
+    // The first edit form is the default for tabs, so it gets a bit
+    // of special treatment here.
+    if (!$default_task) {
+      $default_task = TRUE;
+      // Add the callback for the default tab.
+      $items["admin/build/pages/edit/%"] = array(
+        'title' => t('Edit'),
+        'page callback' => 'delegator_page_edit_subtask',
+        'page arguments' => array(4, $form_id),
+      ) + $base;
+
+      // And make sure it's the default local task.
+      $type = MENU_DEFAULT_LOCAL_TASK;
+    }
+    else {
+      // This allows an empty form title to provide a hidden form
+      // which is useful for doing more branch-like multi-step
+      // functionality.
+      $type = $form_title ? MENU_LOCAL_TASK : MENU_CALLBACK;
+    }
+
+    // Handler to edit delegator task handlers. May exist in its own UI.
+    $items["admin/build/pages/edit/%/$form_id"] = array(
+      'title' => $form_title,
+      'page callback' => 'delegator_page_edit_subtask',
+      'page arguments' => array(4, 5),
+      'type' => $type,
+      'weight' => $weight++,
+    ) + $base;
+  }
+
+  // AJAX callbacks for argument modal.
+  $items['admin/build/delegator/argument'] = array(
+    'page callback' => 'delegator_page_subtask_argument_ajax',
+  ) + $base;
+
+  // Add menu entries for each subtask
+  foreach (delegator_page_load_all() as $subtask_id => $subtask) {
+    if (!isset($subtask->access['settings'])) {
+      $subtask->access['settings'] = NULL;
+    }
+
+    $path = array();
+    $page_arguments = array($subtask_id);
+    $access_arguments = array($subtask->access['type'], $subtask->access['settings']);
+    $load_arguments = array($subtask_id, '%index');
+
+    // Replace named placeholders with our own placeholder to load contexts.
+    foreach (explode('/', $subtask->path) as $position => $bit) {
+      if ($bit[0] == '%' && $bit != '%') {
+        // If an argument, swap it out with our argument loader and make sure
+        // the argument gets passed through to the page callback.
+        $path[] = '%dp_arg';
+        $page_arguments[] = $position;
+        $access_arguments[] = $position;
+      }
+      else {
+        $path[] = $bit;
+      }
+    }
+
+    $menu_path = implode('/', $path);
+
+    $items[$menu_path] = delegator_page_menu_item($subtask->menu, $access_arguments, $page_arguments, $load_arguments);
+
+    // Add a parent menu item if one is configured.
+    if ($subtask->menu['type'] == 'default tab' && $subtask->menu['parent']['type'] != 'none') {
+      array_pop($path);
+      $parent_path = implode('/', $path);
+      $items[$parent_path] = delegator_page_menu_item($subtask->menu['parent'], $access_arguments, $page_arguments, $load_arguments);
+    }
+  }
+}
+
+/**
+ * Create a menu item for delegator pages.
+ *
+ * @param $menu
+ *   The configuration to use. It will contain a type, and depending on the
+ *   type may also contain weight, title and name. These are presumed to have
+ *   been configured from the UI.
+ * @param $access_arguments
+ *   Arguments that go with ctools_access_menu; it should be loaded with
+ *   the access plugin type, settings, and positions of any arguments that
+ *   may produce contexts.
+ * @param $page_arguments
+ *   This should be seeded with the subtask name for easy loading and like
+ *   the access arguments above should contain positions of arguments so
+ *   that the menu system passes contexts through.
+ * @param $load_arguments
+ *   Arguments to send to the arg loader; should be the subtask id and '%index'.
+ */
+function delegator_page_menu_item($menu, $access_arguments, $page_arguments, $load_arguments) {
+  $item = array(
+    'access callback' => 'ctools_access_menu',
+    'access arguments' => $access_arguments,
+    'page callback' => 'delegator_page_execute',
+    'page arguments' => $page_arguments,
+    'load arguments' => $load_arguments,
+    'file' => 'plugins/tasks/page.admin.inc',
+  );
+
+  if (isset($menu['title'])) {
+    $item['title'] = $menu['title'];
+  }
+  if (isset($menu['weight'])) {
+    $item['weight'] = $menu['weight'];
+  }
+
+  switch ($menu['type']) {
+    case 'none':
+    default:
+      $item['type'] = MENU_CALLBACK;
+      break;
+    case 'normal':
+      $item['type'] = MENU_NORMAL_ITEM;
+      // Insert item into the proper menu
+      $item['menu_name'] = $menu['name'];
+      break;
+    case 'tab':
+      $item['type'] = MENU_LOCAL_TASK;
+      break;
+    case 'default tab':
+      $item['type'] = MENU_DEFAULT_LOCAL_TASK;
+      break;
+  }
+
+  return $item;
+}
+
 /**
  * Get the cached changes to a given task handler.
  */
@@ -147,7 +302,7 @@ function delegator_page_edit_subtask($page_name, $step = NULL) {
 function delegator_page_add_subtask_finish(&$form_state) {
   $page = &$form_state['page'];
   // Ensure $page->arguments contains only real arguments:
-  $arguments = delegator_page_get_arguments($page->path);
+  $arguments = delegator_page_get_named_arguments($page->path);
   $args = array();
   foreach ($arguments as $keyword => $position) {
     if (isset($page->arguments[$keyword])) {
@@ -240,7 +395,7 @@ function delegator_page_form_basic_validate_filter($value) {
  */
 function delegator_page_form_basic_validate(&$form, &$form_state) {
   // Ensure name is properly formed.
-  $args = delegator_page_get_arguments($form_state['values']['path']);
+  $args = delegator_page_get_named_arguments($form_state['values']['path']);
   if ($invalid_args = array_filter($args, 'delegator_page_form_basic_validate_filter')) {
     foreach ($invalid_args as $arg => $position) {
       form_error($form['path'], t('Duplicated argument %arg', array('%arg' => $arg)));
@@ -428,14 +583,7 @@ function delegator_page_form_access(&$form, &$form_state) {
 
   $contexts = array();
   // Load contexts based on argument data:
-  if ($page->arguments) {
-    $arguments = array();
-    foreach ($page->arguments as $keyword => $argument) {
-      if (isset($argument['name'])) {
-        $argument['keyword'] = $keyword;
-        $arguments[$argument['id']] = $argument;
-      }
-    }
+  if ($arguments = delegator_page_get_arguments($page)) {
     $contexts = ctools_context_get_placeholders_from_argument($arguments);
   }
 
@@ -549,7 +697,7 @@ function delegator_page_form_argument(&$form, &$form_state) {
   $path = $form_state['page']->path;
   $page = &$form_state['page'];
 
-  $arguments = delegator_page_get_arguments($path);
+  $arguments = delegator_page_get_named_arguments($path);
 
   $form['table'] = array(
     '#theme' => 'delegator_page_form_argument_table',
@@ -671,7 +819,7 @@ function delegator_page_subtask_argument_ajax($step = NULL, $cache_name = NULL,
   }
 
   $path = $page->path;
-  $arguments = delegator_page_get_arguments($path);
+  $arguments = delegator_page_get_named_arguments($path);
 
   // Load stored object from cache.
   if (!isset($arguments[$keyword])) {
diff --git a/delegator/plugins/tasks/page.inc b/delegator/plugins/tasks/page.inc
index 84630943c3856f7d5425b3f3227e229eb645fcd9..f86658b27820f4a22bb0f9d28bd45fe474daf3a8 100644
--- a/delegator/plugins/tasks/page.inc
+++ b/delegator/plugins/tasks/page.inc
@@ -7,6 +7,9 @@
  *
  * This creates subtasks and stores them in the delegator_pages table. These
  * are exportable objects, too.
+ *
+ * The render callback for this task type has $handler, $page, $contexts as
+ * parameters.
  */
 
 /**
@@ -19,8 +22,13 @@ function delegator_page_delegator_tasks() {
       'title' => t('User pages'),
       'description' => t('Administrator created pages that have a URL path, access control and entries in the Drupal menu system.'),
       'subtasks' => TRUE,
+      'subtask callback' => 'delegator_page_subtask',
       'subtasks callback' => 'delegator_page_subtasks',
-      'hook menu' => 'delegator_page_menu',
+      'hook menu' => array(
+        'file' => 'page.admin.inc',
+        'path' => drupal_get_path('module', 'delegator') . '/plugins/tasks',
+        'function' => 'delegator_page_menu',
+      ),
       'hook theme' => 'delegator_page_theme',
 
       // page only items
@@ -35,6 +43,11 @@ function delegator_page_delegator_tasks() {
           'href' => 'admin/build/pages/add',
         ),
       ),
+
+      // context only items
+      'type' => 'context',
+      'get arguments' => 'delegator_page_get_argument_callback',
+      'get context placeholders' => 'delegator_page_get_contexts',
     ),
   );
 }
@@ -43,131 +56,92 @@ function delegator_page_delegator_tasks() {
  * Return a list of all subtasks.
  */
 function delegator_page_subtasks($task) {
-  $subtasks = delegator_page_load_all();
+  $pages = delegator_page_load_all();
   $return = array();
-  foreach ($subtasks as $name => $subtask) {
-    $form_info = delegator_page_edit_form_info();
-    $edit_links = array();
-    foreach ($form_info['order'] as $form_id => $form_title) {
-      $edit_links[] = array(
-        'title' => $form_title,
-        'href' => "admin/build/pages/edit/$name/$form_id",
-      );
-    }
-
-    $operations = array();
-
-    if (empty($subtask->disabled)) {
-      $operations[] =  array(
-        'title' => '<span class="text">' . t('Edit page') . '</span>' . theme('links', $edit_links),
-        'html' => TRUE,
-      );
-      $operations[] = array(
-        'title' => t('Clone'),
-        'href' => "admin/build/pages/clone/$name",
-      );
-      $operations[] = array(
-        'title' => t('Export'),
-        'href' => "admin/build/pages/export/$name",
-      );
-      if ($subtask->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
-        $operations[] = array(
-          'title' => t('Revert'),
-          'href' => "admin/build/pages/delete/$name",
-        );
-      }
-      else if ($subtask->export_type == EXPORT_IN_CODE) {
-        $operations[] = array(
-          'title' => t('Disable'),
-          'href' => "admin/build/pages/disable/$name",
-        );
-      }
-      else {
-        $operations[] = array(
-          'title' => t('Delete'),
-          'href' => "admin/build/pages/delete/$name",
-        );
-      }
-    }
-    else {
-      $operations[] = array(
-        'title' => t('Enable'),
-        'href' => "admin/build/pages/enable/$name",
-      );
-    }
-    $return[$name] = array(
-      'name' => $name,
-      'admin title' => $subtask->admin_title,
-      'admin description' => t('TODO'),
-      'admin path' => $subtask->path,
-      'subtask' => $subtask,
-      'operations' => $operations,
-    );
+  foreach ($pages as $name => $page) {
+    $return[$name] = delegator_page_build_subtask($task, $page);
   }
 
   return $return;
 }
 
 /**
- * Delegated implementation of hook_menu().
+ * Callback to return a single subtask.
  */
-function delegator_page_menu(&$items, $task) {
-  // Set up access permissions.
-  $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');
-
-  $base = array(
-    'access callback' => $access_callback,
-    'access arguments' => $access_arguments,
-    'file' => 'plugins/tasks/page.admin.inc',
-  );
-
-  $items['admin/build/pages/add'] = array(
-    'title' => 'Add page',
-    'description' => 'Add a delegator page subtask.',
-    'page callback' => 'delegator_page_add_subtask',
-    'type' => MENU_LOCAL_TASK,
-  ) + $base;
+function delegator_page_subtask($task, $subtask_id) {
+  $page = delegator_page_load($subtask_id);
+  if ($page) {
+    return delegator_page_build_subtask($task, $page);
+  }
+}
 
+/**
+ * Build a subtask array for a given page.
+ */
+function delegator_page_build_subtask($task, $page) {
   $form_info = delegator_page_edit_form_info();
-  $default_task = FALSE;
-  $weight = 0;
+  $edit_links = array();
+  $name = $page->name;
+
   foreach ($form_info['order'] as $form_id => $form_title) {
-    // The first edit form is the default for tabs, so it gets a bit
-    // of special treatment here.
-    if (!$default_task) {
-      $default_task = TRUE;
-      // Add the callback for the default tab.
-      $items["admin/build/pages/edit/%"] = array(
-        'title' => t('Edit'),
-        'page callback' => 'delegator_page_edit_subtask',
-        'page arguments' => array(4, $form_id),
-      ) + $base;
-
-      // And make sure it's the default local task.
-      $type = MENU_DEFAULT_LOCAL_TASK;
+    $edit_links[] = array(
+      'title' => $form_title,
+      'href' => "admin/build/pages/edit/$name/$form_id",
+    );
+  }
+
+  $operations = array();
+
+  if (empty($page->disabled)) {
+    $operations[] = array(
+      'title' => t('Task handlers'),
+      'href' => "admin/build/delegator/" . delegator_make_task_name($task['name'], $name),
+    );
+    $operations[] =  array(
+      'title' => '<span class="text">' . t('Edit page') . '</span>' . theme('links', $edit_links),
+      'html' => TRUE,
+    );
+    $operations[] = array(
+      'title' => t('Clone'),
+      'href' => "admin/build/pages/clone/$name",
+    );
+    $operations[] = array(
+      'title' => t('Export'),
+      'href' => "admin/build/pages/export/$name",
+    );
+    if ($page->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
+      $operations[] = array(
+        'title' => t('Revert'),
+        'href' => "admin/build/pages/delete/$name",
+      );
+    }
+    else if ($page->export_type == EXPORT_IN_CODE) {
+      $operations[] = array(
+        'title' => t('Disable'),
+        'href' => "admin/build/pages/disable/$name",
+      );
     }
     else {
-      // This allows an empty form title to provide a hidden form
-      // which is useful for doing more branch-like multi-step
-      // functionality.
-      $type = $form_title ? MENU_LOCAL_TASK : MENU_CALLBACK;
+      $operations[] = array(
+        'title' => t('Delete'),
+        'href' => "admin/build/pages/delete/$name",
+      );
     }
-
-    // Handler to edit delegator task handlers. May exist in its own UI.
-    $items["admin/build/pages/edit/%/$form_id"] = array(
-      'title' => $form_title,
-      'page callback' => 'delegator_page_edit_subtask',
-      'page arguments' => array(4, 5),
-      'type' => $type,
-      'weight' => $weight++,
-    ) + $base;
   }
-
-  // AJAX callbacks for argument modal.
-  $items['admin/build/delegator/argument'] = array(
-    'page callback' => 'delegator_page_subtask_argument_ajax',
-  ) + $base;
+  else {
+    $operations[] = array(
+      'title' => t('Enable'),
+      'href' => "admin/build/pages/enable/$name",
+    );
+  }
+  return array(
+    'name' => $name,
+    'admin title' => $page->admin_title,
+    'admin description' => t('TODO'),
+    'admin path' => $page->path,
+    'subtask' => $page,
+    'operations' => $operations,
+  );
 }
 
 /**
@@ -221,6 +195,139 @@ function delegator_page_edit_form_info() {
   );
 }
 
+// --------------------------------------------------------------------------
+// Page execution functions
+
+/**
+ * Load a context from an argument for a given page task.
+ *
+ * @param $value
+ *   The incoming argument value.
+ * @param $subtask
+ *   The subtask id.
+ * @param $argument
+ *   The numeric position of the argument in the path, counting from 0.
+ *
+ * @return
+ *   A context item if one is configured, the argument if one is not, or
+ *   FALSE if restricted or invalid.
+ */
+function _dp_arg_load($value, $subtask, $argument) {
+  $page = delegator_page_load($subtask);
+  if (!$page) {
+    return FALSE;
+  }
+
+  $path = explode('/', $page->path);
+  if (empty($path[$argument])) {
+    return FALSE;
+  }
+
+  $keyword = substr($path[$argument], 1);
+  if (empty($page->arguments[$keyword])) {
+    return $value;
+  }
+
+  $page->arguments[$keyword]['keyword'] = $keyword;
+
+  ctools_include('context');
+  $context = ctools_context_get_context_from_argument($page->arguments[$keyword], $value);
+
+  // convert false equivalents to false.
+  return $context ? $context : FALSE;
+}
+
+/**
+ * Execute a page task.
+ *
+ * This is the callback to entries in the Drupal menu system created by the
+ * page task.
+ *
+ * @param $subtask_id
+ *   The name of the page task used.
+ * @param ...
+ *   A number of context objects as specified by the user when
+ *   creating named arguments in the path.
+ */
+function delegator_page_execute($subtask_id) {
+  // Turn the contexts into a properly keyed array.
+  $contexts = array();
+  foreach (func_get_args() as $arg) {
+    if (is_object($arg) && get_class($arg) == 'ctools_context') {
+      $contexts[$arg->id] = $arg;
+    }
+  }
+
+  $task = delegator_get_task('page');
+  $handlers = delegator_load_sorted_handlers($task, $subtask_id);
+
+  $page = delegator_page_load($subtask_id);
+
+  // Try each handler.
+  foreach ($handlers as $handler) {
+    if ($function = ctools_plugin_load_function('delegator', 'task_handlers', $handler->handler, 'render')) {
+      $output = $function($handler, $page, $contexts);
+      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;
+      }
+    }
+  }
+
+  return drupal_access_denied();
+}
+
+// --------------------------------------------------------------------------
+// Context type callbacks
+
+/**
+ * Return a list of arguments used by this task.
+ */
+function delegator_page_get_argument_callback($task, $subtask_id) {
+  $page = delegator_page_load($subtask_id);
+
+  if ($page) {
+    return delegator_page_get_arguments($page);
+  }
+}
+
+/**
+ * Get a group of context placeholders for the arguments.
+ */
+function delegator_page_get_contexts($task, $subtask_id) {
+  $page = delegator_page_load($subtask_id);
+
+  if ($page && $arguments = delegator_page_get_arguments($page)) {
+    ctools_include('context');
+    return ctools_context_get_placeholders_from_argument($arguments);
+  }
+}
+
+/**
+ * Return a list of arguments used by this page.
+ *
+ * This provides a list of arguments suitable for using in the context
+ * system, which is slightly more data than we store in the database.
+ * This also filters out arguments that have no contexts.
+ */
+function delegator_page_get_arguments($page) {
+  if ($page->arguments) {
+    $arguments = array();
+    foreach ($page->arguments as $keyword => $argument) {
+      if (isset($argument['name'])) {
+        $argument['keyword'] = $keyword;
+        $arguments[$keyword] = $argument;
+      }
+    }
+    return $arguments;
+  }
+}
+
 // --------------------------------------------------------------------------
 // Page task database info.
 
diff --git a/help/context-arguments.html b/help/context-arguments.html
index fca3a01744432e750d65904b9b66f4a249c397af..f59b4a644df267f3b5653d47c0e56607c60acfc6 100644
--- a/help/context-arguments.html
+++ b/help/context-arguments.html
@@ -13,3 +13,8 @@ string as though it came from a URL element.
     'settings form' => params: $form, $form_state, $conf -- gets the whole form. Should put anything it wants to keep automatically in $form['settings']
     'settings form validate' => params: $form, $form_state
     'settings form submit' => params: $form, $form_state
+
+    'criteria form' => params: $form, &$form_state, $conf, $argument, $id -- gets the whole argument. It should only put form widgets in $form[$id]. $conf may not be properly initialized so always guard against this due to arguments being changed and handlers not being updated to match.
+    + submit + validate
+
+    'criteria select' => returns true if the selected criteria matches the context. params: $context, $conf
diff --git a/includes/context.inc b/includes/context.inc
index 23d6a1e44c74328c146af2f942c2b41232c38139..0a3bfcda3023a29022bed24f2deb3887fcbfb156 100644
--- a/includes/context.inc
+++ b/includes/context.inc
@@ -480,6 +480,8 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS
       $context->identifier = $argument['identifier'];
       $context->page_title = isset($argument['title']) ? $argument['title'] : '';
       $context->keyword    = $argument['keyword'];
+      $context->id         = ctools_context_id($argument, 'argument');
+      $context->original_argument = $arg;
     }
     return $context;
   }
diff --git a/includes/dropdown.theme.inc b/includes/dropdown.theme.inc
index c52f6b7c90a495ddcb284575fed35065269669c8..54b42b58bdb5bad0d2db5367da20149a6135f37b 100644
--- a/includes/dropdown.theme.inc
+++ b/includes/dropdown.theme.inc
@@ -3,7 +3,34 @@
 
 /**
  * @file
- * Theme registry for dropdown-div tool.
+ * Provide a javascript based dropdown menu.
+ *
+ * The dropdown menu will show up as a clickable link; when clicked,
+ * a small menu will slide down beneath it, showing the list of links.
+ *
+ * The dropdown will stay open until either the user has moved the mouse
+ * away from the box for > .5 seconds, or can be immediately closed by
+ * clicking the link again. The code is smart enough that if the mouse
+ * moves away and then back within the .5 second window, it will not
+ * re-close.
+ *
+ * Multiple dropdowns can be placed per page.
+ *
+ * If the user does not have javascript enabled, the link will not appear,
+ * and instead by default the list of links will appear as a normal inline
+ * list.
+ *
+ * The menu is heavily styled by default, and to make it look different
+ * will require a little bit of CSS. You can apply your own class to the
+ * dropdown to help ensure that your CSS can override the dropdown's CSS.
+ *
+ * In particular, the text, link, background and border colors may need to
+ * be changed. Please see dropdown.css for information about the existing
+ * styling.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
  */
 function ctools_dropdown_theme(&$items) {
   $items['ctools_dropdown'] = array(
@@ -14,6 +41,16 @@ function ctools_dropdown_theme(&$items) {
 
 /**
  * Create a dropdown menu.
+ *
+ * @param $title
+ *   The text to place in the clickable area to activate the dropdown.
+ * @param $links
+ *   A list of links to provide within the dropdown, suitable for use
+ *   in via Drupal's theme('links').
+ * @param $class
+ *   An optional class to add to the dropdown's container div to allow you
+ *   to style a single dropdown however you like without interfering with
+ *   other dropdowns.
  */
 function theme_ctools_dropdown($title, $links, $class = '') {
   // Provide a unique identifier for every dropdown on the page.
diff --git a/includes/plugins.inc b/includes/plugins.inc
index 4c910603e379483732b8ab9aca11e921b389a582..61235643f0cd21f71ecb8979ffeaef9cf64d0f07 100644
--- a/includes/plugins.inc
+++ b/includes/plugins.inc
@@ -258,7 +258,6 @@ function ctools_plugin_get_function($plugin, $function_name) {
   // If cached the .inc file may not have been loaded. require_once is quite safe
   // and fast so it's okay to keep calling it.
   if (isset($plugin['file'])) {
-    if (!is_array($plugin)) { vpr_trace(); }
     require_once './' . $plugin['path'] . '/' . $plugin['file'];
   }
 
diff --git a/js/collapsible-div.js b/js/collapsible-div.js
index a51035c1992b0c7af17a221e7d2b450606920f6b..36b54f2ba803e3c9585c8b48e8436b63efaa302b 100644
--- a/js/collapsible-div.js
+++ b/js/collapsible-div.js
@@ -1,11 +1,8 @@
 // $Id$
-
-// All CTools tools begin with this:
-if (!Drupal.CTools) {
-  Drupal.CTools = {};
-}
-
 /**
+ * @file
+ * Javascript required for a simple collapsible div.
+ *
  * Creating a collapsible div with this doesn't take too much. There are 
  * three classes necessary:
  *
@@ -21,6 +18,12 @@ if (!Drupal.CTools) {
  * a class, which will cause the container to draw collapsed.
  */
 
+// All CTools tools begin with this if they need to use the CTools namespace.
+if (!Drupal.CTools) {
+  Drupal.CTools = {};
+}
+
+
 // Set up an array for callbacks.
 Drupal.CTools.CollapsibleCallbacks = [];
 Drupal.CTools.CollapsibleCallbacksAfterToggle = [];
diff --git a/js/dependent.js b/js/dependent.js
index 5f2be0020007b861567428e47aab90ccc8a02b89..8c800c4f377e7131d756f5a6aa58de7c66b2ba59 100644
--- a/js/dependent.js
+++ b/js/dependent.js
@@ -1,6 +1,6 @@
 // $Id$
 /**
- * @file dependent.js
+ * @file
  *
  * Written by dmitrig01 (Dmitri Gaskin) for CTools; this provides dependent
  * visibility for form items in CTools' ajax forms.
diff --git a/js/dropdown.js b/js/dropdown.js
index 67fcd6ae6722dabb57e68e2fae008d8f0708be81..17a514996115ecbc2e5c6bc600bdc669196b8b0e 100644
--- a/js/dropdown.js
+++ b/js/dropdown.js
@@ -1,5 +1,26 @@
 // $Id$
-
+/**
+ * @file
+ * Implement a simple, clickable dropdown menu.
+ *
+ * See dropdown.theme.inc for primary documentation.
+ *
+ * The javascript relies on four classes:
+ * - The dropdown must be fully contained in a div with the class 
+ *   ctools-dropdown. It must also contain the class ctools-dropdown-no-js
+ *   which will be immediately removed by the javascript; this allows for
+ *   graceful degradation.
+ * - The trigger that opens the dropdown must be an a tag wit hthe class
+ *   ctools-dropdown-link. The href should just be '#' as this will never
+ *   be allowed to complete.
+ * - The part of the dropdown that will appear when the link is clicked must
+ *   be a div with class ctools-dropdown-container.
+ * - Finally, ctools-dropdown-hover will be placed on any link that is being
+ *   hovered over, so that the browser can restyle the links.
+ *
+ * This tool isn't meant to replace click-tips or anything, it is specifically
+ * meant to work well presenting menus.
+ */
 Drupal.behaviors.CToolsDropdown = function() {
   $('div.ctools-dropdown:not(.ctools-dropdown-processed)')
     .removeClass('ctools-dropdown-no-js')
diff --git a/plugins/arguments/nid.inc b/plugins/arguments/nid.inc
index 2dc9009b2934f9eb4c23f3aa91cefdbced9da44c..7fc115037fe017b5ecf40c5a037440f13485b790 100644
--- a/plugins/arguments/nid.inc
+++ b/plugins/arguments/nid.inc
@@ -15,7 +15,9 @@ function ctools_nid_ctools_arguments() {
     'title' => t("Node ID"),
     'keyword' => 'node',
     'description' => t('Creates a node context from a node ID argument.'),
-    'context' => 'ctools_nid_context',
+    'context' => 'ctools_argument_nid_context',
+    'criteria form' => 'ctools_argument_nid_criteria_form',
+    'criteria select' => 'ctools_argument_nid_criteria_select',
   );
   return $args;
 }
@@ -23,19 +25,65 @@ function ctools_nid_ctools_arguments() {
 /**
  * Discover if this argument gives us the node we crave.
  */
-function ctools_nid_context($node = NULL, $conf = NULL, $empty = FALSE) {
+function ctools_argument_nid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
   // If unset it wants a generic, unfilled context.
   if ($empty) {
     return ctools_context_create_empty('node');
   }
 
-  if (!is_object($node)) {
-    return NULL;
+  if (!is_numeric($arg)) {
+    return FALSE;
   }
 
-  if (array_filter($conf['types']) && empty($conf['types'][$node->type])) {
-    return NULL;
+  $node = node_load($arg);
+  if (!$node) {
+    return FALSE;
   }
 
   return ctools_context_create('node', $node);
 }
+
+/**
+ * Provide a criteria form for selecting a node.
+ */
+function ctools_argument_nid_criteria_form(&$form, &$form_state, $conf, $argument, $id) {
+  // Ensure $conf has valid defaults:
+  if (!is_array($conf)) {
+    $conf = array();
+  }
+
+  $conf += array(
+    'type' => array(),
+  );
+
+  $types = node_get_types();
+  foreach ($types as $type => $info) {
+    $options[$type] = check_plain($info->name);
+  }
+
+  $form[$id]['type'] = array(
+    '#title' => t('Select types for %identifier', array('%identifier' => $argument['identifier'])),
+    '#type' => 'checkboxes',
+    '#options' => $options,
+    '#description' => t('This item will only be selected for nodes having the selected node types. If no node types are selected, it will be selected for all node types.'),
+    '#default_value' => $conf['type'],
+  );
+}
+
+
+/**
+ * Provide a criteria form for selecting a node.
+ */
+function ctools_argument_nid_criteria_select($conf, $context) {
+  // As far as I know there should always be a context at this point, but this
+  // is safe.
+  if (empty($context) || empty($context->data) || empty($context->data->type)) {
+    return FALSE;
+  }
+
+  if (array_filter($conf['type']) && empty($conf['type'][$context->data->type])) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/plugins/arguments/uid.inc b/plugins/arguments/uid.inc
index 8e76d75e9cafb89cc9dad2d9b1bfd16355a0762b..c310b04a42e11f2449494c772f7870b19b5443ef 100644
--- a/plugins/arguments/uid.inc
+++ b/plugins/arguments/uid.inc
@@ -16,7 +16,9 @@ function ctools_uid_ctools_arguments() {
     // keyword to use for %substitution
     'keyword' => 'user',
     'description' => t('Creates a user context from a user ID argument.'),
-    'context' => 'ctools_uid_context',
+    'context' => 'ctools_argument_uid_context',
+    'criteria form' => 'ctools_argument_uid_criteria_form',
+    'criteria select' => 'ctools_argument_uid_criteria_select',
   );
   return $args;
 }
@@ -24,7 +26,7 @@ function ctools_uid_ctools_arguments() {
 /**
  * Discover if this argument gives us the user we crave.
  */
-function ctools_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
+function ctools_argument_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
   // If unset it wants a generic, unfilled context.
   if ($empty) {
     return ctools_context_create_empty('user');
@@ -41,3 +43,47 @@ function ctools_uid_context($arg = NULL, $conf = NULL, $empty = FALSE) {
 
   return ctools_context_create('user', $account);
 }
+
+/**
+ * Provide a criteria form for selecting a node.
+ */
+function ctools_argument_uid_criteria_form(&$form, &$form_state, $conf, $argument, $id) {
+  // Ensure $conf has valid defaults:
+  if (!is_array($conf)) {
+    $conf = array();
+  }
+
+  $conf += array(
+    'rids' => array(),
+  );
+
+
+  $form[$id]['rids'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Select roles for %identifier', array('%identifier' => $argument['identifier'])),
+    '#default_value' => $conf['rids'],
+    '#options' => ctools_get_roles(),
+    '#description' => t('This item will only be selected for users having the selected roles. If no roles are selected, it will be selected for all users.'),
+  );
+}
+
+/**
+ * Provide a criteria form for selecting a node.
+ */
+function ctools_argument_uid_criteria_select($conf, $context) {
+  // As far as I know there should always be a context at this point, but this
+  // is safe.
+  if (empty($context) || empty($context->data) || !isset($context->data->roles)) {
+    return FALSE;
+  }
+
+  $rids = array_filter($conf['rids']);
+  if (!$rids) {
+    return TRUE;
+  }
+
+  $roles = array_keys($context->data->roles);
+  $roles[] = $context->data->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
+
+  return array_intersect($rids, $roles);
+}