diff --git a/resources/sql/autopatches/20140321.mstatus.2.mig.php b/resources/sql/autopatches/20140321.mstatus.2.mig.php
--- a/resources/sql/autopatches/20140321.mstatus.2.mig.php
+++ b/resources/sql/autopatches/20140321.mstatus.2.mig.php
@@ -35,7 +35,8 @@
   $id = $xaction->getID();
   echo pht('Migrating %d...', $id)."\n";
 
-  if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_STATUS) {
+  $xn_type = ManiphestTaskStatusTransaction::TRANSACTIONTYPE;
+  if ($xaction->getTransactionType() == $xn_type) {
     $old = $xaction->getOldValue();
     if ($old !== null && isset($status_map[$old])) {
       $old = $status_map[$old];
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1511,14 +1511,18 @@
     'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php',
     'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php',
     'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php',
+    'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.php',
     'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php',
     'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php',
     'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php',
     'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php',
+    'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php',
     'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php',
     'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php',
     'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php',
+    'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php',
     'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php',
+    'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php',
     'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php',
     'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
     'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php',
@@ -1541,14 +1545,20 @@
     'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
     'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
     'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
+    'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php',
+    'ManiphestTaskMergedIntoTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php',
     'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
+    'ManiphestTaskOwnerTransaction' => 'applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php',
     'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
     'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
+    'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php',
     'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php',
+    'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php',
     'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php',
     'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php',
     'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
     'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php',
+    'ManiphestTaskPriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php',
     'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php',
     'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php',
     'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php',
@@ -1560,9 +1570,14 @@
     'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php',
     'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php',
     'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php',
+    'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php',
+    'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php',
     'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php',
     'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php',
     'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php',
+    'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php',
+    'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php',
+    'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php',
     'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php',
     'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php',
     'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php',
@@ -6569,14 +6584,18 @@
     'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
     'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction',
     'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField',
+    'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule',
     'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship',
     'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource',
+    'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField',
+    'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskDetailController' => 'ManiphestController',
+    'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType',
     'ManiphestTaskEditController' => 'ManiphestController',
     'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock',
@@ -6599,14 +6618,20 @@
     'ManiphestTaskListView' => 'ManiphestView',
     'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
     'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
+    'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType',
+    'ManiphestTaskMergedIntoTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
+    'ManiphestTaskOwnerTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
     'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
+    'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskPoints' => 'Phobject',
+    'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskPriority' => 'ManiphestConstants',
     'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
     'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField',
+    'ManiphestTaskPriorityTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship',
     'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource',
@@ -6618,10 +6643,15 @@
     'ManiphestTaskStatusHeraldAction' => 'HeraldAction',
     'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase',
+    'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType',
+    'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType',
     'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskTestCase' => 'PhabricatorTestCase',
     'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
-    'ManiphestTransaction' => 'PhabricatorApplicationTransaction',
+    'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType',
+    'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType',
+    'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType',
+    'ManiphestTransaction' => 'PhabricatorModularTransaction',
     'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
--- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
+++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
@@ -94,11 +94,12 @@
 
     $xactions = array();
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+      ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
       ->setNewValue(pht('File Scramble Test Task'));
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
+      ->setTransactionType(
+        ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
       ->setNewValue('{'.$file->getMonogram().'}');
 
     id(new ManiphestTransactionEditor())
diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php
--- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php
+++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php
@@ -133,7 +133,7 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+      ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
       ->setNewValue($title);
 
 
@@ -169,11 +169,11 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+      ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($pri);
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
+      ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($sub);
 
     return $this->applyTaskTransactions($viewer, $src, $xactions);
@@ -192,11 +192,11 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+      ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($pri);
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
+      ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($sub);
 
     return $this->applyTaskTransactions($viewer, $src, $xactions);
diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
--- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
+++ b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php
@@ -76,9 +76,9 @@
     $value_map = array();
     $type_map = array(
       'add_comment' => PhabricatorTransactions::TYPE_COMMENT,
-      'assign' => ManiphestTransaction::TYPE_OWNER,
-      'status' => ManiphestTransaction::TYPE_STATUS,
-      'priority' => ManiphestTransaction::TYPE_PRIORITY,
+      'assign' => ManiphestTaskOwnerTransaction::TRANSACTIONTYPE,
+      'status' => ManiphestTaskStatusTransaction::TRANSACTIONTYPE,
+      'priority' => ManiphestTaskPriorityTransaction::TRANSACTIONTYPE,
       'add_project' => PhabricatorTransactions::TYPE_EDGE,
       'remove_project' => PhabricatorTransactions::TYPE_EDGE,
       'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS,
@@ -114,13 +114,13 @@
           case PhabricatorTransactions::TYPE_COMMENT:
             $current = null;
             break;
-          case ManiphestTransaction::TYPE_OWNER:
+          case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
             $current = $task->getOwnerPHID();
             break;
-          case ManiphestTransaction::TYPE_STATUS:
+          case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
             $current = $task->getStatus();
             break;
-          case ManiphestTransaction::TYPE_PRIORITY:
+          case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE:
             $current = $task->getPriority();
             break;
           case PhabricatorTransactions::TYPE_EDGE:
@@ -153,7 +153,7 @@
           }
           $value = head($value);
           break;
-        case ManiphestTransaction::TYPE_OWNER:
+        case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
           if (empty($value)) {
             continue 2;
           }
diff --git a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php
--- a/src/applications/maniphest/command/ManiphestAssignEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestAssignEmailCommand.php
@@ -53,7 +53,7 @@
     }
 
     $xactions[] = $object->getApplicationTransactionTemplate()
-      ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
+      ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
       ->setNewValue($assign_phid);
 
     return $xactions;
diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php
--- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php
@@ -23,7 +23,7 @@
     $xactions = array();
 
     $xactions[] = $object->getApplicationTransactionTemplate()
-      ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
+      ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
       ->setNewValue($viewer->getPHID());
 
     return $xactions;
diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php
--- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php
@@ -24,7 +24,7 @@
     $xactions = array();
 
     $xactions[] = $object->getApplicationTransactionTemplate()
-      ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
+      ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
       ->setNewValue(ManiphestTaskStatus::getDefaultClosedStatus());
 
     return $xactions;
diff --git a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
--- a/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestPriorityEmailCommand.php
@@ -71,7 +71,7 @@
     }
 
     $xactions[] = $object->getApplicationTransactionTemplate()
-      ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+      ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($priority);
 
     return $xactions;
diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
--- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
+++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php
@@ -73,7 +73,7 @@
     }
 
     $xactions[] = $object->getApplicationTransactionTemplate()
-      ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
+      ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
       ->setNewValue($status);
 
     return $xactions;
diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php
--- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php
+++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php
@@ -60,7 +60,7 @@
     if ($is_new) {
       $task->setTitle((string)$request->getValue('title'));
       $task->setDescription((string)$request->getValue('description'));
-      $changes[ManiphestTransaction::TYPE_STATUS] =
+      $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] =
         ManiphestTaskStatus::getDefaultStatus();
       $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] =
         array('+' => array($request->getUser()->getPHID()));
@@ -73,12 +73,12 @@
 
       $title = $request->getValue('title');
       if ($title !== null) {
-        $changes[ManiphestTransaction::TYPE_TITLE] = $title;
+        $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] = $title;
       }
 
       $desc = $request->getValue('description');
       if ($desc !== null) {
-        $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $desc;
+        $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] = $desc;
       }
 
       $status = $request->getValue('status');
@@ -88,7 +88,7 @@
           throw id(new ConduitException('ERR-INVALID-PARAMETER'))
             ->setErrorDescription(pht('Status set to invalid value.'));
         }
-        $changes[ManiphestTransaction::TYPE_STATUS] = $status;
+        $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] = $status;
       }
     }
 
@@ -99,7 +99,7 @@
         throw id(new ConduitException('ERR-INVALID-PARAMETER'))
           ->setErrorDescription(pht('Priority set to invalid value.'));
       }
-      $changes[ManiphestTransaction::TYPE_PRIORITY] = $priority;
+      $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] = $priority;
     }
 
     $owner_phid = $request->getValue('ownerPHID');
@@ -108,7 +108,7 @@
         array($owner_phid),
         PhabricatorPeopleUserPHIDType::TYPECONST,
         'ownerPHID');
-      $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid;
+      $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] = $owner_phid;
     }
 
     $ccs = $request->getValue('ccPHIDs');
diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php
--- a/src/applications/maniphest/controller/ManiphestReportController.php
+++ b/src/applications/maniphest/controller/ManiphestReportController.php
@@ -93,7 +93,7 @@
         ORDER BY x.dateCreated ASC',
       $table->getTableName(),
       $joins,
-      ManiphestTransaction::TYPE_STATUS);
+      ManiphestTaskStatusTransaction::TRANSACTIONTYPE);
 
     $stats = array();
     $day_buckets = array();
diff --git a/src/applications/maniphest/controller/ManiphestSubpriorityController.php b/src/applications/maniphest/controller/ManiphestSubpriorityController.php
--- a/src/applications/maniphest/controller/ManiphestSubpriorityController.php
+++ b/src/applications/maniphest/controller/ManiphestSubpriorityController.php
@@ -43,11 +43,11 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+      ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($pri);
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
+      ->setTransactionType(ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($sub);
 
     $editor = id(new ManiphestTransactionEditor())
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -277,7 +277,7 @@
     $can_create = (bool)$edit_config;
 
     $can_reassign = $edit_engine->hasEditAccessToTransaction(
-      ManiphestTransaction::TYPE_OWNER);
+      ManiphestTaskOwnerTransaction::TRANSACTIONTYPE);
 
     if ($can_create) {
       $form_key = $edit_config->getIdentifier();
diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -150,7 +150,7 @@
         ->setConduitDescription(pht('Create as a subtask of another task.'))
         ->setConduitTypeDescription(pht('PHID of the parent task.'))
         ->setAliases(array('parentPHID'))
-        ->setTransactionType(ManiphestTransaction::TYPE_PARENT)
+        ->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE)
         ->setHandleParameterType(new ManiphestTaskListHTTPParameterType())
         ->setSingleValue(null)
         ->setIsReorderable(false)
@@ -179,7 +179,7 @@
         ->setDescription(pht('Name of the task.'))
         ->setConduitDescription(pht('Rename the task.'))
         ->setConduitTypeDescription(pht('New task name.'))
