diff --git a/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20170504.1.slowvote.shuffle.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll
+  MODIFY shuffle BOOL NOT NULL DEFAULT 0;
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
@@ -3886,10 +3886,12 @@
     'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php',
     'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
     'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php',
+    'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php',
     'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php',
     'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php',
     'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php',
     'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php',
+    'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php',
     'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php',
     'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php',
     'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php',
@@ -3899,12 +3901,16 @@
     'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php',
     'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php',
     'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php',
+    'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php',
     'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php',
+    'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php',
     'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php',
     'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php',
+    'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php',
     'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php',
     'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php',
     'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php',
+    'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php',
     'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php',
     'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php',
     'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php',
@@ -9292,10 +9298,12 @@
     'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
     'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController',
+    'PhabricatorSlowvoteCloseTransaction' => 'PhabricatorSlowvoteTransactionType',
     'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteController' => 'PhabricatorController',
     'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability',
+    'PhabricatorSlowvoteDescriptionTransaction' => 'PhabricatorSlowvoteTransactionType',
     'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
@@ -9315,12 +9323,16 @@
     'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'PhabricatorSlowvoteQuestionTransaction' => 'PhabricatorSlowvoteTransactionType',
     'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
+    'PhabricatorSlowvoteResponsesTransaction' => 'PhabricatorSlowvoteTransactionType',
     'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine',
-    'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction',
+    'PhabricatorSlowvoteShuffleTransaction' => 'PhabricatorSlowvoteTransactionType',
+    'PhabricatorSlowvoteTransaction' => 'PhabricatorModularTransaction',
     'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+    'PhabricatorSlowvoteTransactionType' => 'PhabricatorModularTransactionType',
     'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlug' => 'Phobject',
     'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
@@ -32,7 +32,8 @@
       $xactions = array();
 
       $xactions[] = id(new PhabricatorSlowvoteTransaction())
-        ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_CLOSE)
+        ->setTransactionType(
+            PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE)
         ->setNewValue($new_status);
 
       id(new PhabricatorSlowvoteEditor())
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
@@ -77,23 +77,32 @@
         }
       }
 
-      $xactions = array();
       $template = id(new PhabricatorSlowvoteTransaction());
+      $xactions = array();
+
+      if ($is_new) {
+        $xactions[] = id(new PhabricatorSlowvoteTransaction())
+          ->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
+      }
 
       $xactions[] = id(clone $template)
-        ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_QUESTION)
+        ->setTransactionType(
+            PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE)
         ->setNewValue($v_question);
 
       $xactions[] = id(clone $template)
-        ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION)
+        ->setTransactionType(
+            PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE)
         ->setNewValue($v_description);
 
       $xactions[] = id(clone $template)
-        ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_RESPONSES)
+        ->setTransactionType(
+            PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE)
         ->setNewValue($v_responses);
 
       $xactions[] = id(clone $template)
-        ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE)
+        ->setTransactionType(
+            PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE)
         ->setNewValue($v_shuffle);
 
       $xactions[] = id(clone $template)
diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
--- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
+++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
@@ -13,104 +13,12 @@
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
-
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
 
-    $types[] = PhabricatorSlowvoteTransaction::TYPE_QUESTION;
-    $types[] = PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION;
-    $types[] = PhabricatorSlowvoteTransaction::TYPE_RESPONSES;
-    $types[] = PhabricatorSlowvoteTransaction::TYPE_SHUFFLE;
-    $types[] = PhabricatorSlowvoteTransaction::TYPE_CLOSE;
-
     return $types;
   }
 
