diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 6b38ed042..168d053c6 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,1091 +1,1107 @@
 <?php
 
 final class DifferentialRevisionViewController extends DifferentialController {
 
   private $revisionID;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $this->revisionID = $request->getURIData('id');
 
     $viewer_is_anonymous = !$viewer->isLoggedIn();
 
     $revision = id(new DifferentialRevisionQuery())
       ->withIDs(array($this->revisionID))
       ->setViewer($request->getUser())
       ->needRelationships(true)
       ->needReviewerStatus(true)
       ->needReviewerAuthority(true)
       ->executeOne();
 
     if (!$revision) {
       return new Aphront404Response();
     }
 
     $diffs = id(new DifferentialDiffQuery())
       ->setViewer($request->getUser())
       ->withRevisionIDs(array($this->revisionID))
       ->execute();
     $diffs = array_reverse($diffs, $preserve_keys = true);
 
     if (!$diffs) {
       throw new Exception(
         pht('This revision has no diffs. Something has gone quite wrong.'));
     }
 
     $revision->attachActiveDiff(last($diffs));
 
     $diff_vs = $request->getInt('vs');
     $target_id = $request->getInt('id');
     $target = idx($diffs, $target_id, end($diffs));
 
     $target_manual = $target;
     if (!$target_id) {
       foreach ($diffs as $diff) {
         if ($diff->getCreationMethod() != 'commit') {
           $target_manual = $diff;
         }
       }
     }
 
     if (empty($diffs[$diff_vs])) {
       $diff_vs = null;
     }
 
     $repository = null;
     $repository_phid = $target->getRepositoryPHID();
     if ($repository_phid) {
       if ($repository_phid == $revision->getRepositoryPHID()) {
         $repository = $revision->getRepository();
       } else {
         $repository = id(new PhabricatorRepositoryQuery())
           ->setViewer($viewer)
           ->withPHIDs(array($repository_phid))
           ->executeOne();
       }
     }
 
     list($changesets, $vs_map, $vs_changesets, $rendering_references) =
       $this->loadChangesetsAndVsMap(
         $target,
         idx($diffs, $diff_vs),
         $repository);
 
     if ($request->getExists('download')) {
       return $this->buildRawDiffResponse(
         $revision,
         $changesets,
         $vs_changesets,
         $vs_map,
         $repository);
     }
 
     $map = $vs_map;
     if (!$map) {
       $map = array_fill_keys(array_keys($changesets), 0);
     }
 
     $old_ids = array();
     $new_ids = array();
     foreach ($map as $id => $vs) {
       if ($vs <= 0) {
         $old_ids[] = $id;
         $new_ids[] = $id;
       } else {
         $new_ids[] = $id;
         $new_ids[] = $vs;
       }
     }
 
     $this->loadDiffProperties($diffs);
     $props = $target_manual->getDiffProperties();
 
     $object_phids = array_merge(
       $revision->getReviewers(),
       $revision->getCCPHIDs(),
       $revision->loadCommitPHIDs(),
       array(
         $revision->getAuthorPHID(),
         $viewer->getPHID(),
       ));
 
     foreach ($revision->getAttached() as $type => $phids) {
       foreach ($phids as $phid => $info) {
         $object_phids[] = $phid;
       }
     }
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $revision,
       PhabricatorCustomField::ROLE_VIEW);
 
     $field_list->setViewer($viewer);
     $field_list->readFieldsFromStorage($revision);
 
     $warning_handle_map = array();
     foreach ($field_list->getFields() as $key => $field) {
       $req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings();
       foreach ($req as $phid) {
         $warning_handle_map[$key][] = $phid;
         $object_phids[] = $phid;
       }
     }
 
     $handles = $this->loadViewerHandles($object_phids);
 
     $request_uri = $request->getRequestURI();
 
     $limit = 100;
     $large = $request->getStr('large');
     if (count($changesets) > $limit && !$large) {
       $count = count($changesets);
       $warning = new PHUIInfoView();
       $warning->setTitle(pht('Very Large Diff'));
       $warning->setSeverity(PHUIInfoView::SEVERITY_WARNING);
       $warning->appendChild(hsprintf(
         '%s <strong>%s</strong>',
         pht(
           'This diff is very large and affects %s files. '.
           'You may load each file individually or ',
           new PhutilNumber($count)),
         phutil_tag(
           'a',
           array(
             'class' => 'button grey',
             'href' => $request_uri
               ->alter('large', 'true')
               ->setFragment('toc'),
           ),
           pht('Show All Files Inline'))));
       $warning = $warning->render();
 
       $old = array_select_keys($changesets, $old_ids);
       $new = array_select_keys($changesets, $new_ids);
 
       $query = id(new DifferentialInlineCommentQuery())
         ->setViewer($viewer)
         ->needHidden(true)
         ->withRevisionPHIDs(array($revision->getPHID()));
       $inlines = $query->execute();
       $inlines = $query->adjustInlinesForChangesets(
         $inlines,
         $old,
         $new,
         $revision);
 
       $visible_changesets = array();
       foreach ($inlines as $inline) {
         $changeset_id = $inline->getChangesetID();
         if (isset($changesets[$changeset_id])) {
           $visible_changesets[$changeset_id] = $changesets[$changeset_id];
         }
       }
     } else {
       $warning = null;
       $visible_changesets = $changesets;
     }
 
     $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision');
     $local_commits = idx($props, 'local:commits', array());
     foreach ($local_commits as $local_commit) {
       $commit_hashes[] = idx($local_commit, 'tree');
       $commit_hashes[] = idx($local_commit, 'local');
     }
     $commit_hashes = array_unique(array_filter($commit_hashes));
     if ($commit_hashes) {
       $commits_for_links = id(new DiffusionCommitQuery())
         ->setViewer($viewer)
         ->withIdentifiers($commit_hashes)
         ->execute();
       $commits_for_links = mpull(
         $commits_for_links,
         null,
         'getCommitIdentifier');
     } else {
       $commits_for_links = array();
     }
 
     $header = $this->buildHeader($revision);
     $subheader = $this->buildSubheaderView($revision);
     $details = $this->buildDetails($revision, $field_list);
     $curtain = $this->buildCurtain($revision);
 
     $whitespace = $request->getStr(
       'whitespace',
       DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
 
     $repository = $revision->getRepository();
     if ($repository) {
       $symbol_indexes = $this->buildSymbolIndexes(
         $repository,
         $visible_changesets);
     } else {
       $symbol_indexes = array();
     }
 
     $revision_warnings = $this->buildRevisionWarnings(
       $revision,
       $field_list,
       $warning_handle_map,
       $handles);
     $info_view = null;
     if ($revision_warnings) {
       $info_view = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
         ->setErrors($revision_warnings);
     }
 
     $detail_diffs = array_select_keys(
       $diffs,
       array($diff_vs, $target->getID()));
     $detail_diffs = mpull($detail_diffs, null, 'getPHID');
 
     $this->loadHarbormasterData($detail_diffs);
 
     $diff_detail_box = $this->buildDiffDetailView(
       $detail_diffs,
       $revision,
       $field_list);
 
     $unit_box = $this->buildUnitMessagesView(
       $target,
       $revision);
 
-    $comment_view = $this->buildTransactions(
+    $timeline = $this->buildTransactions(
       $revision,
       $diff_vs ? $diffs[$diff_vs] : $target,
       $target,
       $old_ids,
       $new_ids);
 
-    if (!$viewer_is_anonymous) {
-      $comment_view->setQuoteRef('D'.$revision->getID());
-      $comment_view->setQuoteTargetID('comment-content');
-    }
+    $timeline->setQuoteRef($revision->getMonogram());
 
     $changeset_view = id(new DifferentialChangesetListView())
       ->setChangesets($changesets)
       ->setVisibleChangesets($visible_changesets)
       ->setStandaloneURI('/differential/changeset/')
       ->setRawFileURIs(
         '/differential/changeset/?view=old',
         '/differential/changeset/?view=new')
       ->setUser($viewer)
       ->setDiff($target)
       ->setRenderingReferences($rendering_references)
       ->setVsMap($vs_map)
       ->setWhitespace($whitespace)
       ->setSymbolIndexes($symbol_indexes)
       ->setTitle(pht('Diff %s', $target->getID()))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
     if ($repository) {
       $changeset_view->setRepository($repository);
     }
 
     if (!$viewer_is_anonymous) {
       $changeset_view->setInlineCommentControllerURI(
         '/differential/comment/inline/edit/'.$revision->getID().'/');
     }
 
     $broken_diffs = $this->loadHistoryDiffStatus($diffs);
 
     $history = id(new DifferentialRevisionUpdateHistoryView())
       ->setUser($viewer)
       ->setDiffs($diffs)
       ->setDiffUnitStatuses($broken_diffs)
       ->setSelectedVersusDiffID($diff_vs)
       ->setSelectedDiffID($target->getID())
       ->setSelectedWhitespace($whitespace)
       ->setCommitsForLinks($commits_for_links);
 
     $local_table = id(new DifferentialLocalCommitsView())
       ->setUser($viewer)
       ->setLocalCommits(idx($props, 'local:commits'))
       ->setCommitsForLinks($commits_for_links);
 
     if ($repository) {
       $other_revisions = $this->loadOtherRevisions(
         $changesets,
         $target,
         $repository);
     } else {
       $other_revisions = array();
     }
 
     $other_view = null;
     if ($other_revisions) {
       $other_view = $this->renderOtherRevisions($other_revisions);
     }
 
     $toc_view = $this->buildTableOfContents(
       $changesets,
       $visible_changesets,
       $target->loadCoverageMap($viewer));
 
     $tab_group = id(new PHUITabGroupView())
       ->addTab(
         id(new PHUITabView())
           ->setName(pht('Files'))
           ->setKey('files')
           ->appendChild($toc_view))
       ->addTab(
         id(new PHUITabView())
           ->setName(pht('History'))
           ->setKey('history')
           ->appendChild($history))
       ->addTab(
         id(new PHUITabView())
           ->setName(pht('Commits'))
           ->setKey('commits')
           ->appendChild($local_table));
 
     $stack_graph = id(new DifferentialRevisionGraph())
       ->setViewer($viewer)
       ->setSeedPHID($revision->getPHID())
       ->setLoadEntireGraph(true)
       ->loadGraph();
     if (!$stack_graph->isEmpty()) {
       $stack_table = $stack_graph->newGraphTable();
 
       $parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST;
       $reachable = $stack_graph->getReachableObjects($parent_type);
 
       foreach ($reachable as $key => $reachable_revision) {
         if ($reachable_revision->isClosed()) {
           unset($reachable[$key]);
         }
       }
 
       if ($reachable) {
         $stack_name = pht('Stack (%s Open)', phutil_count($reachable));
         $stack_color = PHUIListItemView::STATUS_FAIL;
       } else {
         $stack_name = pht('Stack');
         $stack_color = null;
       }
 
       $tab_group->addTab(
         id(new PHUITabView())
           ->setName($stack_name)
           ->setKey('stack')
           ->setColor($stack_color)
           ->appendChild($stack_table));
     }
 
     if ($other_view) {
       $tab_group->addTab(
         id(new PHUITabView())
           ->setName(pht('Similar'))
           ->setKey('similar')
           ->appendChild($other_view));
     }
 
     $tab_view = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Revision Contents'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addTabGroup($tab_group);
 
     $signatures = DifferentialRequiredSignaturesField::loadForRevision(
       $revision);
     $missing_signatures = false;
     foreach ($signatures as $phid => $signed) {
       if (!$signed) {
         $missing_signatures = true;
       }
     }
 
     $footer = array();
     $signature_message = null;
     if ($missing_signatures) {
       $signature_message = id(new PHUIInfoView())
         ->setTitle(pht('Content Hidden'))
         ->appendChild(
           pht(
             'The content of this revision is hidden until the author has '.
             'signed all of the required legal agreements.'));
     } else {
       $anchor = id(new PhabricatorAnchorView())
         ->setAnchorName('toc')
         ->setNavigationMarker(true);
 
       $footer[] = array(
         $anchor,
         $warning,
         $tab_view,
         $changeset_view,
       );
     }
 
-    $footer[] = id(new DifferentialRevisionEditEngine())
+    $comment_view = id(new DifferentialRevisionEditEngine())
       ->setViewer($viewer)
       ->buildEditEngineCommentView($revision);
 
-    $object_id = 'D'.$revision->getID();
+    $comment_view->setTransactionTimeline($timeline);
+
+    $review_warnings = array();
+    foreach ($field_list->getFields() as $field) {
+      $review_warnings[] = $field->getWarningsForDetailView();
+    }
+    $review_warnings = array_mergev($review_warnings);
+
+    if ($review_warnings) {
+      $warnings_view = id(new PHUIInfoView())
+        ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+        ->setErrors($review_warnings);
+
+      $comment_view->setInfoView($warnings_view);
+    }
+
+    $footer[] = $comment_view;
+
+    $monogram = $revision->getMonogram();
     $operations_box = $this->buildOperationsBox($revision);
 
     $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->addTextCrumb($object_id, '/'.$object_id);
+    $crumbs->addTextCrumb($monogram, $revision->getURI());
     $crumbs->setBorder(true);
 
     $filetree_on = $viewer->compareUserSetting(
       PhabricatorShowFiletreeSetting::SETTINGKEY,
       PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE);
 
     $nav = null;
     if ($filetree_on) {
       $collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
       $collapsed_value = $viewer->getUserSetting($collapsed_key);
 
       $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
-        ->setTitle('D'.$revision->getID())
-        ->setBaseURI(new PhutilURI('/D'.$revision->getID()))
+        ->setTitle($monogram)
+        ->setBaseURI(new PhutilURI($revision->getURI()))
         ->setCollapsed((bool)$collapsed_value)
         ->build($changesets);
     }
 
     Javelin::initBehavior('differential-user-select');
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
-      ->setMainColumn(array(
-        $operations_box,
-        $info_view,
-        $details,
-        $diff_detail_box,
-        $unit_box,
-        $comment_view,
-        $signature_message,
-      ))
+      ->setMainColumn(
+        array(
+          $operations_box,
+          $info_view,
+          $details,
+          $diff_detail_box,
+          $unit_box,
+          $timeline,
+          $signature_message,
+        ))
       ->setFooter($footer);
 
     $page =  $this->newPage()
-      ->setTitle($object_id.' '.$revision->getTitle())
+      ->setTitle($monogram.' '.$revision->getTitle())
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($revision->getPHID()))
       ->appendChild($view);
 
     if ($nav) {
       $page->setNavigation($nav);
     }
 
     return $page;
   }
 
   private function buildHeader(DifferentialRevision $revision) {
     $view = id(new PHUIHeaderView())
       ->setHeader($revision->getTitle($revision))
       ->setUser($this->getViewer())
       ->setPolicyObject($revision)
       ->setHeaderIcon('fa-cog');
 
     $status = $revision->getStatus();
     $status_name =
       DifferentialRevisionStatus::renderFullDescription($status);
 
     $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name);
 
     return $view;
   }
 
   private function buildSubheaderView(DifferentialRevision $revision) {
     $viewer = $this->getViewer();
 
     $author_phid = $revision->getAuthorPHID();
 
     $author = $viewer->renderHandle($author_phid)->render();
     $date = phabricator_datetime($revision->getDateCreated(), $viewer);
     $author = phutil_tag('strong', array(), $author);
 
     $handles = $viewer->loadHandles(array($author_phid));
     $image_uri = $handles[$author_phid]->getImageURI();
     $image_href = $handles[$author_phid]->getURI();
 
     $content = pht('Authored by %s on %s.', $author, $date);
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
   }
 
   private function buildDetails(
     DifferentialRevision $revision,
     $custom_fields) {
     $viewer = $this->getViewer();
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     if ($custom_fields) {
       $custom_fields->appendFieldsToPropertyList(
         $revision,
         $viewer,
         $properties);
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Details'));
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
   }
 
   private function buildCurtain(DifferentialRevision $revision) {
     $viewer = $this->getViewer();
     $revision_id = $revision->getID();
     $revision_phid = $revision->getPHID();
     $curtain = $this->newCurtainView($revision);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $revision,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setHref("/differential/revision/edit/{$revision_id}/")
         ->setName(pht('Edit Revision'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-upload')
         ->setHref("/differential/revision/update/{$revision_id}/")
         ->setName(pht('Update Diff'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $request_uri = $this->getRequest()->getRequestURI();
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-download')
         ->setName(pht('Download Raw Diff'))
         ->setHref($request_uri->alter('download', 'true')));
 
     $relationship_list = PhabricatorObjectRelationshipList::newForObject(
       $viewer,
       $revision);
 
     $revision_actions = array(
       DifferentialRevisionHasParentRelationship::RELATIONSHIPKEY,
       DifferentialRevisionHasChildRelationship::RELATIONSHIPKEY,
     );
 
     $revision_submenu = $relationship_list->newActionSubmenu($revision_actions)
       ->setName(pht('Edit Related Revisions...'))
       ->setIcon('fa-cog');
 
     $curtain->addAction($revision_submenu);
 
     $relationship_submenu = $relationship_list->newActionMenu();
     if ($relationship_submenu) {
       $curtain->addAction($relationship_submenu);
     }
 
     return $curtain;
   }
 
   private function loadHistoryDiffStatus(array $diffs) {
     assert_instances_of($diffs, 'DifferentialDiff');
 
     $diff_phids = mpull($diffs, 'getPHID');
     $bad_unit_status = array(
       ArcanistUnitTestResult::RESULT_FAIL,
       ArcanistUnitTestResult::RESULT_BROKEN,
     );
 
     $message = new HarbormasterBuildUnitMessage();
     $target = new HarbormasterBuildTarget();
     $build = new HarbormasterBuild();
     $buildable = new HarbormasterBuildable();
 
     $broken_diffs = queryfx_all(
       $message->establishConnection('r'),
       'SELECT distinct a.buildablePHID
         FROM %T m
           JOIN %T t ON m.buildTargetPHID = t.phid
           JOIN %T b ON t.buildPHID = b.phid
           JOIN %T a ON b.buildablePHID = a.phid
         WHERE a.buildablePHID IN (%Ls)
           AND m.result in (%Ls)',
       $message->getTableName(),
       $target->getTableName(),
       $build->getTableName(),
       $buildable->getTableName(),
       $diff_phids,
       $bad_unit_status);
 
     $unit_status = array();
     foreach ($broken_diffs as $broken) {
       $phid = $broken['buildablePHID'];
       $unit_status[$phid] = DifferentialUnitStatus::UNIT_FAIL;
     }
 
     return $unit_status;
   }
 
   private function loadChangesetsAndVsMap(
     DifferentialDiff $target,
     DifferentialDiff $diff_vs = null,
     PhabricatorRepository $repository = null) {
 
     $load_diffs = array($target);
     if ($diff_vs) {
       $load_diffs[] = $diff_vs;
     }
 
     $raw_changesets = id(new DifferentialChangesetQuery())
       ->setViewer($this->getRequest()->getUser())
       ->withDiffs($load_diffs)
       ->execute();
     $changeset_groups = mgroup($raw_changesets, 'getDiffID');
 
     $changesets = idx($changeset_groups, $target->getID(), array());
     $changesets = mpull($changesets, null, 'getID');
 
     $refs          = array();
     $vs_map        = array();
     $vs_changesets = array();
     if ($diff_vs) {
       $vs_id                  = $diff_vs->getID();
       $vs_changesets_path_map = array();
       foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
         $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
         $vs_changesets_path_map[$path] = $changeset;
         $vs_changesets[$changeset->getID()] = $changeset;
       }
       foreach ($changesets as $key => $changeset) {
         $path = $changeset->getAbsoluteRepositoryPath($repository, $target);
         if (isset($vs_changesets_path_map[$path])) {
           $vs_map[$changeset->getID()] =
             $vs_changesets_path_map[$path]->getID();
           $refs[$changeset->getID()] =
             $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID();
           unset($vs_changesets_path_map[$path]);
         } else {
           $refs[$changeset->getID()] = $changeset->getID();
         }
       }
       foreach ($vs_changesets_path_map as $path => $changeset) {
         $changesets[$changeset->getID()] = $changeset;
         $vs_map[$changeset->getID()]     = -1;
         $refs[$changeset->getID()]       = $changeset->getID().'/-1';
       }
     } else {
       foreach ($changesets as $changeset) {
         $refs[$changeset->getID()] = $changeset->getID();
       }
     }
 
     $changesets = msort($changesets, 'getSortKey');
 
     return array($changesets, $vs_map, $vs_changesets, $refs);
   }
 
   private function buildSymbolIndexes(
     PhabricatorRepository $repository,
     array $visible_changesets) {
     assert_instances_of($visible_changesets, 'DifferentialChangeset');
 
     $engine = PhabricatorSyntaxHighlighter::newEngine();
 
     $langs = $repository->getSymbolLanguages();
     $langs = nonempty($langs, array());
 
     $sources = $repository->getSymbolSources();
     $sources = nonempty($sources, array());
 
     $symbol_indexes = array();
 
     if ($langs && $sources) {
       $have_symbols = id(new DiffusionSymbolQuery())
           ->existsSymbolsInRepository($repository->getPHID());
       if (!$have_symbols) {
         return $symbol_indexes;
       }
     }
 
     $repository_phids = array_merge(
       array($repository->getPHID()),
       $sources);
 
     $indexed_langs = array_fill_keys($langs, true);
     foreach ($visible_changesets as $key => $changeset) {
       $lang = $engine->getLanguageFromFilename($changeset->getFilename());
       if (empty($indexed_langs) || isset($indexed_langs[$lang])) {
         $symbol_indexes[$key] = array(
           'lang'         => $lang,
           'repositories' => $repository_phids,
         );
       }
     }
 
     return $symbol_indexes;
   }
 
   private function loadOtherRevisions(
     array $changesets,
     DifferentialDiff $target,
     PhabricatorRepository $repository) {
     assert_instances_of($changesets, 'DifferentialChangeset');
 
     $paths = array();
     foreach ($changesets as $changeset) {
       $paths[] = $changeset->getAbsoluteRepositoryPath(
         $repository,
         $target);
     }
 
     if (!$paths) {
       return array();
     }
 
     $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
 
     if (!$path_map) {
       return array();
     }
 
     $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
 
     $query = id(new DifferentialRevisionQuery())
       ->setViewer($this->getRequest()->getUser())
       ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
       ->withUpdatedEpochBetween($recent, null)
       ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
       ->setLimit(10)
       ->needFlags(true)
       ->needDrafts(true)
       ->needRelationships(true);
 
     foreach ($path_map as $path => $path_id) {
       $query->withPath($repository->getID(), $path_id);
     }
 
     $results = $query->execute();
 
     // Strip out *this* revision.
     foreach ($results as $key => $result) {
       if ($result->getID() == $this->revisionID) {
         unset($results[$key]);
       }
     }
 
     return $results;
   }
 
   private function renderOtherRevisions(array $revisions) {
     assert_instances_of($revisions, 'DifferentialRevision');
     $viewer = $this->getViewer();
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Recent Similar Revisions'));
 
     $view = id(new DifferentialRevisionListView())
       ->setRevisions($revisions)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setNoBox(true)
       ->setUser($viewer);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     return $view;
   }
 
 
   /**
    * Note this code is somewhat similar to the buildPatch method in
    * @{class:DifferentialReviewRequestMail}.
    *
    * @return @{class:AphrontRedirectResponse}
    */
   private function buildRawDiffResponse(
     DifferentialRevision $revision,
     array $changesets,
     array $vs_changesets,
     array $vs_map,
     PhabricatorRepository $repository = null) {
 
     assert_instances_of($changesets,    'DifferentialChangeset');
     assert_instances_of($vs_changesets, 'DifferentialChangeset');
 
     $viewer = $this->getViewer();
 
     id(new DifferentialHunkQuery())
       ->setViewer($viewer)
       ->withChangesets($changesets)
       ->needAttachToChangesets(true)
       ->execute();
 
     $diff = new DifferentialDiff();
     $diff->attachChangesets($changesets);
     $raw_changes = $diff->buildChangesList();
     $changes = array();
     foreach ($raw_changes as $changedict) {
       $changes[] = ArcanistDiffChange::newFromDictionary($changedict);
     }
 
     $loader = id(new PhabricatorFileBundleLoader())
       ->setViewer($viewer);
 
     $bundle = ArcanistBundle::newFromChanges($changes);
     $bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
 
     $vcs = $repository ? $repository->getVersionControlSystem() : null;
     switch ($vcs) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $raw_diff = $bundle->toGitPatch();
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
       default:
         $raw_diff = $bundle->toUnifiedDiff();
         break;
     }
 
     $request_uri = $this->getRequest()->getRequestURI();
 
     // this ends up being something like
     //   D123.diff
     // or the verbose
     //   D123.vs123.id123.whitespaceignore-all.diff
     // lame but nice to include these options
     $file_name = ltrim($request_uri->getPath(), '/').'.';
     foreach ($request_uri->getQueryParams() as $key => $value) {
       if ($key == 'download') {
         continue;
       }
       $file_name .= $key.$value.'.';
     }
     $file_name .= 'diff';
 
     $file = PhabricatorFile::buildFromFileDataOrHash(
       $raw_diff,
       array(
         'name' => $file_name,
         'ttl' => (60 * 60 * 24),
         'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file->attachToObject($revision->getPHID());
     unset($unguarded);
 
     return $file->getRedirectResponse();
   }
 
   private function buildTransactions(
     DifferentialRevision $revision,
     DifferentialDiff $left_diff,
     DifferentialDiff $right_diff,
     array $old_ids,
     array $new_ids) {
 
     $timeline = $this->buildTransactionTimeline(
       $revision,
       new DifferentialTransactionQuery(),
       $engine = null,
       array(
         'left' => $left_diff->getID(),
         'right' => $right_diff->getID(),
         'old' => implode(',', $old_ids),
         'new' => implode(',', $new_ids),
       ));
 
     return $timeline;
   }
 
   private function buildRevisionWarnings(
     DifferentialRevision $revision,
     PhabricatorCustomFieldList $field_list,
     array $warning_handle_map,
     array $handles) {
 
     $warnings = array();
     foreach ($field_list->getFields() as $key => $field) {
       $phids = idx($warning_handle_map, $key, array());
       $field_handles = array_select_keys($handles, $phids);
       $field_warnings = $field->getWarningsForRevisionHeader($field_handles);
       foreach ($field_warnings as $warning) {
         $warnings[] = $warning;
       }
     }
 
     return $warnings;
   }
 
   private function buildDiffDetailView(
     array $diffs,
     DifferentialRevision $revision,
     PhabricatorCustomFieldList $field_list) {
     $viewer = $this->getViewer();
 
     $fields = array();
     foreach ($field_list->getFields() as $field) {
       if ($field->shouldAppearInDiffPropertyView()) {
         $fields[] = $field;
       }
     }
 
     if (!$fields) {
       return null;
     }
 
     $property_lists = array();
     foreach ($this->getDiffTabLabels($diffs) as $tab) {
       list($label, $diff) = $tab;
 
       $property_lists[] = array(
         $label,
         $this->buildDiffPropertyList($diff, $revision, $fields),
       );
     }
 
     $tab_group = id(new PHUITabGroupView())
       ->setHideSingleTab(true);
 
     foreach ($property_lists as $key => $property_list) {
       list($tab_name, $list_view) = $property_list;
 
       $tab = id(new PHUITabView())
         ->setKey($key)
         ->setName($tab_name)
         ->appendChild($list_view);
 
       $tab_group->addTab($tab);
       $tab_group->selectTab($key);
     }
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Diff Detail'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setUser($viewer)
       ->addTabGroup($tab_group);
   }
 
   private function buildDiffPropertyList(
     DifferentialDiff $diff,
     DifferentialRevision $revision,
     array $fields) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($diff);
 
     foreach ($fields as $field) {
       $label = $field->renderDiffPropertyViewLabel($diff);
       $value = $field->renderDiffPropertyViewValue($diff);
       if ($value !== null) {
         $view->addProperty($label, $value);
       }
     }
 
     return $view;
   }
 
   private function buildOperationsBox(DifferentialRevision $revision) {
     $viewer = $this->getViewer();
 
     // Save a query if we can't possibly have pending operations.
     $repository = $revision->getRepository();
     if (!$repository || !$repository->canPerformAutomation()) {
       return null;
     }
 
     $operations = id(new DrydockRepositoryOperationQuery())
       ->setViewer($viewer)
       ->withObjectPHIDs(array($revision->getPHID()))
       ->withIsDismissed(false)
       ->withOperationTypes(
         array(
           DrydockLandRepositoryOperation::OPCONST,
         ))
       ->execute();
     if (!$operations) {
       return null;
     }
 
     $state_fail = DrydockRepositoryOperation::STATE_FAIL;
 
     // We're going to show the oldest operation which hasn't failed, or the
     // most recent failure if they're all failures.
     $operations = msort($operations, 'getID');
     foreach ($operations as $operation) {
       if ($operation->getOperationState() != $state_fail) {
         break;
       }
     }
 
     // If we found a completed operation, don't render anything. We don't want
     // to show an older error after the thing worked properly.
     if ($operation->isDone()) {
       return null;
     }
 
     $box_view = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Active Operations'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
     return id(new DrydockRepositoryOperationStatusView())
       ->setUser($viewer)
       ->setBoxView($box_view)
       ->setOperation($operation);
   }
 
   private function buildUnitMessagesView(
     $diff,
     DifferentialRevision $revision) {
     $viewer = $this->getViewer();
 
     if (!$diff->getBuildable()) {
       return null;
     }
 
     if (!$diff->getUnitMessages()) {
       return null;
     }
 
     $interesting_messages = array();
     foreach ($diff->getUnitMessages() as $message) {
       switch ($message->getResult()) {
         case ArcanistUnitTestResult::RESULT_PASS:
         case ArcanistUnitTestResult::RESULT_SKIP:
           break;
         default:
           $interesting_messages[] = $message;
           break;
       }
     }
 
     if (!$interesting_messages) {
       return null;
     }
 
     $excuse = null;
     if ($diff->hasDiffProperty('arc:unit-excuse')) {
       $excuse = $diff->getProperty('arc:unit-excuse');
     }
 
     return id(new HarbormasterUnitSummaryView())
       ->setUser($viewer)
       ->setExcuse($excuse)
       ->setBuildable($diff->getBuildable())
       ->setUnitMessages($diff->getUnitMessages())
       ->setLimit(5)
       ->setShowViewAll(true);
   }
 
 }
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
index 13054f63c..44793d187 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -1,477 +1,496 @@
 <?php
 
 /**
  * @concrete-extensible
  */
 class PhabricatorApplicationTransactionCommentView extends AphrontView {
 
   private $submitButtonName;
   private $action;
 
   private $previewPanelID;
   private $previewTimelineID;
   private $previewToggleID;
   private $formID;
   private $commentID;
   private $draft;
   private $requestURI;
   private $showPreview = true;
   private $objectPHID;
   private $headerText;
   private $noPermission;
   private $fullWidth;
+  private $infoView;
 
   private $currentVersion;
   private $versionedDraft;
   private $commentActions;
   private $commentActionGroups = array();
   private $transactionTimeline;
 
   public function setObjectPHID($object_phid) {
     $this->objectPHID = $object_phid;
     return $this;
   }
 
   public function getObjectPHID() {
     return $this->objectPHID;
   }
 
   public function setShowPreview($show_preview) {
     $this->showPreview = $show_preview;
     return $this;
   }
 
   public function getShowPreview() {
     return $this->showPreview;
   }
 
   public function setRequestURI(PhutilURI $request_uri) {
     $this->requestURI = $request_uri;
     return $this;
   }
   public function getRequestURI() {
     return $this->requestURI;
   }
 
   public function setCurrentVersion($current_version) {
     $this->currentVersion = $current_version;
     return $this;
   }
 
   public function getCurrentVersion() {
     return $this->currentVersion;
   }
 
   public function setVersionedDraft(
     PhabricatorVersionedDraft $versioned_draft) {
     $this->versionedDraft = $versioned_draft;
     return $this;
   }
 
   public function getVersionedDraft() {
     return $this->versionedDraft;
   }
 
   public function setDraft(PhabricatorDraft $draft) {
     $this->draft = $draft;
     return $this;
   }
 
   public function getDraft() {
     return $this->draft;
   }
 
   public function setSubmitButtonName($submit_button_name) {
     $this->submitButtonName = $submit_button_name;
     return $this;
   }
 
   public function getSubmitButtonName() {
     return $this->submitButtonName;
   }
 
   public function setAction($action) {
     $this->action = $action;
     return $this;
   }
 
   public function getAction() {
     return $this->action;
   }
 
   public function setHeaderText($text) {
     $this->headerText = $text;
     return $this;
   }
 
   public function setFullWidth($fw) {
     $this->fullWidth = $fw;
     return $this;
   }
 
+  public function setInfoView(PHUIInfoView $info_view) {
+    $this->infoView = $info_view;
+    return $this;
+  }
+
+  public function getInfoView() {
+    return $this->infoView;
+  }
+
   public function setCommentActions(array $comment_actions) {
     assert_instances_of($comment_actions, 'PhabricatorEditEngineCommentAction');
     $this->commentActions = $comment_actions;
     return $this;
   }
 
   public function getCommentActions() {
     return $this->commentActions;
   }
 
   public function setCommentActionGroups(array $groups) {
     assert_instances_of($groups, 'PhabricatorEditEngineCommentActionGroup');
     $this->commentActionGroups = $groups;
     return $this;
   }
 
   public function getCommentActionGroups() {
     return $this->commentActionGroups;
   }
 
   public function setNoPermission($no_permission) {
     $this->noPermission = $no_permission;
     return $this;
   }
 
   public function getNoPermission() {
     return $this->noPermission;
   }
 
   public function setTransactionTimeline(
     PhabricatorApplicationTransactionView $timeline) {
 
     $timeline->setQuoteTargetID($this->getCommentID());
     if ($this->getNoPermission()) {
       $timeline->setShouldTerminate(true);
     }
 
     $this->transactionTimeline = $timeline;
     return $this;
   }
 
   public function render() {
     if ($this->getNoPermission()) {
       return null;
     }
 
     $user = $this->getUser();
     if (!$user->isLoggedIn()) {
       $uri = id(new PhutilURI('/login/'))
         ->setQueryParam('next', (string)$this->getRequestURI());
       return id(new PHUIObjectBoxView())
         ->setFlush(true)
         ->appendChild(
           javelin_tag(
             'a',
             array(
               'class' => 'login-to-comment button',
               'href' => $uri,
             ),
             pht('Login to Comment')));
     }
 
     $data = array();
 
     $comment = $this->renderCommentPanel();
 
     if ($this->getShowPreview()) {
       $preview = $this->renderPreviewPanel();
     } else {
       $preview = null;
     }
 
     if (!$this->getCommentActions()) {
       Javelin::initBehavior(
         'phabricator-transaction-comment-form',
         array(
           'formID'        => $this->getFormID(),
           'timelineID'    => $this->getPreviewTimelineID(),
           'panelID'       => $this->getPreviewPanelID(),
           'showPreview'   => $this->getShowPreview(),
           'actionURI'     => $this->getAction(),
         ));
     }
 
     require_celerity_resource('phui-comment-form-css');
     $image_uri = $user->getProfileImageURI();
     $image = phutil_tag(
       'div',
       array(
         'style' => 'background-image: url('.$image_uri.')',
         'class' => 'phui-comment-image',
       ));
     $wedge = phutil_tag(
       'div',
       array(
         'class' => 'phui-timeline-wedge',
       ),
       '');
     $comment_box = id(new PHUIObjectBoxView())
       ->setFlush(true)
       ->addClass('phui-comment-form-view')
       ->addSigil('phui-comment-form')
       ->appendChild($image)
       ->appendChild($wedge)
       ->appendChild($comment);
 
     return array($comment_box, $preview);
   }
 
   private function renderCommentPanel() {
     $draft_comment = '';
     $draft_key = null;
     if ($this->getDraft()) {
       $draft_comment = $this->getDraft()->getDraft();
       $draft_key = $this->getDraft()->getDraftKey();
     }
 
     $versioned_draft = $this->getVersionedDraft();
     if ($versioned_draft) {
       $draft_comment = $versioned_draft->getProperty('comment', '');
     }
 
     if (!$this->getObjectPHID()) {
       throw new PhutilInvalidStateException('setObjectPHID', 'render');
     }
 
     $version_key = PhabricatorVersionedDraft::KEY_VERSION;
     $version_value = $this->getCurrentVersion();
 
     $form = id(new AphrontFormView())
       ->setUser($this->getUser())
       ->addSigil('transaction-append')
       ->setWorkflow(true)
       ->setFullWidth($this->fullWidth)
       ->setMetadata(
         array(
           'objectPHID' => $this->getObjectPHID(),
         ))
       ->setAction($this->getAction())
       ->setID($this->getFormID())
       ->addHiddenInput('__draft__', $draft_key)
       ->addHiddenInput($version_key, $version_value);
 
     $comment_actions = $this->getCommentActions();
     if ($comment_actions) {
       $action_map = array();
       $type_map = array();
 
       $comment_actions = mpull($comment_actions, null, 'getKey');
 
       $draft_actions = array();
       $draft_keys = array();
       if ($versioned_draft) {
         $draft_actions = $versioned_draft->getProperty('actions', array());
 
         if (!is_array($draft_actions)) {
           $draft_actions = array();
         }
 
         foreach ($draft_actions as $action) {
           $type = idx($action, 'type');
           $comment_action = idx($comment_actions, $type);
           if (!$comment_action) {
             continue;
           }
 
           $value = idx($action, 'value');
           $comment_action->setValue($value);
 
           $draft_keys[] = $type;
         }
       }
 
       foreach ($comment_actions as $key => $comment_action) {
         $key = $comment_action->getKey();
         $action_map[$key] = array(
           'key' => $key,
           'label' => $comment_action->getLabel(),
           'type' => $comment_action->getPHUIXControlType(),
           'spec' => $comment_action->getPHUIXControlSpecification(),
           'initialValue' => $comment_action->getInitialValue(),
           'groupKey' => $comment_action->getGroupKey(),
         );
 
         $type_map[$key] = $comment_action;
       }
 
       $options = $this->newCommentActionOptions($action_map);
 
       $action_id = celerity_generate_unique_node_id();
       $input_id = celerity_generate_unique_node_id();
       $place_id = celerity_generate_unique_node_id();
 
       $form->appendChild(
         phutil_tag(
           'input',
           array(
             'type' => 'hidden',
             'name' => 'editengine.actions',
             'id' => $input_id,
           )));
 
       $invisi_bar = phutil_tag(
         'div',
         array(
           'id' => $place_id,
           'class' => 'phui-comment-control-stack',
         ));
 
       $action_select = id(new AphrontFormSelectControl())
         ->addClass('phui-comment-fullwidth-control')
         ->addClass('phui-comment-action-control')
         ->setID($action_id)
         ->setOptions($options);
 
       $action_bar = phutil_tag(
         'div',
         array(
           'class' => 'phui-comment-action-bar grouped',
         ),
         array(
           $action_select,
         ));
 
       $form->appendChild($action_bar);
+
+      $info_view = $this->getInfoView();
+      if ($info_view) {
+        $info_box = id(new PHUIBoxView())
+          ->addMargin(PHUI::MARGIN_LARGE)
+          ->appendChild($info_view);
+        $form->appendChild($info_box);
+      }
+
       $form->appendChild($invisi_bar);
       $form->addClass('phui-comment-has-actions');
 
       Javelin::initBehavior(
         'comment-actions',
         array(
           'actionID' => $action_id,
           'inputID' => $input_id,
           'formID' => $this->getFormID(),
           'placeID' => $place_id,
           'panelID' => $this->getPreviewPanelID(),
           'timelineID' => $this->getPreviewTimelineID(),
           'actions' => $action_map,
           'showPreview' => $this->getShowPreview(),
           'actionURI' => $this->getAction(),
           'drafts' => $draft_keys,
         ));
     }
 
     $submit_button = id(new AphrontFormSubmitControl())
       ->addClass('phui-comment-fullwidth-control')
       ->addClass('phui-comment-submit-control')
       ->setValue($this->getSubmitButtonName());
 
     $form
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setID($this->getCommentID())
           ->addClass('phui-comment-fullwidth-control')
           ->addClass('phui-comment-textarea-control')
           ->setCanPin(true)
           ->setName('comment')
           ->setUser($this->getUser())
           ->setValue($draft_comment))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addClass('phui-comment-fullwidth-control')
           ->addClass('phui-comment-submit-control')
           ->setValue($this->getSubmitButtonName()));
 
     return $form;
   }
 
   private function renderPreviewPanel() {
 
     $preview = id(new PHUITimelineView())
       ->setID($this->getPreviewTimelineID());
 
     return phutil_tag(
       'div',
       array(
         'id'    => $this->getPreviewPanelID(),
         'style' => 'display: none',
         'class' => 'phui-comment-preview-view',
       ),
       $preview);
   }
 
   private function getPreviewPanelID() {
     if (!$this->previewPanelID) {
       $this->previewPanelID = celerity_generate_unique_node_id();
     }
     return $this->previewPanelID;
   }
 
   private function getPreviewTimelineID() {
     if (!$this->previewTimelineID) {
       $this->previewTimelineID = celerity_generate_unique_node_id();
     }
     return $this->previewTimelineID;
   }
 
   public function setFormID($id) {
     $this->formID = $id;
     return $this;
   }
 
   private function getFormID() {
     if (!$this->formID) {
       $this->formID = celerity_generate_unique_node_id();
     }
     return $this->formID;
   }
 
   private function getStatusID() {
     if (!$this->statusID) {
       $this->statusID = celerity_generate_unique_node_id();
     }
     return $this->statusID;
   }
 
   private function getCommentID() {
     if (!$this->commentID) {
       $this->commentID = celerity_generate_unique_node_id();
     }
     return $this->commentID;
   }
 
   private function newCommentActionOptions(array $action_map) {
     $options = array();
     $options['+'] = pht('Add Action...');
 
     // Merge options into groups.
     $groups = array();
     foreach ($action_map as $key => $item) {
       $group_key = $item['groupKey'];
       if (!isset($groups[$group_key])) {
         $groups[$group_key] = array();
       }
       $groups[$group_key][$key] = $item;
     }
 
     $group_specs = $this->getCommentActionGroups();
     $group_labels = mpull($group_specs, 'getLabel', 'getKey');
 
     // Reorder groups to put them in the same order as the recognized
     // group definitions.
     $groups = array_select_keys($groups, array_keys($group_labels)) + $groups;
 
     // Move options with no group to the end.
     $default_group = idx($groups, '');
     if ($default_group) {
       unset($groups['']);
       $groups[''] = $default_group;
     }
 
     foreach ($groups as $group_key => $group_items) {
       if (strlen($group_key)) {
         $group_label = idx($group_labels, $group_key, $group_key);
         $options[$group_label] = ipull($group_items, 'label');
       } else {
         foreach ($group_items as $key => $item) {
           $options[$key] = $item['label'];
         }
       }
     }
 
     return $options;
   }
 
 }