-        ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+        ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
         ->setIsRequired(true)
         ->setValue($object->getTitle()),
       id(new PhabricatorUsersEditField())
@@ -190,7 +190,7 @@
         ->setConduitDescription(pht('Reassign the task.'))
         ->setConduitTypeDescription(
           pht('New task owner, or `null` to unassign.'))
-        ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
+        ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setSingleValue($object->getOwnerPHID())
         ->setCommentActionLabel(pht('Assign / Claim'))
@@ -201,7 +201,7 @@
         ->setDescription(pht('Status of the task.'))
         ->setConduitDescription(pht('Change the task status.'))
         ->setConduitTypeDescription(pht('New task status constant.'))
-        ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
+        ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setValue($object->getStatus())
         ->setOptions($status_map)
@@ -213,7 +213,7 @@
         ->setDescription(pht('Priority of the task.'))
         ->setConduitDescription(pht('Change the priority of the task.'))
         ->setConduitTypeDescription(pht('New task priority constant.'))
-        ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+        ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setValue($object->getPriority())
         ->setOptions($priority_map)
@@ -230,7 +230,7 @@
         ->setDescription(pht('Point value of the task.'))
         ->setConduitDescription(pht('Change the task point value.'))
         ->setConduitTypeDescription(pht('New task point value.'))
-        ->setTransactionType(ManiphestTransaction::TYPE_POINTS)
+        ->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE)
         ->setIsCopyable(true)
         ->setValue($object->getPoints())
         ->setCommentActionLabel($action_label);
@@ -242,7 +242,7 @@
       ->setDescription(pht('Task description.'))
       ->setConduitDescription(pht('Update the task description.'))
       ->setConduitTypeDescription(pht('New task description.'))
-      ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
+      ->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
       ->setValue($object->getDescription())
       ->setPreviewPanel(
         id(new PHUIRemarkupPreviewPanel())
diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -18,18 +18,6 @@
 
     $types[] = PhabricatorTransactions::TYPE_COMMENT;
     $types[] = PhabricatorTransactions::TYPE_EDGE;
-    $types[] = ManiphestTransaction::TYPE_PRIORITY;
-    $types[] = ManiphestTransaction::TYPE_STATUS;
-    $types[] = ManiphestTransaction::TYPE_TITLE;
-    $types[] = ManiphestTransaction::TYPE_DESCRIPTION;
-    $types[] = ManiphestTransaction::TYPE_OWNER;
-    $types[] = ManiphestTransaction::TYPE_SUBPRIORITY;
-    $types[] = ManiphestTransaction::TYPE_MERGED_INTO;
-    $types[] = ManiphestTransaction::TYPE_MERGED_FROM;
-    $types[] = ManiphestTransaction::TYPE_UNBLOCK;
-    $types[] = ManiphestTransaction::TYPE_PARENT;
-    $types[] = ManiphestTransaction::TYPE_COVER_IMAGE;
-    $types[] = ManiphestTransaction::TYPE_POINTS;
     $types[] = PhabricatorTransactions::TYPE_COLUMNS;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@@ -37,47 +25,19 @@
     return $types;
   }
 
+  public function getCreateObjectTitle($author, $object) {
+    return pht('%s created this task.', $author);
+  }
+
+  public function getCreateObjectTitleForFeed($author, $object) {
+    return pht('%s created %s.', $author, $object);
+  }
+
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_PRIORITY:
-        if ($this->getIsNewObject()) {
-          return null;
-        }
-        return (int)$object->getPriority();
-      case ManiphestTransaction::TYPE_STATUS:
-        if ($this->getIsNewObject()) {
-          return null;
-        }
-        return $object->getStatus();
-      case ManiphestTransaction::TYPE_TITLE:
-        if ($this->getIsNewObject()) {
-          return null;
-        }
-        return $object->getTitle();
-      case ManiphestTransaction::TYPE_DESCRIPTION:
-        if ($this->getIsNewObject()) {
-          return null;
-        }
-        return $object->getDescription();
-      case ManiphestTransaction::TYPE_OWNER:
-        return nonempty($object->getOwnerPHID(), null);
-      case ManiphestTransaction::TYPE_SUBPRIORITY:
-        return $object->getSubpriority();
-      case ManiphestTransaction::TYPE_COVER_IMAGE:
-        return $object->getCoverImageFilePHID();
-      case ManiphestTransaction::TYPE_POINTS:
-        $points = $object->getPoints();
-        if ($points !== null) {
-          $points = (double)$points;
-        }
-        return $points;
-      case ManiphestTransaction::TYPE_MERGED_INTO:
-      case ManiphestTransaction::TYPE_MERGED_FROM:
-        return null;
-      case ManiphestTransaction::TYPE_PARENT:
       case PhabricatorTransactions::TYPE_COLUMNS:
         return null;
     }
@@ -88,31 +48,8 @@
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_PRIORITY:
-        return (int)$xaction->getNewValue();
-      case ManiphestTransaction::TYPE_OWNER:
-        return nonempty($xaction->getNewValue(), null);
-      case ManiphestTransaction::TYPE_STATUS:
-      case ManiphestTransaction::TYPE_TITLE:
-      case ManiphestTransaction::TYPE_DESCRIPTION:
-      case ManiphestTransaction::TYPE_SUBPRIORITY:
-      case ManiphestTransaction::TYPE_MERGED_INTO:
-      case ManiphestTransaction::TYPE_MERGED_FROM:
-      case ManiphestTransaction::TYPE_UNBLOCK:
-      case ManiphestTransaction::TYPE_COVER_IMAGE:
-        return $xaction->getNewValue();
-      case ManiphestTransaction::TYPE_PARENT:
       case PhabricatorTransactions::TYPE_COLUMNS:
         return $xaction->getNewValue();
-      case ManiphestTransaction::TYPE_POINTS:
-        $value = $xaction->getNewValue();
-        if (!strlen($value)) {
-          $value = null;
-        }
-        if ($value !== null) {
-          $value = (double)$value;
-        }
-        return $value;
     }
   }
 
@@ -136,72 +73,6 @@
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_PRIORITY:
-        return $object->setPriority($xaction->getNewValue());
-      case ManiphestTransaction::TYPE_STATUS:
-        return $object->setStatus($xaction->getNewValue());
-      case ManiphestTransaction::TYPE_TITLE:
-        return $object->setTitle($xaction->getNewValue());
-      case ManiphestTransaction::TYPE_DESCRIPTION:
-        return $object->setDescription($xaction->getNewValue());
-      case ManiphestTransaction::TYPE_OWNER:
-        $phid = $xaction->getNewValue();
-
-        // Update the "ownerOrdering" column to contain the full name of the
-        // owner, if the task is assigned.
-
-        $handle = null;
-        if ($phid) {
-          $handle = id(new PhabricatorHandleQuery())
-            ->setViewer($this->getActor())
-            ->withPHIDs(array($phid))
-            ->executeOne();
-        }
-
-        if ($handle) {
-          $object->setOwnerOrdering($handle->getName());
-        } else {
-          $object->setOwnerOrdering(null);
-        }
-
-        return $object->setOwnerPHID($phid);
-      case ManiphestTransaction::TYPE_SUBPRIORITY:
-        $object->setSubpriority($xaction->getNewValue());
-        return;
-      case ManiphestTransaction::TYPE_MERGED_INTO:
-        $object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
-        return;
-      case ManiphestTransaction::TYPE_COVER_IMAGE:
-        $file_phid = $xaction->getNewValue();
-
-        if ($file_phid) {
-          $file = id(new PhabricatorFileQuery())
-            ->setViewer($this->getActor())
-            ->withPHIDs(array($file_phid))
-            ->executeOne();
-        } else {
-          $file = null;
-        }
-
-        if (!$file || !$file->isTransformableImage()) {
-          $object->setProperty('cover.filePHID', null);
-          $object->setProperty('cover.thumbnailPHID', null);
-          return;
-        }
-
-        $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD;
-
-        $xform = PhabricatorFileTransform::getTransformByKey($xform_key)
-          ->executeTransform($file);
-
-        $object->setProperty('cover.filePHID', $file->getPHID());
-        $object->setProperty('cover.thumbnailPHID', $xform->getPHID());
-        return;
-      case ManiphestTransaction::TYPE_POINTS:
-        $object->setPoints($xaction->getNewValue());
-        return;
-      case ManiphestTransaction::TYPE_MERGED_FROM:
-      case ManiphestTransaction::TYPE_PARENT:
       case PhabricatorTransactions::TYPE_COLUMNS:
         return;
     }
@@ -212,22 +83,11 @@
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_PARENT:
-        $parent_phid = $xaction->getNewValue();
-        $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
-        $task_phid = $object->getPHID();
-
-        id(new PhabricatorEdgeEditor())
-          ->addEdge($parent_phid, $parent_type, $task_phid)
-          ->save();
-        break;
       case PhabricatorTransactions::TYPE_COLUMNS:
         foreach ($xaction->getNewValue() as $move) {
           $this->applyBoardMove($object, $move);
         }
         break;
-      default:
-        break;
     }
   }
 
@@ -240,7 +100,7 @@
     $unblock_xaction = null;
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
-        case ManiphestTransaction::TYPE_STATUS:
+        case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
           $unblock_xaction = $xaction;
           break;
       }
@@ -265,7 +125,8 @@
 
         foreach ($blocked_tasks as $blocked_task) {
           $parent_xaction = id(new ManiphestTransaction())
-            ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK)
+            ->setTransactionType(
+              ManiphestTaskUnblockTransaction::TRANSACTIONTYPE)
             ->setOldValue(array($object->getPHID() => $old))
             ->setNewValue(array($object->getPHID() => $new));
 