-  protected function transactionHasEffect(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-
-    $old = $xaction->getOldValue();
-    $new = $xaction->getNewValue();
-
-    switch ($xaction->getTransactionType()) {
-      case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
-        if ($old === null) {
-          return true;
-        }
-        return ((int)$old !== (int)$new);
-      case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
-        if ($old === null) {
-          return true;
-        }
-        return ((bool)$old !== (bool)$new);
-    }
-
-    return parent::transactionHasEffect($object, $xaction);
-  }
-
-
-  protected function getCustomTransactionOldValue(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-
-    switch ($xaction->getTransactionType()) {
-      case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
-        return $object->getQuestion();
-      case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
-        return $object->getDescription();
-      case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
-        return $object->getResponseVisibility();
-      case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
-        return $object->getShuffle();
-      case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
-        return $object->getIsClosed();
-    }
-  }
-
-  protected function getCustomTransactionNewValue(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-
-    switch ($xaction->getTransactionType()) {
-      case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
-      case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
-      case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
-      case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
-      case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
-        return $xaction->getNewValue();
-    }
-  }
-
-  protected function applyCustomInternalTransaction(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-
-    switch ($xaction->getTransactionType()) {
-      case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
-        $object->setQuestion($xaction->getNewValue());
-        break;
-      case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
-        $object->setDescription($xaction->getNewValue());
-        break;
-      case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
-        $object->setResponseVisibility($xaction->getNewValue());
-        break;
-      case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
-        $object->setShuffle($xaction->getNewValue());
-        break;
-      case PhabricatorSlowvoteTransaction::TYPE_CLOSE:
-        $object->setIsClosed((int)$xaction->getNewValue());
-        break;
-    }
-  }
-
-  protected function applyCustomExternalTransaction(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    return;
-  }
-
-    protected function shouldSendMail(
+  protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
--- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
@@ -21,8 +21,8 @@
   protected $question;
   protected $description;
   protected $authorPHID;
-  protected $responseVisibility;
-  protected $shuffle;
+  protected $responseVisibility = 0;
+  protected $shuffle = 0;
   protected $method;
   protected $mailKey;
   protected $viewPolicy;
@@ -54,7 +54,7 @@
       self::CONFIG_COLUMN_SCHEMA => array(
         'question' => 'text255',
         'responseVisibility' => 'uint32',
-        'shuffle' => 'uint32',
+        'shuffle' => 'bool',
         'method' => 'uint32',
         'description' => 'text',
         'isClosed' => 'bool',
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
@@ -1,13 +1,7 @@
 <?php
 
 final class PhabricatorSlowvoteTransaction
-  extends PhabricatorApplicationTransaction {
-
-  const TYPE_QUESTION     = 'vote:question';
-  const TYPE_DESCRIPTION  = 'vote:description';
-  const TYPE_RESPONSES    = 'vote:responses';
-  const TYPE_SHUFFLE      = 'vote:shuffle';
-  const TYPE_CLOSE        = 'vote:close';
+  extends PhabricatorModularTransaction {
 
   const MAILTAG_DETAILS = 'vote:details';
   const MAILTAG_RESPONSES = 'vote:responses';
@@ -25,237 +19,21 @@
     return new PhabricatorSlowvoteTransactionComment();
   }
 
-  public function shouldHide() {
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_DESCRIPTION:
-      case self::TYPE_RESPONSES:
-      case self::TYPE_SHUFFLE:
-      case self::TYPE_CLOSE:
-        return ($old === null);
-    }
-
-    return parent::shouldHide();
-  }
-
-  public function getTitle() {
-    $author_phid = $this->getAuthorPHID();
-
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_QUESTION:
-        if ($old === null) {
-          return pht(
-            '%s created this poll.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s changed the poll question from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $old,
-            $new);
-        }
-        break;
-      case self::TYPE_DESCRIPTION:
-        return pht(
-          '%s updated the description for this poll.',
-          $this->renderHandleLink($author_phid));
-      case self::TYPE_RESPONSES:
-        // TODO: This could be more detailed
-        return pht(
-          '%s changed who can see the responses.',
-          $this->renderHandleLink($author_phid));
-      case self::TYPE_SHUFFLE:
-        if ($new) {
-          return pht(
-            '%s made poll responses appear in a random order.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s made poll responses appear in a fixed order.',
-            $this->renderHandleLink($author_phid));
-        }
-        break;
-      case self::TYPE_CLOSE:
-        if ($new) {
-          return pht(
-            '%s closed this poll.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s reopened this poll.',
-            $this->renderHandleLink($author_phid));
-        }
-
-        break;
-    }
-
-    return parent::getTitle();
-  }
-
-  public function getRemarkupBlocks() {
-    $blocks = parent::getRemarkupBlocks();
-
-    $type = $this->getTransactionType();
-    switch ($type) {
-      case self::TYPE_DESCRIPTION:
-        $blocks[] = $this->getNewValue();
-        break;
-    }
-
-    return $blocks;
-  }
-
-  public function getTitleForFeed() {
-    $author_phid = $this->getAuthorPHID();
-    $object_phid = $this->getObjectPHID();
-
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    $type = $this->getTransactionType();
-    switch ($type) {
-      case self::TYPE_QUESTION:
-        if ($old === null) {
-          return pht(
-            '%s created %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-        } else {
-          return pht(
-            '%s renamed %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-      break;
-      case self::TYPE_DESCRIPTION:
-        if ($old === null) {
-          return pht(
-            '%s set the description of %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-        } else {
-          return pht(
-            '%s edited the description of %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-      break;
-      case self::TYPE_RESPONSES:
-        // TODO: This could be more detailed
-        return pht(
-          '%s changed who can see the responses of %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-      case self::TYPE_SHUFFLE:
-        if ($new) {
-          return pht(
-            '%s made %s responses appear in a random order.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-        } else {
-          return pht(
-            '%s made %s responses appear in a fixed order.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-        case self::TYPE_CLOSE:
-        if ($new) {
-          return pht(
-            '%s closed %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-        } else {
-          return pht(
-            '%s reopened %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-      break;
-    }
-
-    return parent::getTitleForFeed();
-  }
-
-  public function getIcon() {
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_QUESTION:
-        if ($old === null) {
-          return 'fa-plus';
-        } else {
-          return 'fa-pencil';
-        }
-      case self::TYPE_DESCRIPTION:
-      case self::TYPE_RESPONSES:
-        return 'fa-pencil';
-      case self::TYPE_SHUFFLE:
-        return 'fa-refresh';
-      case self::TYPE_CLOSE:
-        if ($new) {
-          return 'fa-ban';
-        } else {
-          return 'fa-pencil';
-        }
-    }
-
-    return parent::getIcon();
-  }
-
-
-  public function getColor() {
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_QUESTION:
-      case self::TYPE_DESCRIPTION:
-      case self::TYPE_RESPONSES:
-      case self::TYPE_SHUFFLE:
-      case self::TYPE_CLOSE:
-        return PhabricatorTransactions::COLOR_BLUE;
-    }
-
-    return parent::getColor();
-  }
-
-  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 getBaseTransactionClass() {
+    return 'PhabricatorSlowvoteTransactionType';
   }
 
   public function getMailTags() {
     $tags = parent::getMailTags();
 
     switch ($this->getTransactionType()) {
-      case self::TYPE_QUESTION:
-      case self::TYPE_DESCRIPTION:
-      case self::TYPE_SHUFFLE:
-      case self::TYPE_CLOSE:
+      case PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE:
+      case PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE:
+      case PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE:
+      case PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_DETAILS;
         break;
-      case self::TYPE_RESPONSES:
+      case PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE:
         $tags[] = self::MAILTAG_RESPONSES;
         break;
       default:
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteCloseTransaction.php
@@ -0,0 +1,60 @@
+<?php
+
+final class PhabricatorSlowvoteCloseTransaction
+  extends PhabricatorSlowvoteTransactionType {
+
+  const TRANSACTIONTYPE = 'vote:close';
+
+  public function generateOldValue($object) {
+    return (bool)$object->getIsClosed();
+  }
+
+  public function generateNewValue($object, $value) {
+    return (bool)$value;
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setIsClosed((int)$value);
+  }
+
+  public function getTitle() {
+    $new = $this->getNewValue();
+
+    if ($new) {
+      return pht(
+        '%s closed this poll.',
+        $this->renderAuthor());
+    } else {
+      return pht(
+        '%s reopened this poll.',
+        $this->renderAuthor());
+    }
+  }
+
+  public function getTitleForFeed() {
+    $new = $this->getNewValue();
+
+    if ($new) {
+      return pht(
+        '%s closed %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    } else {
+      return pht(
+        '%s reopened %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+  }
+
+  public function getIcon() {
+    $new = $this->getNewValue();
+
+    if ($new) {
+      return 'fa-ban';
+    } else {
+      return 'fa-pencil';
+    }
+  }
+
+}
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteDescriptionTransaction.php
@@ -0,0 +1,60 @@
+<?php
+
+final class PhabricatorSlowvoteDescriptionTransaction
+  extends PhabricatorSlowvoteTransactionType {
+
+  const TRANSACTIONTYPE = 'vote:description';
+
+  public function generateOldValue($object) {
+    return $object->getDescription();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setDescription($value);
+  }
+
+  public function getTitle() {
+    return pht(
+      '%s updated the description for this poll.',
+      $this->renderAuthor());
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+
+    if ($old === null) {
+      return pht(
+        '%s set the description of %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+
+    } else {
+      return pht(
+        '%s edited the description of %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+  }
+
+  public function hasChangeDetails() {
+    return true;
+  }
+
+  public function newChangeDetailView() {
+    return $this->renderTextCorpusChangeDetails(
+      $this->getViewer(),
+      $this->getOldValue(),
+      $this->getNewValue());
+  }
+
+  public function newRemarkupChanges() {
+    $changes = array();
+
+    $changes[] = $this->newRemarkupChange()
+      ->setOldValue($this->getOldValue())
+      ->setNewValue($this->getNewValue());
+
+    return $changes;
+  }
+
+}
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteQuestionTransaction.php
@@ -0,0 +1,70 @@
+<?php
+
+final class PhabricatorSlowvoteQuestionTransaction
+  extends PhabricatorSlowvoteTransactionType {
+
+  const TRANSACTIONTYPE = 'vote:question';
+
+  public function generateOldValue($object) {
+    return $object->getQuestion();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setQuestion($value);
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    if ($old === null) {
+      return pht(
+        '%s created this poll.',
+        $this->renderAuthor());
+    } else {
+      return pht(
+        '%s changed the poll question from "%s" to "%s".',
+        $this->renderAuthor(),
+        $old,
+        $new);
+    }
+  }
+
+  public function getTitleForFeed() {
+    $old = $this->getOldValue();
+
+    if ($old === null) {
+      return pht(
+        '%s created %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+
+    } else {
+      return pht(
+        '%s renamed %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+  }
+
+  public function getIcon() {
+    $old = $this->getOldValue();
+
+    if ($old === null) {
+      return 'fa-plus';
+    } else {
+      return 'fa-pencil';
+    }
+  }
+
+  public function validateTransactions($object, array $xactions) {
+    $errors = array();
+
+    if ($this->isEmptyTextTransaction($object->getQuestion(), $xactions)) {
+      $errors[] = $this->newRequiredError(pht('Polls must have a question.'));
+    }
+
+    return $errors;
+  }
+
+}
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteResponsesTransaction.php
@@ -0,0 +1,34 @@
+<?php
+
+final class PhabricatorSlowvoteResponsesTransaction
+  extends PhabricatorSlowvoteTransactionType {
+
+  const TRANSACTIONTYPE = 'vote:responses';
+
+  public function generateOldValue($object) {
+    if ($object->getResponseVisibility() === null) {
+      return null;
+    }
+    return (int)$object->getResponseVisibility();
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setResponseVisibility($value);
+  }
+
+  public function getTitle() {
+    // TODO: This could be more detailed
+    return pht(
+      '%s changed who can see the responses.',
+      $this->renderAuthor());
+  }
+
+  public function getTitleForFeed() {
+    // TODO: This could be more detailed
+    return pht(
+      '%s changed who can see the responses of %s.',
+        $this->renderAuthor(),
+        $this->renderObject());
+  }
+
+}
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteShuffleTransaction.php
@@ -0,0 +1,55 @@
+<?php
+
+final class PhabricatorSlowvoteShuffleTransaction
+  extends PhabricatorSlowvoteTransactionType {
+
+  const TRANSACTIONTYPE = 'vote:shuffle';
+
+  public function generateOldValue($object) {
+    return (bool)$object->getShuffle();
+  }
+
+  public function generateNewValue($object, $value) {
+    return (bool)$value;
+  }
+
+  public function applyInternalEffects($object, $value) {
+    $object->setShuffle((int)$value);
+  }
+
+  public function getTitle() {
+    $new = $this->getNewValue();
+
+    if ($new) {
+      return pht(
+        '%s made poll responses appear in a random order.',
+        $this->renderAuthor());
+    } else {
+      return pht(
+        '%s made poll responses appear in a fixed order.',
+        $this->renderAuthor());
+    }
+  }
+
+  public function getTitleForFeed() {
+    $new = $this->getNewValue();
+
+    if ($new) {
+      return pht(
+        '%s made %s responses appear in a random order.',
+        $this->renderAuthor(),
+        $this->renderObject());
+
+    } else {
+      return pht(
+        '%s made %s responses appear in a fixed order.',
+        $this->renderAuthor(),
+        $this->renderObject());
+    }
+  }
+
+  public function getIcon() {
+    return 'fa-refresh';
+  }
+
+}
diff --git a/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php b/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/slowvote/xactions/PhabricatorSlowvoteTransactionType.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class PhabricatorSlowvoteTransactionType
+  extends PhabricatorModularTransactionType {}