@@ -398,7 +259,7 @@
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
-    return $this->shouldSendMail($object, $xactions);
+    return true;
   }
 
   protected function supportsSearch() {
@@ -426,11 +287,11 @@
     parent::requireCapabilities($object, $xaction);
 
     $app_capability_map = array(
-      ManiphestTransaction::TYPE_PRIORITY =>
+      ManiphestTaskPriorityTransaction::TRANSACTIONTYPE =>
         ManiphestEditPriorityCapability::CAPABILITY,
-      ManiphestTransaction::TYPE_STATUS =>
+      ManiphestTaskStatusTransaction::TRANSACTIONTYPE =>
         ManiphestEditStatusCapability::CAPABILITY,
-      ManiphestTransaction::TYPE_OWNER =>
+      ManiphestTaskOwnerTransaction::TRANSACTIONTYPE =>
         ManiphestEditAssignCapability::CAPABILITY,
       PhabricatorTransactions::TYPE_EDIT_POLICY =>
         ManiphestEditPoliciesCapability::CAPABILITY,
@@ -471,7 +332,7 @@
     $copy = parent::adjustObjectForPolicyChecks($object, $xactions);
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
-        case ManiphestTransaction::TYPE_OWNER:
+        case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
           $copy->setOwnerPHID($xaction->getNewValue());
           break;
         default:
@@ -629,157 +490,6 @@
     return array($dst->getPriority(), $sub);
   }
 
-  protected function validateTransaction(
-    PhabricatorLiskDAO $object,
-    $type,
-    array $xactions) {
-
-    $errors = parent::validateTransaction($object, $type, $xactions);
-
-    switch ($type) {
-      case ManiphestTransaction::TYPE_TITLE:
-        $missing = $this->validateIsEmptyTextField(
-          $object->getTitle(),
-          $xactions);
-
-        if ($missing) {
-          $error = new PhabricatorApplicationTransactionValidationError(
-            $type,
-            pht('Required'),
-            pht('Task title is required.'),
-            nonempty(last($xactions), null));
-
-          $error->setIsMissingFieldError(true);
-          $errors[] = $error;
-        }
-        break;
-      case ManiphestTransaction::TYPE_PARENT:
-        $with_effect = array();
-        foreach ($xactions as $xaction) {
-          $task_phid = $xaction->getNewValue();
-          if (!$task_phid) {
-            continue;
-          }
-
-          $with_effect[] = $xaction;
-
-          $task = id(new ManiphestTaskQuery())
-            ->setViewer($this->getActor())
-            ->withPHIDs(array($task_phid))
-            ->executeOne();
-          if (!$task) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht(
-                'Parent task identifier "%s" does not identify a visible '.
-                'task.',
-                $task_phid),
-              $xaction);
-          }
-        }
-
-        if ($with_effect && !$this->getIsNewObject()) {
-          $errors[] = new PhabricatorApplicationTransactionValidationError(
-            $type,
-            pht('Invalid'),
-            pht(
-              'You can only select a parent task when creating a '.
-              'transaction for the first time.'),
-            last($with_effect));
-        }
-        break;
-      case ManiphestTransaction::TYPE_OWNER:
-        foreach ($xactions as $xaction) {
-          $old = $xaction->getOldValue();
-          $new = $xaction->getNewValue();
-          if (!strlen($new)) {
-            continue;
-          }
-
-          if ($new === $old) {
-            continue;
-          }
-
-          $assignee_list = id(new PhabricatorPeopleQuery())
-            ->setViewer($this->getActor())
-            ->withPHIDs(array($new))
-            ->execute();
-          if (!$assignee_list) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht(
-                'User "%s" is not a valid user.',
-                $new),
-              $xaction);
-          }
-        }
-        break;
-      case ManiphestTransaction::TYPE_COVER_IMAGE:
-        foreach ($xactions as $xaction) {
-          $old = $xaction->getOldValue();
-          $new = $xaction->getNewValue();
-          if (!$new) {
-            continue;
-          }
-
-          if ($new === $old) {
-            continue;
-          }
-
-          $file = id(new PhabricatorFileQuery())
-            ->setViewer($this->getActor())
-            ->withPHIDs(array($new))
-            ->executeOne();
-          if (!$file) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht('File "%s" is not valid.', $new),
-              $xaction);
-            continue;
-          }
-
-          if (!$file->isTransformableImage()) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht('File "%s" is not a valid image file.', $new),
-              $xaction);
-            continue;
-          }
-        }
-        break;
-
-      case ManiphestTransaction::TYPE_POINTS:
-        foreach ($xactions as $xaction) {
-          $new = $xaction->getNewValue();
-          if (strlen($new) && !is_numeric($new)) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht('Points value must be numeric or empty.'),
-              $xaction);
-            continue;
-          }
-
-          if ((double)$new < 0) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht('Points value must be nonnegative.'),
-              $xaction);
-            continue;
-          }
-        }
-        break;
-
-    }
-
-    return $errors;
-  }
-
   protected function validateAllTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
@@ -806,7 +516,8 @@
 
     $any_assign = false;
     foreach ($xactions as $xaction) {
-      if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) {
+      if ($xaction->getTransactionType() ==
+        ManiphestTaskOwnerTransaction::TRANSACTIONTYPE) {
         $any_assign = true;
         break;
       }
@@ -817,7 +528,7 @@
     $new_status = null;
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
-        case ManiphestTransaction::TYPE_STATUS:
+        case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
           $new_status = $xaction->getNewValue();
           break;
       }
@@ -838,7 +549,7 @@
       // Don't claim the task if the status is configured to not claim.
       if ($actor_phid && $is_claim) {
         $results[] = id(new ManiphestTransaction())
-          ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
+          ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
           ->setNewValue($actor_phid);
       }
     }
@@ -881,7 +592,7 @@
           $this->moreValidationErrors[] = $error;
         }
         break;
-      case ManiphestTransaction::TYPE_OWNER:
+      case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
         // If this is a no-op update, don't expand it.
         $old_value = $object->getOwnerPHID();
         $new_value = $xaction->getNewValue();
@@ -906,20 +617,6 @@
     return $results;
   }
 
-  protected function extractFilePHIDsFromCustomTransaction(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
-
-    switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_COVER_IMAGE:
-        $phids[] = $xaction->getNewValue();
-        break;
-    }
-
-    return $phids;
-  }
-
   private function buildMoveTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
diff --git a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php
--- a/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php
+++ b/src/applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php
@@ -40,7 +40,7 @@
     }
 
     $xaction = $adapter->newTransaction()
-      ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
+      ->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
       ->setNewValue($phid);
 
     $adapter->queueTransaction($xaction);
diff --git a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php
--- a/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php
+++ b/src/applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php
@@ -40,7 +40,7 @@
     }
 
     $xaction = $adapter->newTransaction()
-      ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+      ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
       ->setNewValue($priority);
 
     $adapter->queueTransaction($xaction);
diff --git a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php
--- a/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php
+++ b/src/applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php
@@ -40,7 +40,7 @@
     }
 
     $xaction = $adapter->newTransaction()
-      ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
+      ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
       ->setNewValue($status);
 
     $adapter->queueTransaction($xaction);
diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
--- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
+++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
@@ -22,15 +22,15 @@
     $template = new ManiphestTransaction();
     // Accumulate Transactions
     $changes = array();
-    $changes[ManiphestTransaction::TYPE_TITLE] =
+    $changes[ManiphestTaskTitleTransaction::TRANSACTIONTYPE] =
       $this->generateTitle();
-    $changes[ManiphestTransaction::TYPE_DESCRIPTION] =
+    $changes[ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE] =
       $this->generateDescription();
-    $changes[ManiphestTransaction::TYPE_OWNER] =
+    $changes[ManiphestTaskOwnerTransaction::TRANSACTIONTYPE] =
       $this->loadOwnerPHID();
-    $changes[ManiphestTransaction::TYPE_STATUS] =
+    $changes[ManiphestTaskStatusTransaction::TRANSACTIONTYPE] =
       $this->generateTaskStatus();
-    $changes[ManiphestTransaction::TYPE_PRIORITY] =
+    $changes[ManiphestTaskPriorityTransaction::TRANSACTIONTYPE] =
       $this->generateTaskPriority();
     $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] =
       array('=' => $this->getCCPHIDs());
diff --git a/src/applications/maniphest/mail/ManiphestReplyHandler.php b/src/applications/maniphest/mail/ManiphestReplyHandler.php
--- a/src/applications/maniphest/mail/ManiphestReplyHandler.php
+++ b/src/applications/maniphest/mail/ManiphestReplyHandler.php
@@ -25,11 +25,12 @@
 
     if ($is_new) {
       $xactions[] = $this->newTransaction()
-        ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+        ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
         ->setNewValue(nonempty($mail->getSubject(), pht('Untitled Task')));
 
       $xactions[] = $this->newTransaction()
-        ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
+        ->setTransactionType(
+          ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
         ->setNewValue($body);
 
       $actor_phid = $actor->getPHID();
diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
--- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
+++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
@@ -19,7 +19,8 @@
   protected function newMergeIntoTransactions(ManiphestTask $task) {
     return array(
       id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO)
+        ->setTransactionType(
+          ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE)
         ->setNewValue($task->getPHID()),
     );
   }
@@ -34,7 +35,8 @@
       ->setNewValue(array('+' => $subscriber_phids));
 
     $xactions[] = id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM)
+        ->setTransactionType(
+          ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE)
         ->setNewValue(mpull($tasks, 'getPHID'));
 
     return $xactions;
diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php
--- a/src/applications/maniphest/storage/ManiphestTransaction.php
+++ b/src/applications/maniphest/storage/ManiphestTransaction.php
@@ -1,25 +1,7 @@
 <?php
 
 final class ManiphestTransaction
-  extends PhabricatorApplicationTransaction {
-
-  const TYPE_TITLE = 'title';
-  const TYPE_STATUS = 'status';
-  const TYPE_DESCRIPTION = 'description';
-  const TYPE_OWNER  = 'reassign';
-  const TYPE_PRIORITY = 'priority';
-  const TYPE_EDGE = 'edge';
-  const TYPE_SUBPRIORITY = 'subpriority';
-  const TYPE_MERGED_INTO = 'mergedinto';
-  const TYPE_MERGED_FROM = 'mergedfrom';
-  const TYPE_UNBLOCK = 'unblock';
-  const TYPE_PARENT = 'parent';
-  const TYPE_COVER_IMAGE = 'cover-image';
-  const TYPE_POINTS = 'points';
-
-  // NOTE: this type is deprecated. Keep it around for legacy installs
-  // so any transactions render correctly.
-  const TYPE_ATTACH = 'attach';
+  extends PhabricatorModularTransaction {
 
   const MAILTAG_STATUS = 'maniphest-status';
   const MAILTAG_OWNER = 'maniphest-owner';
@@ -44,30 +26,20 @@
     return new ManiphestTransactionComment();
   }
 
+  public function getBaseTransactionClass() {
+    return 'ManiphestTaskTransactionType';
+  }
+
   public function shouldGenerateOldValue() {
     switch ($this->getTransactionType()) {
-      case self::TYPE_EDGE:
-      case self::TYPE_UNBLOCK:
+      case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE:
+      case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE:
         return false;
     }
 
     return parent::shouldGenerateOldValue();
   }
 
-  protected function newRemarkupChanges() {
-    $changes = array();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_DESCRIPTION:
-        $changes[] = $this->newRemarkupChange()
-          ->setOldValue($this->getOldValue())
-          ->setNewValue($this->getNewValue());
-        break;
-    }
-
-    return $changes;
-  }
-
   public function getRequiredHandlePHIDs() {
     $phids = parent::getRequiredHandlePHIDs();
 
@@ -75,7 +47,7 @@
     $old = $this->getOldValue();
 
     switch ($this->getTransactionType()) {
-      case self::TYPE_OWNER:
+      case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
         if ($new) {
           $phids[] = $new;
         }
@@ -84,13 +56,13 @@
           $phids[] = $old;
         }
         break;
-      case self::TYPE_MERGED_INTO:
+      case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE:
         $phids[] = $new;
         break;
-      case self::TYPE_MERGED_FROM:
+      case ManiphestTaskMergedFromTransaction::TRANSACTIONTYPE:
         $phids = array_merge($phids, $new);
         break;
-      case self::TYPE_EDGE:
+      case ManiphestTaskEdgeTransaction::TRANSACTIONTYPE:
         $phids = array_mergev(
           array(
             $phids,
@@ -98,7 +70,7 @@
             array_keys(nonempty($new, array())),
           ));
         break;
-      case self::TYPE_ATTACH:
+      case ManiphestTaskAttachTransaction::TRANSACTIONTYPE:
         $old = nonempty($old, array());
         $new = nonempty($new, array());
         $phids = array_mergev(
@@ -108,12 +80,12 @@
             array_keys(idx($old, 'FILE', array())),
           ));
         break;
-      case self::TYPE_UNBLOCK:
+      case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE:
         foreach (array_keys($new) as $phid) {
           $phids[] = $phid;
         }
         break;
-      case self::TYPE_STATUS:
+      case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
         $commit_phid = $this->getMetadataValue('commitPHID');
         if ($commit_phid) {
           $phids[] = $commit_phid;
@@ -124,275 +96,27 @@
     return $phids;
   }
 
-  public function shouldHide() {
-    switch ($this->getTransactionType()) {
-      case PhabricatorTransactions::TYPE_EDGE:
-        $commit_phid = $this->getMetadataValue('commitPHID');
-        $edge_type = $this->getMetadataValue('edge:type');
-
-        if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) {
-          if ($commit_phid) {
-            return true;
-          }
-        }
-        break;
-      case self::TYPE_DESCRIPTION:
-      case self::TYPE_PRIORITY:
-      case self::TYPE_STATUS:
-        if ($this->getOldValue() === null) {
-          return true;
-        } else {
-          return false;
-        }
-        break;
-      case self::TYPE_SUBPRIORITY:
-      case self::TYPE_PARENT:
-        return true;
-      case self::TYPE_COVER_IMAGE:
-        // At least for now, don't show these.
-        return true;
-      case self::TYPE_POINTS:
-        if (!ManiphestTaskPoints::getIsEnabled()) {
-          return true;
-        }
-    }
-
-    return parent::shouldHide();
-  }
-
-  public function shouldHideForMail(array $xactions) {
-    switch ($this->getTransactionType()) {
-      case self::TYPE_POINTS:
-        return true;
-    }
-
-    return parent::shouldHideForMail($xactions);
-  }
-
-  public function shouldHideForFeed() {
-    switch ($this->getTransactionType()) {
-      case self::TYPE_UNBLOCK:
-        // Hide "alice created X, a task blocking Y." from feed because it
-        // will almost always appear adjacent to "alice created Y".
-        $is_new = $this->getMetadataValue('blocker.new');
-        if ($is_new) {
-          return true;
-        }
-        break;
-      case self::TYPE_POINTS:
-        return true;
-    }
-
-    return parent::shouldHideForFeed();
-  }
-
-  public function getActionStrength() {
-    switch ($this->getTransactionType()) {
-      case self::TYPE_TITLE:
-        return 1.4;
-      case self::TYPE_STATUS:
-        return 1.3;
-      case self::TYPE_OWNER:
-        return 1.2;
-      case self::TYPE_PRIORITY:
-        return 1.1;
-    }
-
-    return parent::getActionStrength();
-  }
-
-
-  public function getColor() {
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_OWNER:
-        if ($this->getAuthorPHID() == $new) {
-          return 'green';
-        } else if (!$new) {
-          return 'black';
-        } else if (!$old) {
-          return 'green';
-        } else {
-          return 'green';
-        }
-
-      case self::TYPE_STATUS:
-        $color = ManiphestTaskStatus::getStatusColor($new);
-        if ($color !== null) {
-          return $color;
-        }
-
-        if (ManiphestTaskStatus::isOpenStatus($new)) {
-          return 'green';
-        } else {
-          return 'indigo';
-        }
-
-      case self::TYPE_PRIORITY:
-        if ($old == ManiphestTaskPriority::getDefaultPriority()) {
-          return 'green';
-        } else if ($old > $new) {
-          return 'grey';
-        } else {
-          return 'yellow';
-        }
-
-      case self::TYPE_MERGED_FROM:
-        return 'orange';
-
-      case self::TYPE_MERGED_INTO:
-        return 'indigo';
-    }
-
-    return parent::getColor();
-  }
-
   public function getActionName() {
     $old = $this->getOldValue();
     $new = $this->getNewValue();
-
     switch ($this->getTransactionType()) {
-      case self::TYPE_TITLE:
-        if ($old === null) {
-          return pht('Created');
-        }
-
-        return pht('Retitled');
-
-      case self::TYPE_STATUS:
-        $action = ManiphestTaskStatus::getStatusActionName($new);
-        if ($action) {
-          return $action;
-        }
-
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new);
-
-        if ($new_closed && !$old_closed) {
-          return pht('Closed');
-        } else if (!$new_closed && $old_closed) {
-          return pht('Reopened');
-        } else {
-          return pht('Changed Status');
-        }
-
-      case self::TYPE_DESCRIPTION:
-        return pht('Edited');
-
-      case self::TYPE_OWNER:
-        if ($this->getAuthorPHID() == $new) {
-          return pht('Claimed');
-        } else if (!$new) {
-          return pht('Unassigned');
-        } else if (!$old) {
-          return pht('Assigned');
-        } else {
-          return pht('Reassigned');
-        }
-
       case PhabricatorTransactions::TYPE_COLUMNS:
         return pht('Changed Project Column');
-
-      case self::TYPE_PRIORITY:
-        if ($old == ManiphestTaskPriority::getDefaultPriority()) {
-          return pht('Triaged');
-        } else if ($old > $new) {
-          return pht('Lowered Priority');
-        } else {
-          return pht('Raised Priority');
-        }
-
-      case self::TYPE_EDGE:
-      case self::TYPE_ATTACH:
-        return pht('Attached');
-
-      case self::TYPE_UNBLOCK:
-        $old_status = head($old);
-        $new_status = head($new);
-
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
-
-        if ($old_closed && !$new_closed) {
-          return pht('Block');
-        } else if (!$old_closed && $new_closed) {
-          return pht('Unblock');
-        } else {
-          return pht('Blocker');
-        }
-
-      case self::TYPE_MERGED_INTO:
-      case self::TYPE_MERGED_FROM:
-        return pht('Merged');
-
     }
 
     return parent::getActionName();
   }
 
   public function getIcon() {
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
     switch ($this->getTransactionType()) {
-      case self::TYPE_OWNER:
-        return 'fa-user';
-
-      case self::TYPE_TITLE:
-        if ($old === null) {
-          return 'fa-pencil';
-        }
-
-        return 'fa-pencil';
-
-      case self::TYPE_STATUS:
-        $action = ManiphestTaskStatus::getStatusIcon($new);
-        if ($action !== null) {
-          return $action;
-        }
-
-        if (ManiphestTaskStatus::isClosedStatus($new)) {
-          return 'fa-check';
-        } else {
-          return 'fa-pencil';
-        }
-
-      case self::TYPE_DESCRIPTION:
-        return 'fa-pencil';
-
       case PhabricatorTransactions::TYPE_COLUMNS:
         return 'fa-columns';
-
-      case self::TYPE_MERGED_INTO:
-        return 'fa-check';
-      case self::TYPE_MERGED_FROM:
-        return 'fa-compress';
-
-      case self::TYPE_PRIORITY:
-        if ($old == ManiphestTaskPriority::getDefaultPriority()) {
-          return 'fa-arrow-right';
-        } else if ($old > $new) {
-          return 'fa-arrow-down';
-        } else {
-          return 'fa-arrow-up';
-        }
-
-      case self::TYPE_EDGE:
-      case self::TYPE_ATTACH:
-        return 'fa-thumb-tack';
-
-      case self::TYPE_UNBLOCK:
-        return 'fa-shield';
-
     }
 
     return parent::getIcon();
   }
 
 
-
   public function getTitle() {
     $author_phid = $this->getAuthorPHID();
 
@@ -400,244 +124,13 @@
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
-      case PhabricatorTransactions::TYPE_CREATE:
-        return pht(
-          '%s created this task.',
-          $this->renderHandleLink($author_phid));
       case PhabricatorTransactions::TYPE_SUBTYPE:
         return pht(
           '%s changed the subtype of this task from "%s" to "%s".',
           $this->renderHandleLink($author_phid),
           $this->renderSubtypeName($old),
           $this->renderSubtypeName($new));
-      case self::TYPE_TITLE:
-        if ($old === null) {
-          return pht(
-            '%s created this task.',
-            $this->renderHandleLink($author_phid));
-        }
-        return pht(
-          '%s changed the title from "%s" to "%s".',
-          $this->renderHandleLink($author_phid),
-          $old,
-          $new);
-
-      case self::TYPE_DESCRIPTION:
-        return pht(
-          '%s edited the task description.',
-          $this->renderHandleLink($author_phid));
-
-      case self::TYPE_STATUS:
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new);
-
-        $old_name = ManiphestTaskStatus::getTaskStatusName($old);
-        $new_name = ManiphestTaskStatus::getTaskStatusName($new);
-
-        $commit_phid = $this->getMetadataValue('commitPHID');
-
-        if ($new_closed && !$old_closed) {
-          if ($new == ManiphestTaskStatus::getDuplicateStatus()) {
-            if ($commit_phid) {
-              return pht(
-                '%s closed this task as a duplicate by committing %s.',
-                $this->renderHandleLink($author_phid),
-                $this->renderHandleLink($commit_phid));
-            } else {
-              return pht(
-                '%s closed this task as a duplicate.',
-                $this->renderHandleLink($author_phid));
-            }
-          } else {
-            if ($commit_phid) {
-              return pht(
-                '%s closed this task as "%s" by committing %s.',
-                $this->renderHandleLink($author_phid),
-                $new_name,
-                $this->renderHandleLink($commit_phid));
-            } else {
-              return pht(
-                '%s closed this task as "%s".',
-                $this->renderHandleLink($author_phid),
-                $new_name);
-            }
-          }
-        } else if (!$new_closed && $old_closed) {
-          if ($commit_phid) {
-            return pht(
-              '%s reopened this task as "%s" by committing %s.',
-              $this->renderHandleLink($author_phid),
-              $new_name,
-              $this->renderHandleLink($commit_phid));
-          } else {
-            return pht(
-              '%s reopened this task as "%s".',
-              $this->renderHandleLink($author_phid),
-              $new_name);
-          }
-        } else {
-          if ($commit_phid) {
-            return pht(
-              '%s changed the task status from "%s" to "%s" by committing %s.',
-              $this->renderHandleLink($author_phid),
-              $old_name,
-              $new_name,
-              $this->renderHandleLink($commit_phid));
-          } else {
-            return pht(
-              '%s changed the task status from "%s" to "%s".',
-              $this->renderHandleLink($author_phid),
-              $old_name,
-              $new_name);
-          }
-        }
-
-      case self::TYPE_UNBLOCK:
-        $blocker_phid = key($new);
-        $old_status = head($old);
-        $new_status = head($new);
-
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
-
-        $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
-        $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
-
-        if ($this->getMetadataValue('blocker.new')) {
-          return pht(
-            '%s created subtask %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid));
-        } else if ($old_closed && !$new_closed) {
-          return pht(
-            '%s reopened subtask %s as "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $new_name);
-        } else if (!$old_closed && $new_closed) {
-          return pht(
-            '%s closed subtask %s as "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $new_name);
-        } else {
-          return pht(
-            '%s changed the status of subtask %s from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $old_name,
-            $new_name);
-        }
-
-      case self::TYPE_OWNER:
-        if ($author_phid == $new) {
-          return pht(
-            '%s claimed this task.',
-            $this->renderHandleLink($author_phid));
-        } else if (!$new) {
-          return pht(
-            '%s removed %s as the assignee of this task.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($old));
-        } else if (!$old) {
-          return pht(
-            '%s assigned this task to %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($new));
-        } else {
-          return pht(
-            '%s reassigned this task from %s to %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($old),
-            $this->renderHandleLink($new));
-        }
-
-      case self::TYPE_PRIORITY:
-        $old_name = ManiphestTaskPriority::getTaskPriorityName($old);
-        $new_name = ManiphestTaskPriority::getTaskPriorityName($new);
-
-        if ($old == ManiphestTaskPriority::getDefaultPriority()) {
-          return pht(
-            '%s triaged this task as "%s" priority.',
-            $this->renderHandleLink($author_phid),
-            $new_name);
-        } else if ($old > $new) {
-          return pht(
-            '%s lowered the priority of this task from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $old_name,
-            $new_name);
-        } else {
-          return pht(
-            '%s raised the priority of this task from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $old_name,
-            $new_name);
-        }
-
-      case self::TYPE_ATTACH:
-        $old = nonempty($old, array());
-        $new = nonempty($new, array());
-        $new = array_keys(idx($new, 'FILE', array()));
-        $old = array_keys(idx($old, 'FILE', array()));
-
-        $added = array_diff($new, $old);
-        $removed = array_diff($old, $new);
-        if ($added && !$removed) {
-          return pht(
-            '%s attached %s file(s): %s.',
-            $this->renderHandleLink($author_phid),
-            phutil_count($added),
-            $this->renderHandleList($added));
-        } else if ($removed && !$added) {
-          return pht(
-            '%s detached %s file(s): %s.',
-            $this->renderHandleLink($author_phid),
-            phutil_count($removed),
-            $this->renderHandleList($removed));
-        } else {
-          return pht(
-            '%s changed file(s), attached %s: %s; detached %s: %s.',
-            $this->renderHandleLink($author_phid),
-            phutil_count($added),
-            $this->renderHandleList($added),
-            phutil_count($removed),
-            $this->renderHandleList($removed));
-        }
-
-      case self::TYPE_MERGED_INTO:
-        return pht(
-          '%s closed this task as a duplicate of %s.',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($new));
-        break;
-
-      case self::TYPE_MERGED_FROM:
-        return pht(
-          '%s merged %s task(s): %s.',
-          $this->renderHandleLink($author_phid),
-          phutil_count($new),
-          $this->renderHandleList($new));
         break;
-
-      case self::TYPE_POINTS:
-        if ($old === null) {
-          return pht(
-            '%s set the point value for this task to %s.',
-            $this->renderHandleLink($author_phid),
-            $new);
-        } else if ($new === null) {
-          return pht(
-            '%s removed the point value for this task.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s changed the point value for this task from %s to %s.',
-            $this->renderHandleLink($author_phid),
-            $old,
-            $new);
-        }
-
     }
 
     return parent::getTitle();
@@ -651,236 +144,6 @@
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
-      case self::TYPE_TITLE:
-        if ($old === null) {
-          return pht(
-            '%s created %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-
-        return pht(
-          '%s renamed %s from "%s" to "%s".',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($object_phid),
-          $old,
-          $new);
-
-      case self::TYPE_DESCRIPTION:
-        return pht(
-          '%s edited the description of %s.',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($object_phid));
-
-      case self::TYPE_STATUS:
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new);
-
-        $old_name = ManiphestTaskStatus::getTaskStatusName($old);
-        $new_name = ManiphestTaskStatus::getTaskStatusName($new);
-
-        $commit_phid = $this->getMetadataValue('commitPHID');
-
-        if ($new_closed && !$old_closed) {
-          if ($new == ManiphestTaskStatus::getDuplicateStatus()) {
-            if ($commit_phid) {
-              return pht(
-                '%s closed %s as a duplicate by committing %s.',
-                $this->renderHandleLink($author_phid),
-                $this->renderHandleLink($object_phid),
-                $this->renderHandleLink($commit_phid));
-            } else {
-              return pht(
-                '%s closed %s as a duplicate.',
-                $this->renderHandleLink($author_phid),
-                $this->renderHandleLink($object_phid));
-            }
-          } else {
-            if ($commit_phid) {
-              return pht(
-                '%s closed %s as "%s" by committing %s.',
-                $this->renderHandleLink($author_phid),
-                $this->renderHandleLink($object_phid),
-                $new_name,
-                $this->renderHandleLink($commit_phid));
-            } else {
-              return pht(
-                '%s closed %s as "%s".',
-                $this->renderHandleLink($author_phid),
-                $this->renderHandleLink($object_phid),
-                $new_name);
-            }
-          }
-        } else if (!$new_closed && $old_closed) {
-          if ($commit_phid) {
-            return pht(
-              '%s reopened %s as "%s" by committing %s.',
-              $this->renderHandleLink($author_phid),
-              $this->renderHandleLink($object_phid),
-              $new_name,
-              $this->renderHandleLink($commit_phid));
-          } else {
-            return pht(
-              '%s reopened %s as "%s".',
-              $this->renderHandleLink($author_phid),
-              $this->renderHandleLink($object_phid),
-              $new_name);
-          }
-        } else {
-          if ($commit_phid) {
-            return pht(
-              '%s changed the status of %s from "%s" to "%s" by committing %s.',
-              $this->renderHandleLink($author_phid),
-              $this->renderHandleLink($object_phid),
-              $old_name,
-              $new_name,
-              $this->renderHandleLink($commit_phid));
-          } else {
-            return pht(
-              '%s changed the status of %s from "%s" to "%s".',
-              $this->renderHandleLink($author_phid),
-              $this->renderHandleLink($object_phid),
-              $old_name,
-              $new_name);
-          }
-        }
-
-      case self::TYPE_UNBLOCK:
-        $blocker_phid = key($new);
-        $old_status = head($old);
-        $new_status = head($new);
-
-        $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
-        $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
-
-        $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
-        $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
-
-        if ($old_closed && !$new_closed) {
-          return pht(
-            '%s reopened %s, a subtask of %s, as "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $this->renderHandleLink($object_phid),
-            $new_name);
-        } else if (!$old_closed && $new_closed) {
-          return pht(
-            '%s closed %s, a subtask of %s, as "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $this->renderHandleLink($object_phid),
-            $new_name);
-        } else {
-          return pht(
-            '%s changed the status of %s, a subtask of %s, '.
-            'from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($blocker_phid),
-            $this->renderHandleLink($object_phid),
-            $old_name,
-            $new_name);
-        }
-
-      case self::TYPE_OWNER:
-        if ($author_phid == $new) {
-          return pht(
-            '%s claimed %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        } else if (!$new) {
-          return pht(
-            '%s placed %s up for grabs.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        } else if (!$old) {
-          return pht(
-            '%s assigned %s to %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            $this->renderHandleLink($new));
-        } else {
-          return pht(
-            '%s reassigned %s from %s to %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            $this->renderHandleLink($old),
-            $this->renderHandleLink($new));
-        }
-
-      case self::TYPE_PRIORITY:
-        $old_name = ManiphestTaskPriority::getTaskPriorityName($old);
-        $new_name = ManiphestTaskPriority::getTaskPriorityName($new);
-
-        if ($old == ManiphestTaskPriority::getDefaultPriority()) {
-          return pht(
-            '%s triaged %s as "%s" priority.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            $new_name);
-        } else if ($old > $new) {
-          return pht(
-            '%s lowered the priority of %s from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            $old_name,
-            $new_name);
-        } else {
-          return pht(
-            '%s raised the priority of %s from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            $old_name,
-            $new_name);
-        }
-
-      case self::TYPE_ATTACH:
-        $old = nonempty($old, array());
-        $new = nonempty($new, array());
-        $new = array_keys(idx($new, 'FILE', array()));
-        $old = array_keys(idx($old, 'FILE', array()));
-
-        $added = array_diff($new, $old);
-        $removed = array_diff($old, $new);
-        if ($added && !$removed) {
-          return pht(
-            '%s attached %d file(s) of %s: %s',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            count($added),
-            $this->renderHandleList($added));
-        } else if ($removed && !$added) {
-          return pht(
-            '%s detached %d file(s) of %s: %s',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            count($removed),
-            $this->renderHandleList($removed));
-        } else {
-          return pht(
-            '%s changed file(s) for %s, attached %d: %s; detached %d: %s',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid),
-            count($added),
-            $this->renderHandleList($added),
-            count($removed),
-            $this->renderHandleList($removed));
-        }
-
-      case self::TYPE_MERGED_INTO:
-        return pht(
-          '%s merged task %s into %s.',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($object_phid),
-          $this->renderHandleLink($new));
-
-      case self::TYPE_MERGED_FROM:
-        return pht(
-          '%s merged %s task(s) %s into %s.',
-          $this->renderHandleLink($author_phid),
-          phutil_count($new),
-          $this->renderHandleList($new),
-          $this->renderHandleLink($object_phid));
-
       case PhabricatorTransactions::TYPE_SUBTYPE:
         return pht(
           '%s changed the subtype of %s from "%s" to "%s".',
@@ -893,39 +156,14 @@
     return parent::getTitleForFeed();
   }
 
-  private function renderSubtypeName($value) {
-    $object = $this->getObject();
-    $map = $object->newEditEngineSubtypeMap();
-    if (!isset($map[$value])) {
-      return $value;
-    }
-
-    return $map[$value]->getName();
-  }
-
-  public function hasChangeDetails() {
-    switch ($this->getTransactionType()) {
-      case self::TYPE_DESCRIPTION:
-        return true;
-    }
-    return parent::hasChangeDetails();
-  }
-
-  public function renderChangeDetails(PhabricatorUser $viewer) {
-    return $this->renderTextCorpusChangeDetails(
-      $viewer,
-      $this->getOldValue(),
-      $this->getNewValue());
-  }
-
   public function getMailTags() {
     $tags = array();
     switch ($this->getTransactionType()) {
-      case self::TYPE_MERGED_INTO:
-      case self::TYPE_STATUS:
+      case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE:
+      case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_STATUS;
         break;
-      case self::TYPE_OWNER:
+      case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_OWNER;
         break;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
@@ -941,10 +179,10 @@
             break;
         }
         break;
-      case self::TYPE_PRIORITY:
+      case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_PRIORITY;
         break;
-      case self::TYPE_UNBLOCK:
+      case ManiphestTaskUnblockTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_UNBLOCK;
         break;
       case PhabricatorTransactions::TYPE_COLUMNS:
@@ -961,13 +199,12 @@
   }
 
   public function getNoEffectDescription() {
-
     switch ($this->getTransactionType()) {
-      case self::TYPE_STATUS:
+      case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
         return pht('The task already has the selected status.');
-      case self::TYPE_OWNER:
+      case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
         return pht('The task already has the selected owner.');
-      case self::TYPE_PRIORITY:
+      case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE:
         return pht('The task already has the selected priority.');
     }
 
diff --git a/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskAttachTransaction.php
@@ -0,0 +1,91 @@
+<?php
+
+final class ManiphestTaskAttachTransaction
+  extends ManiphestTaskTransactionType {
+
+  // NOTE: this type is deprecated. Keep it around for legacy installs
+  // so any transactions render correctly.
+
+  const TRANSACTIONTYPE = 'attach';
+
+  public function getActionName() {
+    return pht('Attached');
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old = nonempty($old, array());
+    $new = nonempty($new, array());
+    $new = array_keys(idx($new, 'FILE', array()));
+    $old = array_keys(idx($old, 'FILE', array()));
+
+    $added = array_diff($new, $old);
+    $removed = array_diff($old, $new);
+    if ($added && !$removed) {
+      return pht(
+        '%s attached %s file(s): %s.',
+        $this->renderAuthor(),
+        phutil_count($added),
+        $this->renderHandleList($added));
+    } else if ($removed && !$added) {
+      return pht(
+        '%s detached %s file(s): %s.',
+        $this->renderAuthor(),
+        phutil_count($removed),
+        $this->renderHandleList($removed));
+    } else {
+      return pht(
+        '%s changed file(s), attached %s: %s; detached %s: %s.',
+        $this->renderAuthor(),
+        phutil_count($added),
+        $this->renderHandleList($added),
+        phutil_count($removed),
+        $this->renderHandleList($removed));
+    }
+
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old = nonempty($old, array());
+    $new = nonempty($new, array());
+    $new = array_keys(idx($new, 'FILE', array()));
+    $old = array_keys(idx($old, 'FILE', array()));
+
+    $added = array_diff($new, $old);
+    $removed = array_diff($old, $new);
+    if ($added && !$removed) {
+      return pht(
+        '%s attached %d file(s) of %s: %s',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        count($added),
+        $this->renderHandleList($added));
+    } else if ($removed && !$added) {
+      return pht(
+        '%s detached %d file(s) of %s: %s',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        count($removed),
+        $this->renderHandleList($removed));
+    } else {
+      return pht(
+        '%s changed file(s) for %s, attached %d: %s; detached %d: %s',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        count($added),
+        $this->renderHandleList($added),
+        count($removed),
+        $this->renderHandleList($removed));
+    }
+  }
+
+  public function getIcon() {
+    return 'fa-thumb-tack';
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php
@@ -0,0 +1,106 @@
+<?php
+
+final class ManiphestTaskCoverImageTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'cover-image';
+
+  public function generateOldValue($object) {
+    return $object->getCoverImageFilePHID();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $file_phid = $value;
+
+    if ($file_phid) {
+      $file = id(new PhabricatorFileQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs(array($file_phid))
+        ->executeOne();
+    } else {
+      $file = null;
+    }
+
+    if (!$file || !$file->isTransformableImage()) {
+      $object->setProperty('cover.filePHID', null);
+      $object->setProperty('cover.thumbnailPHID', null);
+      return;
+    }
+
+    $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD;
+    $xform = PhabricatorFileTransform::getTransformByKey($xform_key)
+      ->executeTransform($file);
+
+    $object->setProperty('cover.filePHID', $file->getPHID());
+    $object->setProperty('cover.thumbnailPHID', $xform->getPHID());
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old === null) {
+      return pht(
+        '%s set the cover image to %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($new));
+    }
+
+    return pht(
+      '%s updated the cover image to %s.',
+      $this->renderAuthor(),
+      $this->renderHandle($new));
+
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    if ($old === null) {
+      return pht(
+        '%s added a cover image to %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+
+    return pht(
+      '%s updated the cover image for %s.',
+      $this->renderAuthor(),
+      $this->renderObject());
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+    $viewer = $this->getActor();
+
+    foreach ($xactions as $xaction) {
+      $file_phid = $xaction->getNewValue();
+
+      $file = id(new PhabricatorFileQuery())
+        ->setViewer($viewer)
+        ->withPHIDs(array($file_phid))
+        ->executeOne();
+
+      if (!$file) {
+        $errors[] = $this->newInvalidError(
+          pht('"%s" is not a valid file PHID.',
+          $file_phid));
+      } else {
+        if (!$file->isViewableImage()) {
+          $mime_type = $file->getMimeType();
+          $errors[] = $this->newInvalidError(
+            pht('File mime type of "%s" is not a valid viewable image.',
+            $mime_type));
+        }
+      }
+
+    }
+
+    return $errors;
+  }
+
+  public function getIcon() {
+    return 'fa-image';
+  }
+
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php
@@ -0,0 +1,61 @@
+<?php
+
+final class ManiphestTaskDescriptionTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'description';
+
+  public function generateOldValue($object) {
+    return $object->getDescription();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setDescription($value);
+  }
+
+  public function getActionName() {
+    return pht('Edited');
+  }
+
+  public function getTitle() {
+    return pht(
+      '%s updated the task description.',
+      $this->renderAuthor());
+  }
+
+  public function getTitleForFeed() {
+    return pht(
+      '%s updated the task description for %s.',
+      $this->renderAuthor(),
+      $this->renderObject());
+  }
+
+  public function hasChangeDetailView() {
+    return true;
+  }
+
+  public function getMailDiffSectionHeader() {
+    return pht('CHANGES TO TASK DESCRIPTION');
+  }
+
+  public function newChangeDetailView() {
+    $viewer = $this->getViewer();
+
+    return id(new PhabricatorApplicationTransactionTextDiffDetailView())
+      ->setViewer($viewer)
+      ->setOldText($this->getOldValue())
+      ->setNewText($this->getNewValue());
+  }
+
+  public function newRemarkupChanges() {
+    $changes = array();
+
+    $changes[] = $this->newRemarkupChange()
+      ->setOldValue($this->getOldValue())
+      ->setNewValue($this->getNewValue());
+
+    return $changes;
+  }
+
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php
@@ -0,0 +1,31 @@
+<?php
+
+final class ManiphestTaskEdgeTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'edge';
+
+  public function generateOldValue($object) {
+    return null;
+  }
+
+  public function shouldHide() {
+    $commit_phid = $this->getMetadataValue('commitPHID');
+    $edge_type = $this->getMetadataValue('edge:type');
+
+    if ($edge_type == ManiphestTaskHasCommitEdgeType::EDGECONST) {
+      if ($commit_phid) {
+        return true;
+      }
+    }
+  }
+
+  public function getActionName() {
+    return pht('Attached');
+  }
+
+  public function getIcon() {
+    return 'fa-thumb-tack';
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php
@@ -0,0 +1,45 @@
+<?php
+
+final class ManiphestTaskMergedFromTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'mergedfrom';
+
+  public function generateOldValue($object) {
+    return null;
+  }
+
+  public function getActionName() {
+    return pht('Merged');
+  }
+
+  public function getTitle() {
+    $new = $this->getNewValue();
+
+    return pht(
+      '%s merged %s task(s): %s.',
+      $this->renderAuthor(),
+      phutil_count($new),
+      $this->renderHandleList($new));
+  }
+
+  public function getTitleForFeed() {
+    $new = $this->getNewValue();
+
+    return pht(
+      '%s merged %s task(s) %s into %s.',
+      $this->renderAuthor(),
+      phutil_count($new),
+      $this->renderHandleList($new),
+      $this->renderObject());
+  }
+
+  public function getIcon() {
+    return 'fa-compress';
+  }
+
+  public function getColor() {
+    return 'orange';
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php
@@ -0,0 +1,47 @@
+<?php
+
+final class ManiphestTaskMergedIntoTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'mergedinto';
+
+  public function generateOldValue($object) {
+    return null;
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
+  }
+
+  public function getActionName() {
+    return pht('Merged');
+  }
+
+  public function getTitle() {
+    $new = $this->getNewValue();
+
+    return pht(
+      '%s closed this task as a duplicate of %s.',
+      $this->renderAuthor(),
+      $this->renderHandle($new));
+  }
+
+  public function getTitleForFeed() {
+    $new = $this->getNewValue();
+
+    return pht(
+      '%s merged task %s into %s.',
+      $this->renderAuthor(),
+      $this->renderObject(),
+      $this->renderHandle($new));
+  }
+
+  public function getIcon() {
+    return 'fa-check';
+  }
+
+  public function getColor() {
+    return 'indigo';
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php
@@ -0,0 +1,158 @@
+<?php
+
+final class ManiphestTaskOwnerTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'reassign';
+
+  public function generateOldValue($object) {
+    return nonempty($object->getOwnerPHID(), null);
+  }
+
+  public function applyInternalEffects($object, $value) {
+    // Update the "ownerOrdering" column to contain the full name of the
+    // owner, if the task is assigned.
+
+    $handle = null;
+    if ($value) {
+      $handle = id(new PhabricatorHandleQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs(array($value))
+        ->executeOne();
+    }
+
+    if ($handle) {
+      $object->setOwnerOrdering($handle->getName());
+    } else {
+      $object->setOwnerOrdering(null);
+    }
+
+    $object->setOwnerPHID($value);
+  }
+
+  public function getActionStrength() {
+    return 1.2;
+  }
+
+  public function getActionName() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($this->getAuthorPHID() == $new) {
+      return pht('Claimed');
+    } else if (!$new) {
+      return pht('Unassigned');
+    } else if (!$old) {
+      return pht('Assigned');
+    } else {
+      return pht('Reassigned');
+    }
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($this->getAuthorPHID() == $new) {
+      return pht(
+        '%s claimed this task.',
+        $this->renderAuthor());
+    } else if (!$new) {
+      return pht(
+        '%s removed %s as the assignee of this task.',
+        $this->renderAuthor(),
+        $this->renderHandle($old));
+    } else if (!$old) {
+      return pht(
+        '%s assigned this task to %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($new));
+    } else {
+      return pht(
+        '%s reassigned this task from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($old),
+        $this->renderHandle($new));
+    }
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($this->getAuthorPHID() == $new) {
+      return pht(
+        '%s claimed %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    } else if (!$new) {
+      return pht(
+        '%s placed %s up for grabs.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    } else if (!$old) {
+      return pht(
+        '%s assigned %s to %s.',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        $this->renderHandle($new));
+    } else {
+      return pht(
+        '%s reassigned %s from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        $this->renderHandle($old),
+        $this->renderHandle($new));
+    }
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    foreach ($xactions as $xaction) {
+      $old = $xaction->getOldValue();
+      $new = $xaction->getNewValue();
+      if (!strlen($new)) {
+        continue;
+      }
+
+      if ($new === $old) {
+        continue;
+      }
+
+      $assignee_list = id(new PhabricatorPeopleQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs(array($new))
+        ->execute();
+
+      if (!$assignee_list) {
+        $errors[] = $this->newInvalidError(
+          pht('User "%s" is not a valid user.',
+          $new));
+      }
+    }
+    return $errors;
+  }
+
+  public function getIcon() {
+    return 'fa-user';
+  }
+
+  public function getColor() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($this->getAuthorPHID() == $new) {
+      return 'green';
+    } else if (!$new) {
+      return 'black';
+    } else if (!$old) {
+      return 'green';
+    } else {
+      return 'green';
+    }
+
+  }
+
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskParentTransaction.php
@@ -0,0 +1,60 @@
+<?php
+
+final class ManiphestTaskParentTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'parent';
+
+  public function generateOldValue($object) {
+    return null;
+  }
+
+  public function shouldHide() {
+    return true;
+  }
+
+  public function applyExternalEffects($object, $value) {
+    $parent_phid = $value;
+    $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
+    $task_phid = $object->getPHID();
+
+    id(new PhabricatorEdgeEditor())
+      ->addEdge($parent_phid, $parent_type, $task_phid)
+      ->save();
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    $with_effect = array();
+    foreach ($xactions as $xaction) {
+      $task_phid = $xaction->getNewValue();
+      if (!$task_phid) {
+        continue;
+      }
+
+      $with_effect[] = $xaction;
+
+      $task = id(new ManiphestTaskQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs(array($task_phid))
+        ->executeOne();
+      if (!$task) {
+        $errors[] = $this->newInvalidError(
+          pht(
+            'Parent task identifier "%s" does not identify a visible '.
+            'task.',
+            $task_phid));
+      }
+    }
+
+    if ($with_effect && !$this->isNewObject()) {
+      $errors[] = $this->newInvalidError(
+        pht(
+          'You can only select a parent task when creating a '.
+          'transaction for the first time.'));
+    }
+
+    return $errors;
+  }
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php
@@ -0,0 +1,76 @@
+<?php
+
+final class ManiphestTaskPointsTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'points';
+
+  public function generateOldValue($object) {
+    return $object->getPoints();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    if (!strlen($value)) {
+      $value = null;
+    }
+    if ($value !== null) {
+      $value = (double)$value;
+    }
+    $object->setPoints($value);
+  }
+
+  public function shouldHideForFeed() {
+    return true;
+  }
+
+  public function shouldHide() {
+    if (!ManiphestTaskPoints::getIsEnabled()) {
+      return true;
+    }
+    return false;
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old === null) {
+      return pht(
+        '%s set the point value for this task to %s.',
+        $this->renderAuthor(),
+        $this->renderNewValue());
+    } else if ($new === null) {
+      return pht(
+        '%s removed the point value for this task.',
+        $this->renderAuthor());
+    } else {
+      return pht(
+        '%s changed the point value for this task from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderOldValue(),
+        $this->renderNewValue());
+    }
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    foreach ($xactions as $xaction) {
+      $new = $xaction->getNewValue();
+      if (strlen($new) && !is_numeric($new)) {
+        $errors[] = $this->newInvalidError(
+          pht('Points value must be numeric or empty.'));
+        continue;
+      }
+
+      if ((double)$new < 0) {
+        $errors[] = $this->newInvalidError(
+          pht('Points value must be nonnegative.'));
+        continue;
+      }
+    }
+
+    return $errors;
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php
@@ -0,0 +1,119 @@
+<?php
+
+final class ManiphestTaskPriorityTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'priority';
+
+  public function generateOldValue($object) {
+    if ($this->isNewObject()) {
+      return null;
+    }
+    return $object->getPriority();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setPriority($value);
+  }
+
+  public function getActionStrength() {
+    return 1.1;
+  }
+
+  public function getActionName() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old == ManiphestTaskPriority::getDefaultPriority()) {
+      return pht('Triaged');
+    } else if ($old > $new) {
+      return pht('Lowered Priority');
+    } else {
+      return pht('Raised Priority');
+    }
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old_name = ManiphestTaskPriority::getTaskPriorityName($old);
+    $new_name = ManiphestTaskPriority::getTaskPriorityName($new);
+
+    if ($old == ManiphestTaskPriority::getDefaultPriority()) {
+      return pht(
+        '%s triaged this task as %s priority.',
+        $this->renderAuthor(),
+        $this->renderValue($new_name));
+    } else if ($old > $new) {
+      return pht(
+        '%s lowered the priority of this task from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    } else {
+      return pht(
+        '%s raised the priority of this task from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    }
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old_name = ManiphestTaskPriority::getTaskPriorityName($old);
+    $new_name = ManiphestTaskPriority::getTaskPriorityName($new);
+
+    if ($old == ManiphestTaskPriority::getDefaultPriority()) {
+      return pht(
+        '%s triaged %s as %s priority.',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        $this->renderValue($new_name));
+    } else if ($old > $new) {
+      return pht(
+        '%s lowered the priority of %s from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    } else {
+      return pht(
+        '%s raised the priority of %s from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderObject(),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    }
+  }
+
+  public function getIcon() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old == ManiphestTaskPriority::getDefaultPriority()) {
+      return 'fa-arrow-right';
+    } else if ($old > $new) {
+      return 'fa-arrow-down';
+    } else {
+      return 'fa-arrow-up';
+    }
+  }
+
+  public function getColor() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old == ManiphestTaskPriority::getDefaultPriority()) {
+      return 'green';
+    } else if ($old > $new) {
+      return 'grey';
+    } else {
+      return 'yellow';
+    }
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php
@@ -0,0 +1,232 @@
+<?php
+
+final class ManiphestTaskStatusTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'status';
+
+  public function generateOldValue($object) {
+    if ($this->isNewObject()) {
+      return null;
+    }
+    return $object->getStatus();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setStatus($value);
+  }
+
+  public function shouldHide() {
+    if ($this->getOldValue() === null) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public function getActionStrength() {
+    return 1.3;
+  }
+
+  public function getActionName() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $action = ManiphestTaskStatus::getStatusActionName($new);
+    if ($action) {
+      return $action;
+    }
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new);
+
+    if ($new_closed && !$old_closed) {
+      return pht('Closed');
+    } else if (!$new_closed && $old_closed) {
+      return pht('Reopened');
+    } else {
+      return pht('Changed Status');
+    }
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new);
+
+    $old_name = ManiphestTaskStatus::getTaskStatusName($old);
+    $new_name = ManiphestTaskStatus::getTaskStatusName($new);
+
+    $commit_phid = $this->getMetadataValue('commitPHID');
+
+    if ($new_closed && !$old_closed) {
+      if ($new == ManiphestTaskStatus::getDuplicateStatus()) {
+        if ($commit_phid) {
+          return pht(
+            '%s closed this task as a duplicate by committing %s.',
+            $this->renderAuthor(),
+            $this->renderHandle($commit_phid));
+        } else {
+          return pht(
+            '%s closed this task as a duplicate.',
+            $this->renderAuthor());
+        }
+      } else {
+        if ($commit_phid) {
+          return pht(
+            '%s closed this task as %s by committing %s.',
+            $this->renderAuthor(),
+            $this->renderValue($new_name),
+            $this->renderHandle($commit_phid));
+        } else {
+          return pht(
+            '%s closed this task as %s.',
+            $this->renderAuthor(),
+            $this->renderValue($new_name));
+        }
+      }
+    } else if (!$new_closed && $old_closed) {
+      if ($commit_phid) {
+        return pht(
+          '%s reopened this task as %s by committing %s.',
+          $this->renderAuthor(),
+          $this->renderValue($new_name),
+          $this->renderHandle($commit_phid));
+      } else {
+        return pht(
+          '%s reopened this task as %s.',
+          $this->renderAuthor(),
+          $this->renderValue($new_name));
+      }
+    } else {
+      if ($commit_phid) {
+        return pht(
+          '%s changed the task status from %s to %s by committing %s.',
+          $this->renderAuthor(),
+          $this->renderValue($old_name),
+          $this->renderValue($new_name),
+          $this->renderHandle($commit_phid));
+      } else {
+        return pht(
+          '%s changed the task status from %s to %s.',
+          $this->renderAuthor(),
+          $this->renderValue($old_name),
+          $this->renderValue($new_name));
+      }
+    }
+
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new);
+
+    $old_name = ManiphestTaskStatus::getTaskStatusName($old);
+    $new_name = ManiphestTaskStatus::getTaskStatusName($new);
+
+    $commit_phid = $this->getMetadataValue('commitPHID');
+
+    if ($new_closed && !$old_closed) {
+      if ($new == ManiphestTaskStatus::getDuplicateStatus()) {
+        if ($commit_phid) {
+          return pht(
+            '%s closed %s as a duplicate by committing %s.',
+            $this->renderAuthor(),
+            $this->renderObject(),
+            $this->renderHandle($commit_phid));
+        } else {
+          return pht(
+            '%s closed %s as a duplicate.',
+            $this->renderAuthor(),
+            $this->renderObject());
+        }
+      } else {
+        if ($commit_phid) {
+          return pht(
+            '%s closed %s as %s by committing %s.',
+            $this->renderAuthor(),
+            $this->renderObject(),
+            $this->renderValue($new_name),
+            $this->renderHandle($commit_phid));
+        } else {
+          return pht(
+            '%s closed %s as %s.',
+            $this->renderAuthor(),
+            $this->renderObject(),
+            $this->renderValue($new_name));
+        }
+      }
+    } else if (!$new_closed && $old_closed) {
+      if ($commit_phid) {
+        return pht(
+          '%s reopened %s as %s by committing %s.',
+          $this->renderAuthor(),
+          $this->renderObject(),
+          $this->renderValue($new_name),
+          $this->renderHandle($commit_phid));
+      } else {
+        return pht(
+          '%s reopened %s as "%s".',
+          $this->renderAuthor(),
+          $this->renderObject(),
+          $new_name);
+      }
+    } else {
+      if ($commit_phid) {
+        return pht(
+          '%s changed the status of %s from %s to %s by committing %s.',
+          $this->renderAuthor(),
+          $this->renderObject(),
+          $this->renderValue($old_name),
+          $this->renderValue($new_name),
+          $this->renderHandle($commit_phid));
+      } else {
+        return pht(
+          '%s changed the status of %s from %s to %s.',
+          $this->renderAuthor(),
+          $this->renderObject(),
+          $this->renderValue($old_name),
+          $this->renderValue($new_name));
+      }
+    }
+  }
+
+  public function getIcon() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $action = ManiphestTaskStatus::getStatusIcon($new);
+    if ($action !== null) {
+      return $action;
+    }
+
+    if (ManiphestTaskStatus::isClosedStatus($new)) {
+      return 'fa-check';
+    } else {
+      return 'fa-pencil';
+    }
+  }
+
+  public function getColor() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $color = ManiphestTaskStatus::getStatusColor($new);
+    if ($color !== null) {
+      return $color;
+    }
+
+    if (ManiphestTaskStatus::isOpenStatus($new)) {
+      return 'green';
+    } else {
+      return 'indigo';
+    }
+
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php
@@ -0,0 +1,21 @@
+<?php
+
+final class ManiphestTaskSubpriorityTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'subpriority';
+
+  public function generateOldValue($object) {
+    return $object->getSubpriority();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setSubpriority($value);
+  }
+
+  public function shouldHide() {
+    return true;
+  }
+
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php
@@ -0,0 +1,74 @@
+<?php
+
+final class ManiphestTaskTitleTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'title';
+
+  public function generateOldValue($object) {
+    return $object->getTitle();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setTitle($value);
+  }
+
+  public function getActionStrength() {
+    return 1.4;
+  }
+
+  public function getActionName() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+    if ($old === null) {
+      return pht('Created');
+    }
+
+    return pht('Retitled');
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    if ($old === null) {
+      return pht(
+        '%s created this task.',
+        $this->renderAuthor());
+    }
+
+    return pht(
+      '%s changed the title from %s to %s.',
+      $this->renderAuthor(),
+      $this->renderOldValue(),
+      $this->renderNewValue());
+
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    if ($old === null) {
+      return pht(
+        '%s created %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+
+    return pht(
+      '%s changed %s title from %s to %s.',
+      $this->renderAuthor(),
+      $this->renderObject(),
+      $this->renderOldValue(),
+      $this->renderNewValue());
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    if ($this->isEmptyTextTransaction($object->getTitle(), $xactions)) {
+      $errors[] = $this->newRequiredError(
+        pht('Tasks must have a title.'));
+    }
+
+    return $errors;
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskTransactionType.php
@@ -0,0 +1,16 @@
+<?php
+
+abstract class ManiphestTaskTransactionType
+  extends PhabricatorModularTransactionType {
+
+  public function renderSubtypeName($value) {
+    $object = $this->getObject();
+    $map = $object->newEditEngineSubtypeMap();
+    if (!isset($map[$value])) {
+      return $value;
+    }
+
+    return $map[$value]->getName();
+  }
+
+}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php
@@ -0,0 +1,125 @@
+<?php
+
+final class ManiphestTaskUnblockTransaction
+  extends ManiphestTaskTransactionType {
+
+  const TRANSACTIONTYPE = 'unblock';
+
+  public function generateOldValue($object) {
+    return null;
+  }
+
+  public function shouldHideForFeed() {
+    // Hide "alice created X, a task blocking Y." from feed because it
+    // will almost always appear adjacent to "alice created Y".
+    $is_new = $this->getMetadataValue('blocker.new');
+    if ($is_new) {
+      return true;
+    }
+  }
+
+  public function getActionName() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $old_status = head($old);
+    $new_status = head($new);
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
+
+    if ($old_closed && !$new_closed) {
+      return pht('Block');
+    } else if (!$old_closed && $new_closed) {
+      return pht('Unblock');
+    } else {
+      return pht('Blocker');
+    }
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    $blocker_phid = key($new);
+    $old_status = head($old);
+    $new_status = head($new);
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
+
+    $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
+    $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
+
+    if ($this->getMetadataValue('blocker.new')) {
+      return pht(
+        '%s created subtask %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid));
+    } else if ($old_closed && !$new_closed) {
+      return pht(
+        '%s reopened subtask %s as %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderValue($new_name));
+    } else if (!$old_closed && $new_closed) {
+      return pht(
+        '%s closed subtask %s as %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderValue($new_name));
+    } else {
+      return pht(
+        '%s changed the status of subtask %s from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    }
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+    $blocker_phid = key($new);
+    $old_status = head($old);
+    $new_status = head($new);
+
+    $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
+    $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
+
+    $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
+    $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
+
+    if ($old_closed && !$new_closed) {
+      return pht(
+        '%s reopened %s, a subtask of %s, as %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderObject(),
+        $this->renderValue($new_name));
+    } else if (!$old_closed && $new_closed) {
+      return pht(
+        '%s closed %s, a subtask of %s, as %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderObject(),
+        $this->renderValue($new_name));
+    } else {
+      return pht(
+        '%s changed the status of %s, a subtask of %s, '.
+        'from %s to %s.',
+        $this->renderAuthor(),
+        $this->renderHandle($blocker_phid),
+        $this->renderObject(),
+        $this->renderValue($old_name),
+        $this->renderValue($new_name));
+    }
+  }
+
+  public function getIcon() {
+    return 'fa-shield';
+  }
+
+
+}
diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php
--- a/src/applications/nuance/item/NuanceGitHubEventItemType.php
+++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php
@@ -390,12 +390,14 @@
       $state = $xobj->getProperty('task.state');
 
       $xactions[] = id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+        ->setTransactionType(
+          ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
         ->setNewValue($title)
         ->setDateCreated($created);
 
       $xactions[] = id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
+        ->setTransactionType(
+          ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
         ->setNewValue($description)
         ->setDateCreated($created);
 
diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
--- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
+++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
@@ -1337,7 +1337,7 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+      ->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
       ->setNewValue($name);
 
     if ($projects) {
diff --git a/src/applications/project/controller/PhabricatorProjectCoverController.php b/src/applications/project/controller/PhabricatorProjectCoverController.php
--- a/src/applications/project/controller/PhabricatorProjectCoverController.php
+++ b/src/applications/project/controller/PhabricatorProjectCoverController.php
@@ -36,7 +36,7 @@
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE)
+      ->setTransactionType(ManiphestTaskCoverImageTransaction::TRANSACTIONTYPE)
       ->setNewValue($file->getPHID());
 
     $editor = id(new ManiphestTransactionEditor())
diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php
--- a/src/applications/project/controller/PhabricatorProjectMoveController.php
+++ b/src/applications/project/controller/PhabricatorProjectMoveController.php
@@ -153,10 +153,11 @@
     $xactions = array();
     if ($pri !== null) {
       $xactions[] = id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
+        ->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
         ->setNewValue($pri);
       $xactions[] = id(new ManiphestTransaction())
-        ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
+        ->setTransactionType(
+          ManiphestTaskSubpriorityTransaction::TRANSACTIONTYPE)
         ->setNewValue($sub);
     }
 
diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
--- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
@@ -367,7 +367,8 @@
       if ($status) {
         if ($task->getStatus() != $status) {
           $xactions[] = id(new ManiphestTransaction())
-            ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
+            ->setTransactionType(
+              ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
             ->setMetadataValue('commitPHID', $commit->getPHID())
             ->setNewValue($status);