diff --git a/externals/amazon-ses/ses.php b/externals/amazon-ses/ses.php
index b0eebce69..9968e33ac 100644
--- a/externals/amazon-ses/ses.php
+++ b/externals/amazon-ses/ses.php
@@ -1,743 +1,722 @@
 <?php
 /**
 *
 * Copyright (c) 2011, Dan Myers.
 * Parts copyright (c) 2008, Donovan Schonknecht.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * This is a modified BSD license (the third clause has been removed).
 * The BSD license may be found here:
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
 *
 * SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
 * http://undesigned.org.za/2007/10/22/amazon-s3-php-class
 *
 */
 
 /**
 * Amazon SimpleEmailService PHP class
 *
 * @link http://sourceforge.net/projects/php-aws-ses/
 * version 0.8.1
 *
 */
 class SimpleEmailService
 {
   protected $__accessKey; // AWS Access key
   protected $__secretKey; // AWS Secret key
   protected $__host;
 
   public function getAccessKey() { return $this->__accessKey; }
   public function getSecretKey() { return $this->__secretKey; }
   public function getHost() { return $this->__host; }
 
   protected $__verifyHost = 1;
   protected $__verifyPeer = 1;
 
   // verifyHost and verifyPeer determine whether curl verifies ssl certificates.
   // It may be necessary to disable these checks on certain systems.
   // These only have an effect if SSL is enabled.
   public function verifyHost() { return $this->__verifyHost; }
   public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
 
   public function verifyPeer() { return $this->__verifyPeer; }
   public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
 
   // If you use exceptions, errors will be communicated by throwing a
   // SimpleEmailServiceException. By default, they will be trigger_error()'d.
   protected $__useExceptions = 0;
   public function useExceptions() { return $this->__useExceptions; }
   public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
 
   /**
   * Constructor
   *
   * @param string $accessKey Access key
   * @param string $secretKey Secret key
   * @return void
   */
   public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
+    if (!function_exists('simplexml_load_string')) {
+      throw new Exception(
+        pht(
+          'The PHP SimpleXML extension is not available, but this '.
+          'extension is required to send mail via Amazon SES, because '.
+          'Amazon SES returns API responses in XML format. Install or '.
+          'enable the SimpleXML extension.'));
+    }
+
+    // Catch mistakes with reading the wrong column out of the SES
+    // documentation. See T10728.
+    if (preg_match('(-smtp)', $host)) {
+      throw new Exception(
+        pht(
+          'Amazon SES is not configured correctly: the configured SES '.
+          'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '.
+          'endpoint.',
+          $host));
+    }
+
     if ($accessKey !== null && $secretKey !== null) {
       $this->setAuth($accessKey, $secretKey);
     }
+
     $this->__host = $host;
   }
 
   /**
   * Set AWS access key and secret key
   *
   * @param string $accessKey Access key
   * @param string $secretKey Secret key
   * @return void
   */
   public function setAuth($accessKey, $secretKey) {
     $this->__accessKey = $accessKey;
     $this->__secretKey = $secretKey;
   }
 
   /**
   * Lists the email addresses that have been verified and can be used as the 'From' address
   *
   * @return An array containing two items: a list of verified email addresses, and the request id.
   */
   public function listVerifiedEmailAddresses() {
     $rest = new SimpleEmailServiceRequest($this, 'GET');
     $rest->setParameter('Action', 'ListVerifiedEmailAddresses');
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('listVerifiedEmailAddresses', $rest->error);
-      return false;
-    }
 
     $response = array();
     if(!isset($rest->body)) {
       return $response;
     }
 
     $addresses = array();
     foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
       $addresses[] = (string)$address;
     }
 
     $response['Addresses'] = $addresses;
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
 
     return $response;
   }
 
   /**
   * Requests verification of the provided email address, so it can be used
   * as the 'From' address when sending emails through SimpleEmailService.
   *
   * After submitting this request, you should receive a verification email
   * from Amazon at the specified address containing instructions to follow.
   *
   * @param string email The email address to get verified
   * @return The request id for this request.
   */
   public function verifyEmailAddress($email) {
     $rest = new SimpleEmailServiceRequest($this, 'POST');
     $rest->setParameter('Action', 'VerifyEmailAddress');
     $rest->setParameter('EmailAddress', $email);
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('verifyEmailAddress', $rest->error);
-      return false;
-    }
 
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
     return $response;
   }
 
   /**
   * Removes the specified email address from the list of verified addresses.
   *
   * @param string email The email address to remove
   * @return The request id for this request.
   */
   public function deleteVerifiedEmailAddress($email) {
     $rest = new SimpleEmailServiceRequest($this, 'DELETE');
     $rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
     $rest->setParameter('EmailAddress', $email);
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('deleteVerifiedEmailAddress', $rest->error);
-      return false;
-    }
 
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
     return $response;
   }
 
   /**
   * Retrieves information on the current activity limits for this account.
   * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
   *
   * @return An array containing information on this account's activity limits.
   */
   public function getSendQuota() {
     $rest = new SimpleEmailServiceRequest($this, 'GET');
     $rest->setParameter('Action', 'GetSendQuota');
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('getSendQuota', $rest->error);
-      return false;
-    }
 
     $response = array();
     if(!isset($rest->body)) {
       return $response;
     }
 
     $response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
     $response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
     $response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
 
     return $response;
   }
 
   /**
   * Retrieves statistics for the last two weeks of activity on this account.
   * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
   *
   * @return An array of activity statistics.  Each array item covers a 15-minute period.
   */
   public function getSendStatistics() {
     $rest = new SimpleEmailServiceRequest($this, 'GET');
     $rest->setParameter('Action', 'GetSendStatistics');
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('getSendStatistics', $rest->error);
-      return false;
-    }
 
     $response = array();
     if(!isset($rest->body)) {
       return $response;
     }
 
     $datapoints = array();
     foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
       $p = array();
       $p['Bounces'] = (string)$datapoint->Bounces;
       $p['Complaints'] = (string)$datapoint->Complaints;
       $p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
       $p['Rejects'] = (string)$datapoint->Rejects;
       $p['Timestamp'] = (string)$datapoint->Timestamp;
 
       $datapoints[] = $p;
     }
 
     $response['SendDataPoints'] = $datapoints;
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
 
     return $response;
   }
 
 
   public function sendRawEmail($raw) {
     $rest = new SimpleEmailServiceRequest($this, 'POST');
     $rest->setParameter('Action', 'SendRawEmail');
     $rest->setParameter('RawMessage.Data', base64_encode($raw));
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('sendRawEmail', $rest->error);
-      return false;
-    }
 
     $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
     return $response;
   }
 
   /**
   * Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
   *
   * @return An array containing the unique identifier for this message and a separate request id.
   *         Returns false if the provided message is missing any required fields.
   */
   public function sendEmail($sesMessage) {
     if(!$sesMessage->validate()) {
       return false;
     }
 
     $rest = new SimpleEmailServiceRequest($this, 'POST');
     $rest->setParameter('Action', 'SendEmail');
 
     $i = 1;
     foreach($sesMessage->to as $to) {
       $rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
       $i++;
     }
 
     if(is_array($sesMessage->cc)) {
       $i = 1;
       foreach($sesMessage->cc as $cc) {
         $rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
         $i++;
       }
     }
 
     if(is_array($sesMessage->bcc)) {
       $i = 1;
       foreach($sesMessage->bcc as $bcc) {
         $rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
         $i++;
       }
     }
 
     if(is_array($sesMessage->replyto)) {
       $i = 1;
       foreach($sesMessage->replyto as $replyto) {
         $rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
         $i++;
       }
     }
 
     $rest->setParameter('Source', $sesMessage->from);
 
     if($sesMessage->returnpath != null) {
       $rest->setParameter('ReturnPath', $sesMessage->returnpath);
     }
 
     if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
       $rest->setParameter('Message.Subject.Data', $sesMessage->subject);
       if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
         $rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
       }
     }
 
 
     if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
       $rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
       if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
         $rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
       }
     }
 
     if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
       $rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
       if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
         $rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
       }
     }
 
     $rest = $rest->getResponse();
-    if($rest->error === false && $rest->code !== 200) {
-      $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
-    }
-    if($rest->error !== false) {
-      $this->__triggerError('sendEmail', $rest->error);
-      return false;
-    }
 
     $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
     $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
     return $response;
   }
 
   /**
   * Trigger an error message
   *
   * @internal Used by member functions to output errors
   * @param array $error Array containing error information
   * @return string
   */
   public function __triggerError($functionname, $error)
   {
     if($error == false) {
       $message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
     }
     else if(isset($error['curl']) && $error['curl'])
     {
       $message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
     }
     else if(isset($error['Error']))
     {
       $e = $error['Error'];
       $message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
     }
 
     if ($this->useExceptions()) {
       throw new SimpleEmailServiceException($message);
     } else {
       trigger_error($message, E_USER_WARNING);
     }
   }
 
   /**
   * Callback handler for 503 retries.
   *
   * @internal Used by SimpleDBRequest to call the user-specified callback, if set
   * @param $attempt The number of failed attempts so far
   * @return The retry delay in microseconds, or 0 to stop retrying.
   */
   public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
   {
     if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
       $callback = $this->__serviceUnavailableRetryDelayCallback;
       return $callback($attempt);
     }
     return 0;
   }
 }
 
 final class SimpleEmailServiceRequest
 {
   private $ses, $verb, $parameters = array();
   public $response;
 
   /**
   * Constructor
   *
   * @param string $ses The SimpleEmailService object making this request
   * @param string $action action
   * @param string $verb HTTP verb
   * @return mixed
   */
   function __construct($ses, $verb) {
     $this->ses = $ses;
     $this->verb = $verb;
     $this->response = new STDClass;
     $this->response->error = false;
   }
 
   /**
   * Set request parameter
   *
   * @param string  $key Key
   * @param string  $value Value
   * @param boolean $replace Whether to replace the key if it already exists (default true)
   * @return void
   */
   public function setParameter($key, $value, $replace = true) {
     if(!$replace && isset($this->parameters[$key]))
     {
       $temp = (array)($this->parameters[$key]);
       $temp[] = $value;
       $this->parameters[$key] = $temp;
     }
     else
     {
       $this->parameters[$key] = $value;
     }
   }
 
   /**
   * Get the response
   *
   * @return object | false
   */
   public function getResponse() {
 
     $params = array();
     foreach ($this->parameters as $var => $value)
     {
       if(is_array($value))
       {
         foreach($value as $v)
         {
           $params[] = $var.'='.$this->__customUrlEncode($v);
         }
       }
       else
       {
         $params[] = $var.'='.$this->__customUrlEncode($value);
       }
     }
 
     sort($params, SORT_STRING);
 
     // must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
     $date = gmdate('D, d M Y H:i:s e');
 
     $query = implode('&', $params);
 
     $headers = array();
     $headers[] = 'Date: '.$date;
     $headers[] = 'Host: '.$this->ses->getHost();
 
     $auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
     $auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
     $headers[] = 'X-Amzn-Authorization: '.$auth;
 
     $url = 'https://'.$this->ses->getHost().'/';
 
     // Basic setup
     $curl = curl_init();
     curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
 
     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
 
     // Request types
     switch ($this->verb) {
       case 'GET':
         $url .= '?'.$query;
         break;
       case 'POST':
         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
         curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
         $headers[] = 'Content-Type: application/x-www-form-urlencoded';
       break;
       case 'DELETE':
         $url .= '?'.$query;
         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
       break;
       default: break;
     }
     curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
     curl_setopt($curl, CURLOPT_HEADER, false);
 
     curl_setopt($curl, CURLOPT_URL, $url);
     curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
     curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
     curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
 
     // Execute, grab errors
-    if (curl_exec($curl)) {
-      $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
-    } else {
-      $this->response->error = array(
-        'curl' => true,
-        'code' => curl_errno($curl),
-        'message' => curl_error($curl),
-        'resource' => $this->resource
-      );
+    if (!curl_exec($curl)) {
+      throw new SimpleEmailServiceException(
+        pht(
+          'Encountered an error while making an HTTP request to Amazon SES '.
+          '(cURL Error #%d): %s',
+          curl_errno($curl),
+          curl_error($curl)));
+    }
+
+    $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+    if ($this->response->code != 200) {
+      throw new SimpleEmailServiceException(
+        pht(
+          'Unexpected HTTP status while making request to Amazon SES: '.
+          'expected 200, got %s.',
+          $this->response->code));
     }
 
     @curl_close($curl);
 
     // Parse body into XML
     if ($this->response->error === false && isset($this->response->body)) {
       $this->response->body = simplexml_load_string($this->response->body);
 
       // Grab SES errors
       if (!in_array($this->response->code, array(200, 201, 202, 204))
         && isset($this->response->body->Error)) {
         $error = $this->response->body->Error;
         $output = array();
         $output['curl'] = false;
         $output['Error'] = array();
         $output['Error']['Type'] = (string)$error->Type;
         $output['Error']['Code'] = (string)$error->Code;
         $output['Error']['Message'] = (string)$error->Message;
         $output['RequestId'] = (string)$this->response->body->RequestId;
 
         $this->response->error = $output;
         unset($this->response->body);
       }
     }
 
     return $this->response;
   }
 
   /**
   * CURL write callback
   *
   * @param resource &$curl CURL resource
   * @param string &$data Data
   * @return integer
   */
   private function __responseWriteCallback(&$curl, &$data) {
     if(!isset($this->response->body)) $this->response->body = '';
     $this->response->body .= $data;
     return strlen($data);
   }
 
   /**
   * Contributed by afx114
   * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
   * PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
   * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
   *
   * @param string $var String to encode
   * @return string
   */
   private function __customUrlEncode($var) {
     return str_replace('%7E', '~', rawurlencode($var));
   }
 
   /**
   * Generate the auth string using Hmac-SHA256
   *
   * @internal Used by SimpleDBRequest::getResponse()
   * @param string $string String to sign
   * @return string
   */
   private function __getSignature($string) {
     return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
   }
 }
 
 
 final class SimpleEmailServiceMessage {
 
   // these are public for convenience only
   // these are not to be used outside of the SimpleEmailService class!
   public $to, $cc, $bcc, $replyto;
   public $from, $returnpath;
   public $subject, $messagetext, $messagehtml;
   public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
 
   function __construct() {
     $to = array();
     $cc = array();
     $bcc = array();
     $replyto = array();
 
     $from = null;
     $returnpath = null;
 
     $subject = null;
     $messagetext = null;
     $messagehtml = null;
 
     $subjectCharset = null;
     $messageTextCharset = null;
     $messageHtmlCharset = null;
   }
 
 
   /**
   * addTo, addCC, addBCC, and addReplyTo have the following behavior:
   * If a single address is passed, it is appended to the current list of addresses.
   * If an array of addresses is passed, that array is merged into the current list.
   */
   function addTo($to) {
     if(!is_array($to)) {
       $this->to[] = $to;
     }
     else {
       $this->to = array_merge($this->to, $to);
     }
   }
 
   function addCC($cc) {
     if(!is_array($cc)) {
       $this->cc[] = $cc;
     }
     else {
       $this->cc = array_merge($this->cc, $cc);
     }
   }
 
   function addBCC($bcc) {
     if(!is_array($bcc)) {
       $this->bcc[] = $bcc;
     }
     else {
       $this->bcc = array_merge($this->bcc, $bcc);
     }
   }
 
   function addReplyTo($replyto) {
     if(!is_array($replyto)) {
       $this->replyto[] = $replyto;
     }
     else {
       $this->replyto = array_merge($this->replyto, $replyto);
     }
   }
 
   function setFrom($from) {
     $this->from = $from;
   }
 
   function setReturnPath($returnpath) {
     $this->returnpath = $returnpath;
   }
 
   function setSubject($subject) {
     $this->subject = $subject;
   }
 
   function setSubjectCharset($charset) {
     $this->subjectCharset = $charset;
   }
 
   function setMessageFromString($text, $html = null) {
     $this->messagetext = $text;
     $this->messagehtml = $html;
   }
 
   function setMessageFromFile($textfile, $htmlfile = null) {
     if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
       $this->messagetext = file_get_contents($textfile);
     }
     if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
       $this->messagehtml = file_get_contents($htmlfile);
     }
   }
 
   function setMessageFromURL($texturl, $htmlurl = null) {
     $this->messagetext = file_get_contents($texturl);
     if($htmlurl !== null) {
       $this->messagehtml = file_get_contents($htmlurl);
     }
   }
 
   function setMessageCharset($textCharset, $htmlCharset = null) {
     $this->messageTextCharset = $textCharset;
     $this->messageHtmlCharset = $htmlCharset;
   }
 
   /**
   * Validates whether the message object has sufficient information to submit a request to SES.
   * This does not guarantee the message will arrive, nor that the request will succeed;
   * instead, it makes sure that no required fields are missing.
   *
   * This is used internally before attempting a SendEmail or SendRawEmail request,
   * but it can be used outside of this file if verification is desired.
   * May be useful if e.g. the data is being populated from a form; developers can generally
   * use this function to verify completeness instead of writing custom logic.
   *
   * @return boolean
   */
   public function validate() {
     if(count($this->to) == 0)
       return false;
     if($this->from == null || strlen($this->from) == 0)
       return false;
     if($this->messagetext == null)
       return false;
     return true;
   }
 }
 
 
 /**
  * Thrown by SimpleEmailService when errors occur if you call
  * enableUseExceptions(true).
  */
 final class SimpleEmailServiceException extends Exception {
 
 }
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 3d5f59abe..edc6372e8 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2347 +1,2347 @@
 <?php
 
 /**
  * This file is automatically generated. Use 'bin/celerity map' to rebuild it.
  *
  * @generated
  */
 return array(
   'names' => array(
-    'core.pkg.css' => '2d0339fc',
+    'core.pkg.css' => '82cefddc',
     'core.pkg.js' => 'e5484f37',
     'darkconsole.pkg.js' => 'e7393ebb',
     'differential.pkg.css' => '7ba78475',
     'differential.pkg.js' => 'd0cd0df6',
     'diffusion.pkg.css' => 'dc8e0cc2',
     'diffusion.pkg.js' => '3a9a8bfa',
     'maniphest.pkg.css' => '4845691a',
     'maniphest.pkg.js' => '949a7498',
     'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
     'rsrc/css/aphront/dark-console.css' => 'f54bf286',
     'rsrc/css/aphront/dialog-view.css' => 'b4334e08',
     'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
     'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
     'rsrc/css/aphront/multi-column.css' => 'fd18389d',
     'rsrc/css/aphront/notification.css' => '7f684b62',
     'rsrc/css/aphront/panel-view.css' => '8427b78d',
     'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758',
-    'rsrc/css/aphront/table-view.css' => '036b6cdc',
+    'rsrc/css/aphront/table-view.css' => '9258e19f',
     'rsrc/css/aphront/tokenizer.css' => '056da01b',
     'rsrc/css/aphront/tooltip.css' => '1a07aea8',
     'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
     'rsrc/css/aphront/typeahead.css' => 'd4f16145',
     'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
     'rsrc/css/application/auth/auth.css' => '0877ed6e',
     'rsrc/css/application/base/main-menu-view.css' => 'd00a795a',
     'rsrc/css/application/base/notification-menu.css' => 'f31c0bde',
     'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601',
     'rsrc/css/application/base/phui-theme.css' => '027ba77e',
     'rsrc/css/application/base/standard-page-view.css' => 'e709f6d0',
     'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
     'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4',
     'rsrc/css/application/config/config-options.css' => '0ede4c9b',
     'rsrc/css/application/config/config-template.css' => '8e6c6fcd',
     'rsrc/css/application/config/config-welcome.css' => '6abd79be',
     'rsrc/css/application/config/setup-issue.css' => 'db7e9c40',
     'rsrc/css/application/config/unhandled-exception.css' => '4c96257a',
     'rsrc/css/application/conpherence/durable-column.css' => '86396117',
     'rsrc/css/application/conpherence/menu.css' => 'f99fee4c',
     'rsrc/css/application/conpherence/message-pane.css' => '5897d3ac',
     'rsrc/css/application/conpherence/notification.css' => '6cdcc253',
     'rsrc/css/application/conpherence/transaction.css' => '85d0974c',
     'rsrc/css/application/conpherence/update.css' => 'faf6be09',
     'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba',
     'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
     'rsrc/css/application/countdown/timer.css' => '96696f21',
     'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a',
-    'rsrc/css/application/dashboard/dashboard.css' => 'eb458607',
+    'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
     'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
     'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
     'rsrc/css/application/differential/changeset-view.css' => '3e3b0b76',
     'rsrc/css/application/differential/core.css' => '5b7b8ff4',
     'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
     'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
     'rsrc/css/application/differential/revision-history.css' => '0e8eb855',
     'rsrc/css/application/differential/revision-list.css' => 'f3c47d33',
     'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55',
     'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d',
     'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb',
     'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3',
     'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
     'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2',
     'rsrc/css/application/flag/flag.css' => '5337623f',
     'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4',
     'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
     'rsrc/css/application/herald/herald.css' => 'dc31f6e9',
     'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
     'rsrc/css/application/maniphest/report.css' => '9b9580b7',
     'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
     'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
     'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6',
     'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
     'rsrc/css/application/paste/paste.css' => '1898e534',
     'rsrc/css/application/people/people-profile.css' => '2473d929',
     'rsrc/css/application/phame/phame.css' => '737792d6',
     'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee',
     'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
     'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
     'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02',
     'rsrc/css/application/phortune/phortune.css' => '9149f103',
     'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad',
     'rsrc/css/application/phriction/phriction-document-css.css' => 'd1861e06',
     'rsrc/css/application/policy/policy-edit.css' => '815c66f7',
     'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
     'rsrc/css/application/policy/policy.css' => '957ea14c',
     'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96',
     'rsrc/css/application/project/project-card-view.css' => '9418c97d',
     'rsrc/css/application/project/project-view.css' => 'cbaa10a1',
     'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
     'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
     'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
     'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
     'rsrc/css/application/search/search-results.css' => '7dea472c',
     'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230',
     'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
     'rsrc/css/application/uiexample/example.css' => '528b19de',
     'rsrc/css/core/core.css' => 'd0801452',
     'rsrc/css/core/remarkup.css' => '2c9ed46f',
     'rsrc/css/core/syntax.css' => '9fd11da8',
     'rsrc/css/core/z-index.css' => '5b6fcf3f',
     'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
     'rsrc/css/font/font-aleo.css' => '8bdb2835',
     'rsrc/css/font/font-awesome.css' => 'c43323c5',
     'rsrc/css/font/font-lato.css' => 'c7ccd872',
     'rsrc/css/font/phui-font-icon-base.css' => '6449bce8',
     'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
     'rsrc/css/layout/phabricator-side-menu-view.css' => '3a3d9f41',
     'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983',
     'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93',
     'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338',
     'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0',
     'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893',
     'rsrc/css/phui/phui-action-list.css' => 'c5eba19d',
     'rsrc/css/phui/phui-action-panel.css' => '91c7b835',
     'rsrc/css/phui/phui-badge.css' => 'f25c3476',
     'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
-    'rsrc/css/phui/phui-box.css' => 'b2d49bae',
+    'rsrc/css/phui/phui-box.css' => 'd909ea3d',
     'rsrc/css/phui/phui-button.css' => 'a64a8de6',
     'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
-    'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5',
+    'rsrc/css/phui/phui-crumbs-view.css' => '1a1265d4',
     'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
-    'rsrc/css/phui/phui-document-pro.css' => '92d5b648',
+    'rsrc/css/phui/phui-document-pro.css' => '73e45fd2',
     'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
     'rsrc/css/phui/phui-document.css' => '9c71d2bf',
     'rsrc/css/phui/phui-feed-story.css' => '04aec08f',
     'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
     'rsrc/css/phui/phui-form-view.css' => '6a51768e',
     'rsrc/css/phui/phui-form.css' => 'aac1d51d',
     'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
     'rsrc/css/phui/phui-header-view.css' => '230254d3',
     'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
     'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
     'rsrc/css/phui/phui-icon.css' => '3f33ab57',
     'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
     'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
     'rsrc/css/phui/phui-info-view.css' => '28efab79',
     'rsrc/css/phui/phui-list.css' => '9da2aa00',
     'rsrc/css/phui/phui-object-box.css' => '6b487c57',
     'rsrc/css/phui/phui-object-item-list-view.css' => '8d99e42b',
     'rsrc/css/phui/phui-pager.css' => 'bea33d23',
     'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
     'rsrc/css/phui/phui-profile-menu.css' => '7e92a89a',
     'rsrc/css/phui/phui-property-list-view.css' => '1d42ee7c',
     'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
     'rsrc/css/phui/phui-segment-bar-view.css' => '46342871',
     'rsrc/css/phui/phui-spacing.css' => '042804d6',
     'rsrc/css/phui/phui-status.css' => '37309046',
     'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
     'rsrc/css/phui/phui-timeline-view.css' => '6e342216',
-    'rsrc/css/phui/phui-two-column-view.css' => '691fec04',
+    'rsrc/css/phui/phui-two-column-view.css' => 'b9538af1',
     'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
     'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
     'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96',
     'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373',
     'rsrc/css/sprite-login.css' => '60e8560e',
     'rsrc/css/sprite-menu.css' => '9dd65b92',
     'rsrc/css/sprite-tokens.css' => '4f399012',
     'rsrc/externals/d3/d3.min.js' => 'a11a5ff2',
     'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7',
     'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e',
     'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0',
     'rsrc/externals/font/aleo/aleo-bold.woff' => '93b513a1',
     'rsrc/externals/font/aleo/aleo-bold.woff2' => '75fbf322',
     'rsrc/externals/font/aleo/aleo-regular.eot' => 'a4e29e2f',
     'rsrc/externals/font/aleo/aleo-regular.svg' => '42a86f7a',
     'rsrc/externals/font/aleo/aleo-regular.ttf' => '751e7479',
     'rsrc/externals/font/aleo/aleo-regular.woff' => 'c3744be9',
     'rsrc/externals/font/aleo/aleo-regular.woff2' => '851aa0ee',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '346fbcc5',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '510fccb2',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => '0334f580',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '45dca585',
     'rsrc/externals/font/lato/lato-bold.eot' => '99fbcf8c',
     'rsrc/externals/font/lato/lato-bold.svg' => '2aa83045',
     'rsrc/externals/font/lato/lato-bold.ttf' => '0a7141f7',
     'rsrc/externals/font/lato/lato-bold.woff' => 'f5db2061',
     'rsrc/externals/font/lato/lato-bold.woff2' => '37a94ecd',
     'rsrc/externals/font/lato/lato-bolditalic.eot' => 'b93389d0',
     'rsrc/externals/font/lato/lato-bolditalic.svg' => '5442e1ef',
     'rsrc/externals/font/lato/lato-bolditalic.ttf' => 'dad31252',
     'rsrc/externals/font/lato/lato-bolditalic.woff' => 'e53bcf47',
     'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'd035007f',
     'rsrc/externals/font/lato/lato-italic.eot' => '6a903f5d',
     'rsrc/externals/font/lato/lato-italic.svg' => '0dc7cf2f',
     'rsrc/externals/font/lato/lato-italic.ttf' => '629f64f0',
     'rsrc/externals/font/lato/lato-italic.woff' => '678dc4bb',
     'rsrc/externals/font/lato/lato-italic.woff2' => '7c8dd650',
     'rsrc/externals/font/lato/lato-regular.eot' => '848dfb1e',
     'rsrc/externals/font/lato/lato-regular.svg' => 'cbd5fd6b',
     'rsrc/externals/font/lato/lato-regular.ttf' => 'e270165b',
     'rsrc/externals/font/lato/lato-regular.woff' => '13d39fe2',
     'rsrc/externals/font/lato/lato-regular.woff2' => '57a9f742',
     'rsrc/externals/javelin/core/Event.js' => '2ee659ce',
     'rsrc/externals/javelin/core/Stratcom.js' => '6ad39b6f',
     'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4',
     'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85',
     'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313',
     'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d',
     'rsrc/externals/javelin/core/init.js' => '3010e992',
     'rsrc/externals/javelin/core/init_node.js' => 'c234aded',
     'rsrc/externals/javelin/core/install.js' => '05270951',
     'rsrc/externals/javelin/core/util.js' => '93cc50d6',
     'rsrc/externals/javelin/docs/Base.js' => '74676256',
     'rsrc/externals/javelin/docs/onload.js' => 'e819c479',
     'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a',
     'rsrc/externals/javelin/ext/fx/FX.js' => '54b612ba',
     'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => 'f6555212',
     'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '2b8de964',
     'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '1ad0a787',
     'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '76f4ebed',
     'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => 'c90a04fc',
     'rsrc/externals/javelin/ext/view/HTMLView.js' => 'fe287620',
     'rsrc/externals/javelin/ext/view/View.js' => '0f764c35',
     'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => 'f829edb3',
     'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => '47830651',
     'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2',
     'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472',
     'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb',
     'rsrc/externals/javelin/ext/view/__tests__/View.js' => '6450b38b',
     'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5',
     'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9',
     'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03',
     'rsrc/externals/javelin/lib/DOM.js' => '805b806a',
     'rsrc/externals/javelin/lib/History.js' => 'd4505101',
     'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
     'rsrc/externals/javelin/lib/Leader.js' => '331b1611',
     'rsrc/externals/javelin/lib/Mask.js' => '8a41885b',
     'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b',
     'rsrc/externals/javelin/lib/Request.js' => '94b750d2',
     'rsrc/externals/javelin/lib/Resource.js' => '44959b73',
     'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692',
     'rsrc/externals/javelin/lib/Router.js' => '29274e2b',
     'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c',
     'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5',
     'rsrc/externals/javelin/lib/URI.js' => 'c989ade3',
     'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8',
     'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4',
     'rsrc/externals/javelin/lib/Workflow.js' => '28cfbdd0',
     'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
     'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
     'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68',
     'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9',
     'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783',
     'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a',
     'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '8d3bc1b2',
     'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f',
     'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'e6e25838',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '1bc11c4a',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
     'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962',
     'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3',
     'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672',
     'rsrc/favicons/favicon-128.png' => '47cdff03',
     'rsrc/favicons/favicon-16x16.png' => 'ee2523ac',
     'rsrc/favicons/favicon-32x32.png' => 'b6a8150e',
     'rsrc/favicons/favicon-96x96.png' => '8f7ea177',
     'rsrc/favicons/mask-icon.svg' => '0460cb1f',
     'rsrc/image/BFCFDA.png' => 'd5ec91f4',
     'rsrc/image/actions/edit.png' => '2fc41442',
     'rsrc/image/avatar.png' => 'e132bb6a',
     'rsrc/image/checker_dark.png' => 'd8e65881',
     'rsrc/image/checker_light.png' => 'a0155918',
     'rsrc/image/checker_lighter.png' => 'd5da91b6',
     'rsrc/image/d5d8e1.png' => '0c2a1497',
     'rsrc/image/darkload.gif' => '1ffd3ec6',
     'rsrc/image/divot.png' => '94dded62',
     'rsrc/image/examples/hero.png' => '979a86ae',
     'rsrc/image/grippy_texture.png' => 'aca81e2f',
     'rsrc/image/icon/fatcow/arrow_branch.png' => '2537c01c',
     'rsrc/image/icon/fatcow/arrow_merge.png' => '21b660e0',
     'rsrc/image/icon/fatcow/bullet_black.png' => 'ff190031',
     'rsrc/image/icon/fatcow/bullet_orange.png' => 'e273e5bb',
     'rsrc/image/icon/fatcow/bullet_red.png' => 'c0b75434',
     'rsrc/image/icon/fatcow/calendar_edit.png' => '24632275',
     'rsrc/image/icon/fatcow/document_black.png' => '45fe1c60',
     'rsrc/image/icon/fatcow/flag_blue.png' => 'a01abb1d',
     'rsrc/image/icon/fatcow/flag_finish.png' => '67825cee',
     'rsrc/image/icon/fatcow/flag_ghost.png' => '20ca8783',
     'rsrc/image/icon/fatcow/flag_green.png' => '7e0eaa7a',
     'rsrc/image/icon/fatcow/flag_orange.png' => '9e73df66',
     'rsrc/image/icon/fatcow/flag_pink.png' => '7e92f3b2',
     'rsrc/image/icon/fatcow/flag_purple.png' => 'cc517522',
     'rsrc/image/icon/fatcow/flag_red.png' => '04ec726f',
     'rsrc/image/icon/fatcow/flag_yellow.png' => '73946fd4',
     'rsrc/image/icon/fatcow/key_question.png' => '52a0c26a',
     'rsrc/image/icon/fatcow/link.png' => '7afd4d5e',
     'rsrc/image/icon/fatcow/page_white_edit.png' => '39a2eed8',
     'rsrc/image/icon/fatcow/page_white_put.png' => '08c95a0c',
     'rsrc/image/icon/fatcow/source/conduit.png' => '4ea01d2f',
     'rsrc/image/icon/fatcow/source/email.png' => '9bab3239',
     'rsrc/image/icon/fatcow/source/fax.png' => '04195e68',
     'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264',
     'rsrc/image/icon/fatcow/source/tablet.png' => '49396799',
     'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d',
     'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8',
     'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e',
     'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b',
     'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3',
     'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0',
     'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21',
     'rsrc/image/icon/subscribe.png' => 'd03ed5a5',
     'rsrc/image/icon/tango/attachment.png' => 'ecc8022e',
     'rsrc/image/icon/tango/edit.png' => '929a1363',
     'rsrc/image/icon/tango/go-down.png' => '96d95e43',
     'rsrc/image/icon/tango/log.png' => 'b08cc63a',
     'rsrc/image/icon/tango/upload.png' => '7bbb7984',
     'rsrc/image/icon/unsubscribe.png' => '25725013',
     'rsrc/image/lightblue-header.png' => '5c168b6d',
     'rsrc/image/main_texture.png' => '29a2c5ad',
     'rsrc/image/menu_texture.png' => '5a17580d',
     'rsrc/image/people/harding.png' => '45aa614e',
     'rsrc/image/people/jefferson.png' => 'afca0e53',
     'rsrc/image/people/lincoln.png' => '9369126d',
     'rsrc/image/people/mckinley.png' => 'fb8f16ce',
     'rsrc/image/people/taft.png' => 'd7bc402c',
     'rsrc/image/people/user0.png' => '03dacaea',
     'rsrc/image/people/user1.png' => '4a4e7702',
     'rsrc/image/people/user2.png' => '47a0ee40',
     'rsrc/image/people/user3.png' => '835ff627',
     'rsrc/image/people/user4.png' => 'b0e830f1',
     'rsrc/image/people/user5.png' => '9c95b369',
     'rsrc/image/people/user6.png' => 'ba3fbfb0',
     'rsrc/image/people/user7.png' => 'da613924',
     'rsrc/image/people/user8.png' => 'f1035edf',
     'rsrc/image/people/user9.png' => '66730be3',
     'rsrc/image/people/washington.png' => '40dd301c',
     'rsrc/image/phrequent_active.png' => 'a466a8ed',
     'rsrc/image/phrequent_inactive.png' => 'bfc15a69',
     'rsrc/image/sprite-login-X2.png' => 'e3991e37',
     'rsrc/image/sprite-login.png' => '03d5af29',
     'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5',
     'rsrc/image/sprite-menu.png' => 'd7a99faa',
     'rsrc/image/sprite-tokens-X2.png' => '348f1745',
     'rsrc/image/sprite-tokens.png' => 'ce0b62be',
     'rsrc/image/texture/card-gradient.png' => '815f26e8',
     'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8',
     'rsrc/image/texture/dark-menu.png' => '7e22296e',
     'rsrc/image/texture/grip.png' => '719404f3',
     'rsrc/image/texture/panel-header-gradient.png' => 'e3b8dcfe',
     'rsrc/image/texture/phlnx-bg.png' => '8d819209',
     'rsrc/image/texture/pholio-background.gif' => 'ba29239c',
     'rsrc/image/texture/table_header.png' => '5c433037',
     'rsrc/image/texture/table_header_hover.png' => '038ec3b9',
     'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
     'rsrc/js/application/aphlict/Aphlict.js' => '5359e785',
     'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '031cee25',
     'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d',
     'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
     'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66',
     'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
     'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2',
     'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8',
     'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f',
     'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408',
     'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2',
     'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a',
     'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091',
     'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d',
     'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861',
     'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3',
     'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'a8458711',
     'rsrc/js/application/countdown/timer.js' => 'e4cc26b3',
     'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145',
     'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
     'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '019f36c4',
     'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
     'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
     'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756',
     'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '64a5550f',
     'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
     'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
     'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76',
     'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1',
     'rsrc/js/application/differential/behavior-dropdown-menus.js' => '9a6b9324',
     'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '4fbbc3e9',
     'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492',
     'rsrc/js/application/differential/behavior-populate.js' => '8694b1df',
     'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb',
     'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d',
     'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7',
     'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a',
     'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04',
     'rsrc/js/application/diffusion/behavior-commit-graph.js' => '5a0b1a64',
     'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef',
     'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667',
     'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947',
     'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
     'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781',
     'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
     'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
     'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
     'rsrc/js/application/herald/HeraldRuleEditor.js' => 'd6a7e717',
     'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
     'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
     'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7',
     'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5',
     'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
     'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
     'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
     'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0',
     'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3',
     'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc',
     'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '246dc085',
     'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'fbe497e7',
     'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf',
     'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c',
     'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
     'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
     'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
     'rsrc/js/application/projects/WorkboardBoard.js' => '52291776',
     'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f',
     'rsrc/js/application/projects/WorkboardColumn.js' => 'bae58312',
     'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed',
     'rsrc/js/application/projects/behavior-project-boards.js' => '14a1faae',
     'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
     'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
     'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
     'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8',
     'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
     'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43',
     'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072',
     'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
     'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
     'rsrc/js/application/transactions/behavior-comment-actions.js' => '06460e71',
     'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
     'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
     'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6',
     'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6',
     'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea',
     'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec',
     'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3',
     'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807',
     'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8',
     'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea',
     'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403',
     'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850',
     'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8',
     'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4',
     'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f',
     'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711',
     'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee',
     'rsrc/js/application/uiexample/busy-example.js' => '60479091',
     'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
     'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
     'rsrc/js/core/Busy.js' => '59a7976a',
     'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5',
     'rsrc/js/core/DraggableList.js' => '5a13c79f',
     'rsrc/js/core/FileUpload.js' => '680ea2c8',
     'rsrc/js/core/Hovercard.js' => '1bd28176',
     'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
     'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
     'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
     'rsrc/js/core/Notification.js' => 'ccf1cbf8',
     'rsrc/js/core/Prefab.js' => 'e67df814',
     'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
     'rsrc/js/core/TextAreaUtils.js' => '5813016a',
     'rsrc/js/core/Title.js' => 'df5e11d2',
     'rsrc/js/core/ToolTip.js' => '6323f942',
     'rsrc/js/core/behavior-active-nav.js' => 'e379b58e',
     'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
     'rsrc/js/core/behavior-autofocus.js' => '7319e029',
     'rsrc/js/core/behavior-choose-control.js' => '327a00d1',
     'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2',
     'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae',
     'rsrc/js/core/behavior-device.js' => 'b5b36110',
     'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e',
     'rsrc/js/core/behavior-error-log.js' => '6882e80a',
     'rsrc/js/core/behavior-fancy-datepicker.js' => '8ae55229',
     'rsrc/js/core/behavior-file-tree.js' => '88236f00',
     'rsrc/js/core/behavior-form.js' => '5c54cbf3',
     'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
     'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404',
     'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03',
     'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
     'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
     'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
     'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6',
     'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
     'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
     'rsrc/js/core/behavior-more.js' => 'a80d0378',
     'rsrc/js/core/behavior-object-selector.js' => '49b73b36',
     'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
     'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03',
     'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '340c8eff',
     'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b',
     'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e',
     'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
     'rsrc/js/core/behavior-reveal-content.js' => '60821bc7',
     'rsrc/js/core/behavior-scrollbar.js' => '834a1173',
     'rsrc/js/core/behavior-search-typeahead.js' => '06c32383',
     'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
     'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0',
     'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33',
     'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884',
     'rsrc/js/core/behavior-tooltip.js' => '42fcb747',
     'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d',
     'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
     'rsrc/js/core/phtize.js' => 'd254d646',
     'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475',
     'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
     'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9',
     'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
     'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
     'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
     'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
-    'rsrc/js/phuix/PHUIXFormControl.js' => 'a7763e11',
+    'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8',
     'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
   ),
   'symbols' => array(
     'almanac-css' => 'dbb9b3af',
     'aphront-bars' => '231ac33c',
     'aphront-dark-console-css' => 'f54bf286',
     'aphront-dialog-view-css' => 'b4334e08',
     'aphront-list-filter-view-css' => '5d6f0526',
     'aphront-multi-column-view-css' => 'fd18389d',
     'aphront-panel-view-css' => '8427b78d',
-    'aphront-table-view-css' => '036b6cdc',
+    'aphront-table-view-css' => '9258e19f',
     'aphront-tokenizer-control-css' => '056da01b',
     'aphront-tooltip-css' => '1a07aea8',
     'aphront-typeahead-control-css' => 'd4f16145',
     'auth-css' => '0877ed6e',
     'bulk-job-css' => 'df9c1d4a',
     'changeset-view-manager' => 'a2828756',
     'conduit-api-css' => '7bc725c4',
     'config-options-css' => '0ede4c9b',
     'config-welcome-css' => '6abd79be',
     'conpherence-durable-column-view' => '86396117',
     'conpherence-menu-css' => 'f99fee4c',
     'conpherence-message-pane-css' => '5897d3ac',
     'conpherence-notification-css' => '6cdcc253',
     'conpherence-thread-manager' => '01774ab2',
     'conpherence-transaction-css' => '85d0974c',
     'conpherence-update-css' => 'faf6be09',
     'conpherence-widget-pane-css' => '775eaaba',
     'd3' => 'a11a5ff2',
     'differential-changeset-view-css' => '3e3b0b76',
     'differential-core-view-css' => '5b7b8ff4',
     'differential-inline-comment-editor' => '64a5550f',
     'differential-revision-add-comment-css' => 'c47f8c40',
     'differential-revision-comment-css' => '14b8565a',
     'differential-revision-history-css' => '0e8eb855',
     'differential-revision-list-css' => 'f3c47d33',
     'differential-table-of-contents-css' => 'ae4b7a55',
     'diffusion-icons-css' => '3311444d',
     'diffusion-readme-css' => '297373eb',
     'diffusion-source-css' => '68b30fd3',
     'diviner-shared-css' => 'aa3656aa',
     'font-aleo' => '8bdb2835',
     'font-fontawesome' => 'c43323c5',
     'font-lato' => 'c7ccd872',
     'global-drag-and-drop-css' => '5c1b47c2',
     'harbormaster-css' => 'f491c9f4',
     'herald-css' => 'dc31f6e9',
     'herald-rule-editor' => 'd6a7e717',
     'herald-test-css' => 'a52e323e',
     'inline-comment-summary-css' => '51efda3a',
     'javelin-aphlict' => '5359e785',
     'javelin-behavior' => '61cbc29a',
     'javelin-behavior-aphlict-dropdown' => '031cee25',
     'javelin-behavior-aphlict-listen' => 'fb20ac8d',
     'javelin-behavior-aphlict-status' => 'ea681761',
     'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
     'javelin-behavior-aphront-crop' => 'fa0f4fc2',
     'javelin-behavior-aphront-drag-and-drop-textarea' => '4f6a4b4e',
     'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3',
     'javelin-behavior-aphront-more' => 'a80d0378',
     'javelin-behavior-audio-source' => '59b251eb',
     'javelin-behavior-audit-preview' => 'd835b03a',
     'javelin-behavior-bulk-job-reload' => 'edf8a145',
     'javelin-behavior-choose-control' => '327a00d1',
     'javelin-behavior-comment-actions' => '06460e71',
     'javelin-behavior-config-reorder-fields' => 'b6993408',
     'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a',
     'javelin-behavior-conpherence-menu' => '1d45c74d',
     'javelin-behavior-conpherence-pontificate' => '21ba5861',
     'javelin-behavior-conpherence-widget-pane' => 'a8458711',
     'javelin-behavior-countdown-timer' => 'e4cc26b3',
     'javelin-behavior-dark-console' => 'f411b6ae',
     'javelin-behavior-dashboard-async-panel' => '469c0d9e',
     'javelin-behavior-dashboard-move-panels' => '019f36c4',
     'javelin-behavior-dashboard-query-panel-select' => '453c5375',
     'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
     'javelin-behavior-day-view' => '5c46cff2',
     'javelin-behavior-desktop-notifications-control' => 'edd1ba66',
     'javelin-behavior-device' => 'b5b36110',
     'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
     'javelin-behavior-differential-comment-jump' => '4fdb476d',
     'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
     'javelin-behavior-differential-dropdown-menus' => '9a6b9324',
     'javelin-behavior-differential-edit-inline-comments' => '4fbbc3e9',
     'javelin-behavior-differential-feedback-preview' => 'b064af76',
     'javelin-behavior-differential-keyboard-navigation' => '2c426492',
     'javelin-behavior-differential-populate' => '8694b1df',
     'javelin-behavior-differential-toggle-files' => 'ca3f91eb',
     'javelin-behavior-differential-user-select' => 'a8d8459d',
     'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04',
     'javelin-behavior-diffusion-commit-graph' => '5a0b1a64',
     'javelin-behavior-diffusion-jump-to' => '73d09eef',
     'javelin-behavior-diffusion-locate-file' => '6d3e1947',
     'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
     'javelin-behavior-doorkeeper-tag' => 'e5822781',
     'javelin-behavior-drydock-live-operation-status' => '901935ef',
     'javelin-behavior-durable-column' => 'c72aa091',
     'javelin-behavior-editengine-reorder-configs' => 'd7a74243',
     'javelin-behavior-editengine-reorder-fields' => 'b59e1e96',
     'javelin-behavior-error-log' => '6882e80a',
     'javelin-behavior-event-all-day' => '38dcf3c8',
     'javelin-behavior-fancy-datepicker' => '8ae55229',
     'javelin-behavior-global-drag-and-drop' => 'c8e57404',
     'javelin-behavior-herald-rule-editor' => '7ebaeed3',
     'javelin-behavior-high-security-warning' => 'a464fe03',
     'javelin-behavior-history-install' => '7ee2b591',
     'javelin-behavior-icon-composer' => '8499b6ab',
     'javelin-behavior-launch-icon-composer' => '48086888',
     'javelin-behavior-lightbox-attachments' => 'f8ba29d7',
     'javelin-behavior-line-chart' => 'e4232876',
     'javelin-behavior-load-blame' => '42126667',
     'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
     'javelin-behavior-maniphest-batch-selector' => '7b98d7c5',
     'javelin-behavior-maniphest-list-editor' => 'a9f88de2',
     'javelin-behavior-maniphest-subpriority-editor' => '71237763',
     'javelin-behavior-owners-path-editor' => '7a68dda3',
     'javelin-behavior-passphrase-credential-control' => '3cb0b2fc',
     'javelin-behavior-persona-login' => '9414ff18',
     'javelin-behavior-phabricator-active-nav' => 'e379b58e',
     'javelin-behavior-phabricator-autofocus' => '7319e029',
     'javelin-behavior-phabricator-busy-example' => '60479091',
     'javelin-behavior-phabricator-file-tree' => '88236f00',
     'javelin-behavior-phabricator-gesture' => '3ab51e2c',
     'javelin-behavior-phabricator-gesture-example' => '558829c2',
     'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
     'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6',
     'javelin-behavior-phabricator-line-linker' => '1499a8cb',
     'javelin-behavior-phabricator-nav' => '56a1ca03',
     'javelin-behavior-phabricator-notification-example' => '8ce821c5',
     'javelin-behavior-phabricator-object-selector' => '49b73b36',
     'javelin-behavior-phabricator-oncopy' => '2926fff2',
     'javelin-behavior-phabricator-remarkup-assist' => '340c8eff',
     'javelin-behavior-phabricator-reveal-content' => '60821bc7',
     'javelin-behavior-phabricator-search-typeahead' => '06c32383',
     'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
     'javelin-behavior-phabricator-tooltips' => '42fcb747',
     'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6',
     'javelin-behavior-phabricator-transaction-list' => '13c739ea',
     'javelin-behavior-phabricator-watch-anchor' => '9f36c42d',
     'javelin-behavior-pholio-mock-edit' => '246dc085',
     'javelin-behavior-pholio-mock-view' => 'fbe497e7',
     'javelin-behavior-phui-dropdown-menu' => '54733475',
     'javelin-behavior-phui-hovercards' => 'bcaccd64',
     'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
     'javelin-behavior-phui-profile-menu' => '12884df9',
     'javelin-behavior-policy-control' => 'd0c516d5',
     'javelin-behavior-policy-rule-editor' => '5e9f347c',
     'javelin-behavior-project-boards' => '14a1faae',
     'javelin-behavior-project-create' => '065227cc',
     'javelin-behavior-quicksand-blacklist' => '7927a7d3',
     'javelin-behavior-recurring-edit' => '5f1c4d5f',
     'javelin-behavior-refresh-csrf' => 'ab2f381b',
     'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf',
     'javelin-behavior-releeph-request-state-change' => 'a0b57eb8',
     'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
     'javelin-behavior-remarkup-preview' => '4b700e9e',
     'javelin-behavior-reorder-applications' => '76b9fc3e',
     'javelin-behavior-reorder-columns' => 'e1d25dfb',
     'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072',
     'javelin-behavior-repository-crossreference' => 'e5339c43',
     'javelin-behavior-scrollbar' => '834a1173',
     'javelin-behavior-search-reorder-queries' => 'e9581f08',
     'javelin-behavior-select-on-click' => '4e3e79a6',
     'javelin-behavior-slowvote-embed' => '887ad43f',
     'javelin-behavior-stripe-payment-form' => '3f5d6dbf',
     'javelin-behavior-test-payment-form' => 'fc91ab6c',
     'javelin-behavior-time-typeahead' => 'f80d6bf0',
     'javelin-behavior-toggle-class' => '5d7c9f33',
     'javelin-behavior-typeahead-browse' => '635de1ec',
     'javelin-behavior-typeahead-search' => '93d0c9e3',
     'javelin-behavior-view-placeholder' => '47830651',
     'javelin-behavior-workflow' => '0a3f3021',
     'javelin-color' => '7e41274a',
     'javelin-cookie' => '62dfea03',
     'javelin-diffusion-locate-file-source' => 'b42eddc7',
     'javelin-dom' => '805b806a',
     'javelin-dynval' => 'f6555212',
     'javelin-event' => '2ee659ce',
     'javelin-fx' => '54b612ba',
     'javelin-history' => 'd4505101',
     'javelin-install' => '05270951',
     'javelin-json' => '69adf288',
     'javelin-leader' => '331b1611',
     'javelin-magical-init' => '3010e992',
     'javelin-mask' => '8a41885b',
     'javelin-quicksand' => '6b8ef10b',
     'javelin-reactor' => '2b8de964',
     'javelin-reactor-dom' => 'c90a04fc',
     'javelin-reactor-node-calmer' => '76f4ebed',
     'javelin-reactornode' => '1ad0a787',
     'javelin-request' => '94b750d2',
     'javelin-resource' => '44959b73',
     'javelin-routable' => 'b3e7d692',
     'javelin-router' => '29274e2b',
     'javelin-scrollbar' => '087e919c',
     'javelin-sound' => '949c0fe5',
     'javelin-stratcom' => '6ad39b6f',
     'javelin-tokenizer' => '8d3bc1b2',
     'javelin-typeahead' => '70baed2f',
     'javelin-typeahead-composite-source' => '503e17fd',
     'javelin-typeahead-normalizer' => 'e6e25838',
     'javelin-typeahead-ondemand-source' => '013ffff9',
     'javelin-typeahead-preloaded-source' => '54f314a0',
     'javelin-typeahead-source' => '1bc11c4a',
     'javelin-typeahead-static-source' => '6c0e62fa',
     'javelin-uri' => 'c989ade3',
     'javelin-util' => '93cc50d6',
     'javelin-vector' => '2caa8fb8',
     'javelin-view' => '0f764c35',
     'javelin-view-html' => 'fe287620',
     'javelin-view-interpreter' => 'f829edb3',
     'javelin-view-renderer' => '6c2b09a2',
     'javelin-view-visitor' => 'efe49472',
     'javelin-websocket' => 'e292eaf4',
     'javelin-workboard-board' => '52291776',
     'javelin-workboard-card' => 'c587b80f',
     'javelin-workboard-column' => 'bae58312',
     'javelin-workboard-controller' => '55baf5ed',
     'javelin-workflow' => '28cfbdd0',
     'lightbox-attachment-css' => '7acac05d',
     'maniphest-batch-editor' => 'b0f0b6d5',
     'maniphest-report-css' => '9b9580b7',
     'maniphest-task-edit-css' => 'fda62a9b',
     'maniphest-task-summary-css' => '11cc5344',
     'multirow-row-manager' => 'b5d57730',
     'owners-path-editor' => 'aa1733d0',
     'owners-path-editor-css' => '2f00933b',
     'paste-css' => '1898e534',
     'path-typeahead' => 'f7fc67ec',
     'people-profile-css' => '2473d929',
     'phabricator-action-list-view-css' => 'c5eba19d',
     'phabricator-application-launch-view-css' => '95351601',
     'phabricator-busy' => '59a7976a',
     'phabricator-chatlog-css' => 'd295b020',
     'phabricator-content-source-view-css' => '4b8b05d4',
     'phabricator-core-css' => 'd0801452',
     'phabricator-countdown-css' => '96696f21',
-    'phabricator-dashboard-css' => 'eb458607',
+    'phabricator-dashboard-css' => 'bc6f2127',
     'phabricator-drag-and-drop-file-upload' => '81f182b5',
     'phabricator-draggable-list' => '5a13c79f',
     'phabricator-fatal-config-template-css' => '8e6c6fcd',
     'phabricator-feed-css' => 'ecd4ec57',
     'phabricator-file-upload' => '680ea2c8',
     'phabricator-filetree-view-css' => 'fccf9f82',
     'phabricator-flag-css' => '5337623f',
     'phabricator-keyboard-shortcut' => '1ae869f2',
     'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
     'phabricator-main-menu-view' => 'd00a795a',
     'phabricator-nav-view-css' => 'ac79a758',
     'phabricator-notification' => 'ccf1cbf8',
     'phabricator-notification-css' => '7f684b62',
     'phabricator-notification-menu-css' => 'f31c0bde',
     'phabricator-object-selector-css' => '85ee8ce6',
     'phabricator-phtize' => 'd254d646',
     'phabricator-prefab' => 'e67df814',
     'phabricator-remarkup-css' => '2c9ed46f',
     'phabricator-search-results-css' => '7dea472c',
     'phabricator-shaped-request' => '7cbe244b',
     'phabricator-side-menu-view-css' => '3a3d9f41',
     'phabricator-slowvote-css' => 'a94b7230',
     'phabricator-source-code-view-css' => 'cbeef983',
     'phabricator-standard-page-view' => 'e709f6d0',
     'phabricator-textareautils' => '5813016a',
     'phabricator-title' => 'df5e11d2',
     'phabricator-tooltip' => '6323f942',
     'phabricator-ui-example-css' => '528b19de',
     'phabricator-uiexample-javelin-view' => 'd4a14807',
     'phabricator-uiexample-reactor-button' => 'd19198c8',
     'phabricator-uiexample-reactor-checkbox' => '519705ea',
     'phabricator-uiexample-reactor-focus' => '40a6a403',
     'phabricator-uiexample-reactor-input' => '886fd850',
     'phabricator-uiexample-reactor-mouseover' => '47c794d8',
     'phabricator-uiexample-reactor-radio' => '988040b4',
     'phabricator-uiexample-reactor-select' => 'a155550f',
     'phabricator-uiexample-reactor-sendclass' => '1def2711',
     'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
     'phabricator-zindex-css' => '5b6fcf3f',
     'phame-css' => '737792d6',
     'pholio-css' => 'ca89d380',
     'pholio-edit-css' => '3ad9d1ee',
     'pholio-inline-comments-css' => '8e545e49',
     'phortune-credit-card-form' => '2290aeef',
     'phortune-credit-card-form-css' => '8391eb02',
     'phortune-css' => '9149f103',
     'phrequent-css' => 'ffc185ad',
     'phriction-document-css' => 'd1861e06',
     'phui-action-panel-css' => '91c7b835',
     'phui-badge-view-css' => 'f25c3476',
     'phui-big-info-view-css' => 'bd903741',
-    'phui-box-css' => 'b2d49bae',
+    'phui-box-css' => 'd909ea3d',
     'phui-button-css' => 'a64a8de6',
     'phui-calendar-css' => 'ccabe893',
     'phui-calendar-day-css' => 'd1cf6f93',
     'phui-calendar-list-css' => 'c1c7f338',
     'phui-calendar-month-css' => '476be7e0',
     'phui-chart-css' => '6bf6f78e',
-    'phui-crumbs-view-css' => '79d536e5',
+    'phui-crumbs-view-css' => '1a1265d4',
     'phui-curtain-view-css' => '7148ae25',
     'phui-document-summary-view-css' => '9ca48bdf',
     'phui-document-view-css' => '9c71d2bf',
-    'phui-document-view-pro-css' => '92d5b648',
+    'phui-document-view-pro-css' => '73e45fd2',
     'phui-feed-story-css' => '04aec08f',
     'phui-font-icon-base-css' => '6449bce8',
     'phui-fontkit-css' => '9cda225e',
     'phui-form-css' => 'aac1d51d',
     'phui-form-view-css' => '6a51768e',
     'phui-head-thing-view-css' => 'fd311e5f',
     'phui-header-view-css' => '230254d3',
     'phui-hovercard' => '1bd28176',
     'phui-hovercard-view-css' => 'de1a2119',
     'phui-icon-set-selector-css' => '1ab67aad',
     'phui-icon-view-css' => '3f33ab57',
     'phui-image-mask-css' => 'a8498f9c',
     'phui-info-panel-css' => '27ea50a1',
     'phui-info-view-css' => '28efab79',
     'phui-inline-comment-view-css' => '5953c28e',
     'phui-list-view-css' => '9da2aa00',
     'phui-object-box-css' => '6b487c57',
     'phui-object-item-list-view-css' => '8d99e42b',
     'phui-pager-css' => 'bea33d23',
     'phui-pinboard-view-css' => '2495140e',
     'phui-profile-menu-css' => '7e92a89a',
     'phui-property-list-view-css' => '1d42ee7c',
     'phui-remarkup-preview-css' => '1a8f2591',
     'phui-segment-bar-view-css' => '46342871',
     'phui-spacing-css' => '042804d6',
     'phui-status-list-view-css' => '37309046',
     'phui-tag-view-css' => '6bbd83e2',
     'phui-theme-css' => '027ba77e',
     'phui-timeline-view-css' => '6e342216',
-    'phui-two-column-view-css' => '691fec04',
+    'phui-two-column-view-css' => 'b9538af1',
     'phui-workboard-color-css' => 'ac6fe6a7',
     'phui-workboard-view-css' => 'e6d89647',
     'phui-workcard-view-css' => '3646fb96',
     'phui-workpanel-view-css' => '92197373',
     'phuix-action-list-view' => 'b5c256b8',
     'phuix-action-view' => '8cf6d262',
     'phuix-autocomplete' => '9196fb06',
     'phuix-dropdown-menu' => 'bd4c8dca',
-    'phuix-form-control-view' => 'a7763e11',
+    'phuix-form-control-view' => 'e15869a8',
     'phuix-icon-view' => 'bff6884b',
     'policy-css' => '957ea14c',
     'policy-edit-css' => '815c66f7',
     'policy-transaction-detail-css' => '82100a43',
     'ponder-view-css' => 'fbd45f96',
     'project-card-view-css' => '9418c97d',
     'project-view-css' => 'cbaa10a1',
     'releeph-core' => '9b3c5733',
     'releeph-preview-branch' => 'b7a6f4a5',
     'releeph-request-differential-create-dialog' => '8d8b92cd',
     'releeph-request-typeahead-css' => '667a48ae',
     'setup-issue-css' => 'db7e9c40',
     'sprite-login-css' => '60e8560e',
     'sprite-menu-css' => '9dd65b92',
     'sprite-tokens-css' => '4f399012',
     'syntax-highlighting-css' => '9fd11da8',
     'tokens-css' => '3d0f239e',
     'typeahead-browse-css' => 'd8581d2c',
     'unhandled-exception-css' => '4c96257a',
   ),
   'requires' => array(
     '013ffff9' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-typeahead-source',
     ),
     '01774ab2' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
       'javelin-aphlict',
       'javelin-workflow',
       'javelin-router',
       'javelin-behavior-device',
       'javelin-vector',
     ),
     '019f36c4' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
     ),
     '031cee25' => array(
       'javelin-behavior',
       'javelin-request',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-uri',
       'javelin-behavior-device',
       'phabricator-title',
     ),
     '05270951' => array(
       'javelin-util',
       'javelin-magical-init',
     ),
     '056da01b' => array(
       'aphront-typeahead-control-css',
       'phui-tag-view-css',
     ),
     '06460e71' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phuix-form-control-view',
       'phuix-icon-view',
       'javelin-behavior-phabricator-gesture',
     ),
     '065227cc' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
     ),
     '06c32383' => array(
       'javelin-behavior',
       'javelin-typeahead-ondemand-source',
       'javelin-typeahead',
       'javelin-dom',
       'javelin-uri',
       'javelin-util',
       'javelin-stratcom',
       'phabricator-prefab',
       'phuix-icon-view',
     ),
     '087e919c' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
     ),
     '0a3f3021' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'javelin-router',
     ),
     '0f764c35' => array(
       'javelin-install',
       'javelin-util',
     ),
     '12884df9' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '13c739ea' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'javelin-uri',
       'phabricator-textareautils',
     ),
     '1499a8cb' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-history',
     ),
     '14a1faae' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-workboard-controller',
     ),
     '1ad0a787' => array(
       'javelin-install',
       'javelin-reactor',
       'javelin-util',
       'javelin-reactor-node-calmer',
     ),
     '1ae869f2' => array(
       'javelin-install',
       'javelin-util',
       'phabricator-keyboard-shortcut-manager',
     ),
     '1bc11c4a' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-typeahead-normalizer',
     ),
     '1bd28176' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
       'javelin-request',
       'javelin-uri',
     ),
     '1d45c74d' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-behavior-device',
       'javelin-history',
       'javelin-vector',
       'javelin-scrollbar',
       'phabricator-title',
       'phabricator-shaped-request',
       'conpherence-thread-manager',
     ),
     '1def2711' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     '21ba5861' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-stratcom',
       'conpherence-thread-manager',
     ),
     '2290aeef' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-json',
       'javelin-workflow',
       'javelin-util',
     ),
     '246dc085' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-workflow',
       'javelin-quicksand',
       'phabricator-phtize',
       'phabricator-drag-and-drop-file-upload',
       'phabricator-draggable-list',
     ),
     '28cfbdd0' => array(
       'javelin-stratcom',
       'javelin-request',
       'javelin-dom',
       'javelin-vector',
       'javelin-install',
       'javelin-util',
       'javelin-mask',
       'javelin-uri',
       'javelin-routable',
     ),
     '2926fff2' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '29274e2b' => array(
       'javelin-install',
       'javelin-util',
     ),
     '2b8de964' => array(
       'javelin-install',
       'javelin-util',
     ),
     '2bfa2836' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '2c426492' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'phabricator-keyboard-shortcut',
     ),
     '2caa8fb8' => array(
       'javelin-install',
       'javelin-event',
     ),
     '2ee659ce' => array(
       'javelin-install',
     ),
     '327a00d1' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-workflow',
     ),
     '331b1611' => array(
       'javelin-install',
     ),
     '340c8eff' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phabricator-phtize',
       'phabricator-textareautils',
       'javelin-workflow',
       'javelin-vector',
       'phuix-autocomplete',
     ),
     '3ab51e2c' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-magical-init',
     ),
     '3cb0b2fc' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'javelin-uri',
     ),
     '3e3b0b76' => array(
       'phui-inline-comment-view-css',
     ),
     '3f5d6dbf' => array(
       'javelin-behavior',
       'javelin-dom',
       'phortune-credit-card-form',
     ),
     '40a6a403' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     42126667 => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-request',
     ),
     '42fcb747' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'phabricator-tooltip',
     ),
     '44959b73' => array(
       'javelin-util',
       'javelin-uri',
       'javelin-install',
     ),
     '453c5375' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '469c0d9e' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-workflow',
     ),
     47830651 => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-view-renderer',
       'javelin-install',
     ),
     '47c794d8' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     48086888 => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-workflow',
     ),
     '49b73b36' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-request',
       'javelin-util',
     ),
     '4b700e9e' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phabricator-shaped-request',
     ),
     '4e3e79a6' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '4f6a4b4e' => array(
       'javelin-behavior',
       'javelin-dom',
       'phabricator-drag-and-drop-file-upload',
       'phabricator-textareautils',
     ),
     '4fbbc3e9' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-util',
       'javelin-vector',
       'differential-inline-comment-editor',
     ),
     '4fdb476d' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '503e17fd' => array(
       'javelin-install',
       'javelin-typeahead-source',
       'javelin-util',
     ),
     '519705ea' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     52291776 => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
       'javelin-workboard-column',
     ),
     '5359e785' => array(
       'javelin-install',
       'javelin-util',
       'javelin-websocket',
       'javelin-leader',
       'javelin-json',
     ),
     54733475 => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phuix-dropdown-menu',
     ),
     '54b612ba' => array(
       'javelin-color',
       'javelin-install',
       'javelin-util',
     ),
     '54f314a0' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-typeahead-source',
     ),
     '558829c2' => array(
       'javelin-stratcom',
       'javelin-behavior',
       'javelin-vector',
       'javelin-dom',
     ),
     '55baf5ed' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-drag-and-drop-file-upload',
       'javelin-workboard-board',
     ),
     '56a1ca03' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-magical-init',
       'javelin-vector',
       'javelin-request',
       'javelin-util',
     ),
     '5813016a' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
     ),
     '59a7976a' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-fx',
     ),
     '59b251eb' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
     ),
     '5a0b1a64' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '5a13c79f' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
       'javelin-vector',
       'javelin-magical-init',
     ),
     '5c54cbf3' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '5d7c9f33' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '5e9f347c' => array(
       'javelin-behavior',
       'multirow-row-manager',
       'javelin-dom',
       'javelin-util',
       'phabricator-prefab',
       'javelin-json',
     ),
     60479091 => array(
       'phabricator-busy',
       'javelin-behavior',
     ),
     '60821bc7' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '61cbc29a' => array(
       'javelin-magical-init',
       'javelin-util',
     ),
     '62dfea03' => array(
       'javelin-install',
       'javelin-util',
     ),
     '6323f942' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-vector',
     ),
     '635de1ec' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
     ),
     '64a5550f' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
       'javelin-request',
       'javelin-workflow',
     ),
     '680ea2c8' => array(
       'javelin-install',
       'javelin-dom',
       'phabricator-notification',
     ),
     '6882e80a' => array(
       'javelin-dom',
     ),
     '69adf288' => array(
       'javelin-install',
     ),
     '6ad39b6f' => array(
       'javelin-install',
       'javelin-event',
       'javelin-util',
       'javelin-magical-init',
     ),
     '6b8ef10b' => array(
       'javelin-install',
     ),
     '6c0e62fa' => array(
       'javelin-install',
       'javelin-typeahead-source',
     ),
     '6c2b09a2' => array(
       'javelin-install',
       'javelin-util',
     ),
     '6d3e1947' => array(
       'javelin-behavior',
       'javelin-diffusion-locate-file-source',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-uri',
     ),
     '70baed2f' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
       'javelin-util',
     ),
     71237763 => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
     ),
     '7319e029' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '73d09eef' => array(
       'javelin-behavior',
       'javelin-vector',
       'javelin-dom',
     ),
     '76b9fc3e' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     '76f4ebed' => array(
       'javelin-install',
       'javelin-reactor',
       'javelin-util',
     ),
     '782ab6e7' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phabricator-prefab',
       'multirow-row-manager',
       'javelin-json',
     ),
     '7927a7d3' => array(
       'javelin-behavior',
       'javelin-quicksand',
     ),
     '7a68dda3' => array(
       'owners-path-editor',
       'javelin-behavior',
     ),
     '7b98d7c5' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
     ),
     '7cbe244b' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-router',
     ),
     '7e41274a' => array(
       'javelin-install',
     ),
     '7ebaeed3' => array(
       'herald-rule-editor',
       'javelin-behavior',
     ),
     '7ee2b591' => array(
       'javelin-behavior',
       'javelin-history',
     ),
     '805b806a' => array(
       'javelin-magical-init',
       'javelin-install',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
     ),
     '81f182b5' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-dom',
       'javelin-uri',
       'phabricator-file-upload',
     ),
     '834a1173' => array(
       'javelin-behavior',
       'javelin-scrollbar',
     ),
     '8499b6ab' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '85ee8ce6' => array(
       'aphront-dialog-view-css',
     ),
     '8694b1df' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'phabricator-tooltip',
       'changeset-view-manager',
     ),
     '88236f00' => array(
       'javelin-behavior',
       'phabricator-keyboard-shortcut',
       'javelin-stratcom',
     ),
     '886fd850' => array(
       'javelin-install',
       'javelin-reactor-dom',
       'javelin-view-html',
       'javelin-view-interpreter',
       'javelin-view-renderer',
     ),
     '887ad43f' => array(
       'javelin-behavior',
       'javelin-request',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '8a41885b' => array(
       'javelin-install',
       'javelin-dom',
     ),
     '8ae55229' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
     ),
     '8bdb2835' => array(
       'phui-fontkit-css',
     ),
     '8ce821c5' => array(
       'phabricator-notification',
       'javelin-stratcom',
       'javelin-behavior',
     ),
     '8cf6d262' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
     ),
     '8d3bc1b2' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
     ),
     '901935ef' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-request',
     ),
     '9196fb06' => array(
       'javelin-install',
       'javelin-dom',
       'phuix-icon-view',
       'phabricator-prefab',
     ),
     92197373 => array(
       'phui-workcard-view-css',
     ),
     '93d0c9e3' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
     ),
     '9414ff18' => array(
       'javelin-behavior',
       'javelin-resource',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
     ),
     '949c0fe5' => array(
       'javelin-install',
     ),
     '94b750d2' => array(
       'javelin-install',
       'javelin-stratcom',
       'javelin-util',
       'javelin-behavior',
       'javelin-json',
       'javelin-dom',
       'javelin-resource',
       'javelin-routable',
     ),
     '988040b4' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     '9a6b9324' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'phabricator-phtize',
       'changeset-view-manager',
     ),
     '9f36c42d' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
     ),
     'a0b57eb8' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'phabricator-keyboard-shortcut',
     ),
     'a155550f' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     'a2828756' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
       'javelin-workflow',
       'javelin-router',
       'javelin-behavior-device',
       'javelin-vector',
     ),
     'a464fe03' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-notification',
     ),
-    'a7763e11' => array(
-      'javelin-install',
-      'javelin-dom',
-    ),
     'a80d0378' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'a8458711' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'phabricator-notification',
       'javelin-behavior-device',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'conpherence-thread-manager',
     ),
     'a8d8459d' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     'a8da01f0' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-keyboard-shortcut',
     ),
     'a9f88de2' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-fx',
       'javelin-util',
     ),
     'aa1733d0' => array(
       'multirow-row-manager',
       'javelin-install',
       'path-typeahead',
       'javelin-dom',
       'javelin-util',
       'phabricator-prefab',
     ),
     'ab2f381b' => array(
       'javelin-request',
       'javelin-behavior',
       'javelin-dom',
       'javelin-router',
       'javelin-util',
       'phabricator-busy',
     ),
     'b064af76' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-request',
       'javelin-util',
       'phabricator-shaped-request',
     ),
     'b1f0ccee' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-reactor-dom',
     ),
     'b23b49e6' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-request',
       'phabricator-shaped-request',
     ),
     'b2b4fbaf' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-uri',
       'javelin-request',
     ),
     'b3a4b884' => array(
       'javelin-behavior',
       'phabricator-prefab',
     ),
     'b3e7d692' => array(
       'javelin-install',
     ),
     'b42eddc7' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-typeahead-preloaded-source',
       'javelin-util',
     ),
     'b59e1e96' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'b5b36110' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
       'javelin-install',
     ),
     'b5c256b8' => array(
       'javelin-install',
       'javelin-dom',
     ),
     'b5d57730' => array(
       'javelin-install',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-util',
     ),
     'b6993408' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-json',
       'phabricator-draggable-list',
     ),
     'bae58312' => array(
       'javelin-install',
       'javelin-workboard-card',
     ),
     'bcaccd64' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-vector',
       'phui-hovercard',
     ),
     'bd4c8dca' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-vector',
       'javelin-stratcom',
     ),
     'bdaf4d04' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-request',
     ),
     'bff6884b' => array(
       'javelin-install',
       'javelin-dom',
     ),
     'c1700f6f' => array(
       'javelin-install',
       'javelin-util',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
     ),
     'c587b80f' => array(
       'javelin-install',
     ),
     'c72aa091' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-behavior-device',
       'javelin-scrollbar',
       'javelin-quicksand',
       'phabricator-keyboard-shortcut',
       'conpherence-thread-manager',
     ),
     'c7ccd872' => array(
       'phui-fontkit-css',
     ),
     'c8e57404' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-uri',
       'javelin-mask',
       'phabricator-drag-and-drop-file-upload',
     ),
     'c90a04fc' => array(
       'javelin-dom',
       'javelin-dynval',
       'javelin-reactor',
       'javelin-reactornode',
       'javelin-install',
       'javelin-util',
     ),
     'c989ade3' => array(
       'javelin-install',
       'javelin-util',
       'javelin-stratcom',
     ),
     'ca3f91eb' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'phabricator-phtize',
     ),
     'ccf1cbf8' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
       'phabricator-notification-css',
     ),
     'cf86d16a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-workflow',
       'phabricator-drag-and-drop-file-upload',
     ),
     'd00a795a' => array(
       'phui-theme-css',
     ),
     'd0c516d5' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'javelin-workflow',
       'phuix-icon-view',
     ),
     'd19198c8' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
       'javelin-dynval',
       'javelin-reactor-dom',
     ),
     'd254d646' => array(
       'javelin-util',
     ),
     'd4505101' => array(
       'javelin-stratcom',
       'javelin-install',
       'javelin-uri',
       'javelin-util',
     ),
     'd4a14807' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-view',
     ),
     'd4eecc63' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     'd6a7e717' => array(
       'multirow-row-manager',
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-json',
       'phabricator-prefab',
     ),
     'd75709e6' => array(
       'javelin-behavior',
       'javelin-workflow',
       'javelin-json',
       'javelin-dom',
       'phabricator-keyboard-shortcut',
     ),
     'd7a74243' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'd835b03a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phabricator-shaped-request',
     ),
     'dbbf48b6' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phabricator-busy',
     ),
     'de2e896f' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-typeahead-ondemand-source',
       'javelin-dom',
     ),
     'df5e11d2' => array(
       'javelin-install',
     ),
     'e10f8e18' => array(
       'javelin-behavior',
       'javelin-dom',
       'phabricator-prefab',
     ),
+    'e15869a8' => array(
+      'javelin-install',
+      'javelin-dom',
+    ),
     'e1d25dfb' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'e1ff79b1' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'e292eaf4' => array(
       'javelin-install',
     ),
     'e2e0a072' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'e379b58e' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-uri',
     ),
     'e4232876' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-vector',
       'phui-chart-css',
     ),
     'e4cc26b3' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     'e5339c43' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-uri',
     ),
     'e5822781' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-json',
       'javelin-workflow',
       'javelin-magical-init',
     ),
     'e67df814' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-tokenizer',
       'javelin-typeahead-preloaded-source',
       'javelin-typeahead-ondemand-source',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
     ),
     'e6e25838' => array(
       'javelin-install',
     ),
     'e9581f08' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'ea681761' => array(
       'javelin-behavior',
       'javelin-aphlict',
       'phabricator-phtize',
       'javelin-dom',
     ),
     'edd1ba66' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-uri',
       'phabricator-notification',
     ),
     'edf8a145' => array(
       'javelin-behavior',
       'javelin-uri',
     ),
     'efe49472' => array(
       'javelin-install',
       'javelin-util',
     ),
     'f01586dc' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-json',
     ),
     'f411b6ae' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-util',
       'javelin-dom',
       'javelin-request',
       'phabricator-keyboard-shortcut',
     ),
     'f6555212' => array(
       'javelin-install',
       'javelin-reactornode',
       'javelin-util',
       'javelin-reactor',
     ),
     'f7fc67ec' => array(
       'javelin-install',
       'javelin-typeahead',
       'javelin-dom',
       'javelin-request',
       'javelin-typeahead-ondemand-source',
       'javelin-util',
     ),
     'f80d6bf0' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-typeahead-static-source',
     ),
     'f829edb3' => array(
       'javelin-view',
       'javelin-install',
       'javelin-dom',
     ),
     'f8ba29d7' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-mask',
       'javelin-util',
       'phabricator-busy',
     ),
     'fa0f4fc2' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-vector',
       'javelin-magical-init',
     ),
     'fb20ac8d' => array(
       'javelin-behavior',
       'javelin-aphlict',
       'javelin-stratcom',
       'javelin-request',
       'javelin-uri',
       'javelin-dom',
       'javelin-json',
       'javelin-router',
       'javelin-util',
       'javelin-leader',
       'javelin-sound',
       'phabricator-notification',
     ),
     'fbe497e7' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
       'javelin-magical-init',
       'javelin-request',
       'javelin-history',
       'javelin-workflow',
       'javelin-mask',
       'javelin-behavior-device',
       'phabricator-keyboard-shortcut',
     ),
     'fc91ab6c' => array(
       'javelin-behavior',
       'javelin-dom',
       'phortune-credit-card-form',
     ),
     'fe287620' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-view-visitor',
       'javelin-util',
     ),
   ),
   'packages' => array(
     'core.pkg.css' => array(
       'phabricator-core-css',
       'phabricator-zindex-css',
       'phui-button-css',
       'phabricator-standard-page-view',
       'aphront-dialog-view-css',
       'phui-form-view-css',
       'aphront-panel-view-css',
       'aphront-table-view-css',
       'aphront-tokenizer-control-css',
       'aphront-typeahead-control-css',
       'aphront-list-filter-view-css',
       'phabricator-remarkup-css',
       'syntax-highlighting-css',
       'phui-pager-css',
       'aphront-tooltip-css',
       'phabricator-flag-css',
       'phui-info-view-css',
       'sprite-menu-css',
       'phabricator-main-menu-view',
       'phabricator-notification-css',
       'phabricator-notification-menu-css',
       'lightbox-attachment-css',
       'phui-header-view-css',
       'phabricator-filetree-view-css',
       'phabricator-nav-view-css',
       'phabricator-side-menu-view-css',
       'phui-crumbs-view-css',
       'phui-object-item-list-view-css',
       'global-drag-and-drop-css',
       'phui-spacing-css',
       'phui-form-css',
       'phui-icon-view-css',
       'phui-profile-menu-css',
       'phabricator-application-launch-view-css',
       'phabricator-action-list-view-css',
       'phui-property-list-view-css',
       'phui-tag-view-css',
       'phui-list-view-css',
       'font-fontawesome',
       'phui-font-icon-base-css',
       'phui-box-css',
       'phui-object-box-css',
       'phui-timeline-view-css',
       'sprite-tokens-css',
       'tokens-css',
       'phui-status-list-view-css',
       'phui-feed-story-css',
       'phabricator-feed-css',
       'phabricator-dashboard-css',
       'aphront-multi-column-view-css',
       'conpherence-durable-column-view',
     ),
     'core.pkg.js' => array(
       'javelin-util',
       'javelin-install',
       'javelin-event',
       'javelin-stratcom',
       'javelin-behavior',
       'javelin-resource',
       'javelin-request',
       'javelin-vector',
       'javelin-dom',
       'javelin-json',
       'javelin-uri',
       'javelin-workflow',
       'javelin-mask',
       'javelin-typeahead',
       'javelin-typeahead-normalizer',
       'javelin-typeahead-source',
       'javelin-typeahead-preloaded-source',
       'javelin-typeahead-ondemand-source',
       'javelin-tokenizer',
       'javelin-history',
       'javelin-router',
       'javelin-routable',
       'javelin-behavior-aphront-basic-tokenizer',
       'javelin-behavior-workflow',
       'javelin-behavior-aphront-form-disable-on-submit',
       'phabricator-keyboard-shortcut-manager',
       'phabricator-keyboard-shortcut',
       'javelin-behavior-phabricator-keyboard-shortcuts',
       'javelin-behavior-refresh-csrf',
       'javelin-behavior-phabricator-watch-anchor',
       'javelin-behavior-phabricator-autofocus',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'phabricator-phtize',
       'javelin-behavior-phabricator-oncopy',
       'phabricator-tooltip',
       'javelin-behavior-phabricator-tooltips',
       'phabricator-prefab',
       'javelin-behavior-device',
       'javelin-behavior-toggle-class',
       'javelin-behavior-lightbox-attachments',
       'phabricator-busy',
       'javelin-aphlict',
       'phabricator-notification',
       'javelin-behavior-aphlict-listen',
       'javelin-behavior-phabricator-search-typeahead',
       'javelin-behavior-aphlict-dropdown',
       'javelin-behavior-history-install',
       'javelin-behavior-phabricator-gesture',
       'javelin-behavior-phabricator-active-nav',
       'javelin-behavior-phabricator-nav',
       'javelin-behavior-phabricator-remarkup-assist',
       'phabricator-textareautils',
       'phabricator-file-upload',
       'javelin-behavior-global-drag-and-drop',
       'javelin-behavior-phabricator-reveal-content',
       'phui-hovercard',
       'javelin-behavior-phui-hovercards',
       'javelin-color',
       'javelin-fx',
       'phabricator-draggable-list',
       'javelin-behavior-phabricator-transaction-list',
       'javelin-behavior-phabricator-show-older-transactions',
       'javelin-behavior-phui-dropdown-menu',
       'javelin-behavior-doorkeeper-tag',
       'phabricator-title',
       'javelin-leader',
       'javelin-websocket',
       'javelin-behavior-dashboard-async-panel',
       'javelin-behavior-dashboard-tab-panel',
       'javelin-quicksand',
       'javelin-behavior-quicksand-blacklist',
       'javelin-behavior-high-security-warning',
       'javelin-scrollbar',
       'javelin-behavior-scrollbar',
       'javelin-behavior-durable-column',
       'conpherence-thread-manager',
     ),
     'darkconsole.pkg.js' => array(
       'javelin-behavior-dark-console',
       'javelin-behavior-error-log',
     ),
     'differential.pkg.css' => array(
       'differential-core-view-css',
       'differential-changeset-view-css',
       'differential-revision-history-css',
       'differential-revision-list-css',
       'differential-table-of-contents-css',
       'differential-revision-comment-css',
       'differential-revision-add-comment-css',
       'phabricator-object-selector-css',
       'phabricator-content-source-view-css',
       'inline-comment-summary-css',
       'phui-inline-comment-view-css',
     ),
     'differential.pkg.js' => array(
       'phabricator-drag-and-drop-file-upload',
       'phabricator-shaped-request',
       'javelin-behavior-differential-feedback-preview',
       'javelin-behavior-differential-edit-inline-comments',
       'javelin-behavior-differential-populate',
       'javelin-behavior-differential-diff-radios',
       'javelin-behavior-differential-comment-jump',
       'javelin-behavior-differential-add-reviewers-and-ccs',
       'javelin-behavior-differential-keyboard-navigation',
       'javelin-behavior-aphront-drag-and-drop-textarea',
       'javelin-behavior-phabricator-object-selector',
       'javelin-behavior-repository-crossreference',
       'javelin-behavior-load-blame',
       'differential-inline-comment-editor',
       'javelin-behavior-differential-dropdown-menus',
       'javelin-behavior-differential-toggle-files',
       'javelin-behavior-differential-user-select',
       'javelin-behavior-aphront-more',
       'changeset-view-manager',
     ),
     'diffusion.pkg.css' => array(
       'diffusion-icons-css',
     ),
     'diffusion.pkg.js' => array(
       'javelin-behavior-diffusion-pull-lastmodified',
       'javelin-behavior-diffusion-commit-graph',
       'javelin-behavior-audit-preview',
     ),
     'maniphest.pkg.css' => array(
       'maniphest-task-summary-css',
     ),
     'maniphest.pkg.js' => array(
       'javelin-behavior-maniphest-batch-selector',
       'javelin-behavior-maniphest-subpriority-editor',
       'javelin-behavior-maniphest-list-editor',
     ),
   ),
 );
diff --git a/resources/sql/autopatches/20160404.oauth.1.xaction.sql b/resources/sql/autopatches/20160404.oauth.1.xaction.sql
new file mode 100644
index 000000000..70b7065ea
--- /dev/null
+++ b/resources/sql/autopatches/20160404.oauth.1.xaction.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_oauth_server.oauth_server_transaction (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARBINARY(64) NOT NULL,
+  authorPHID VARBINARY(64) NOT NULL,
+  objectPHID VARBINARY(64) NOT NULL,
+  viewPolicy VARBINARY(64) NOT NULL,
+  editPolicy VARBINARY(64) NOT NULL,
+  commentPHID VARBINARY(64) DEFAULT NULL,
+  commentVersion INT UNSIGNED NOT NULL,
+  transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+  oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  UNIQUE KEY `key_phid` (`phid`),
+  KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160405.oauth.2.disable.sql b/resources/sql/autopatches/20160405.oauth.2.disable.sql
new file mode 100644
index 000000000..fd26ce8a6
--- /dev/null
+++ b/resources/sql/autopatches/20160405.oauth.2.disable.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_oauth_server.oauth_server_oauthserverclient
+  ADD isDisabled BOOL NOT NULL;
diff --git a/resources/sql/autopatches/20160406.badges.ngrams.php b/resources/sql/autopatches/20160406.badges.ngrams.php
new file mode 100644
index 000000000..ce8d8896e
--- /dev/null
+++ b/resources/sql/autopatches/20160406.badges.ngrams.php
@@ -0,0 +1,11 @@
+<?php
+
+$table = new PhabricatorBadgesBadge();
+
+foreach (new LiskMigrationIterator($table) as $badge) {
+  PhabricatorSearchWorker::queueDocumentForIndexing(
+    $badge->getPHID(),
+    array(
+      'force' => true,
+    ));
+}
diff --git a/resources/sql/autopatches/20160406.badges.ngrams.sql b/resources/sql/autopatches/20160406.badges.ngrams.sql
new file mode 100644
index 000000000..14a03759c
--- /dev/null
+++ b/resources/sql/autopatches/20160406.badges.ngrams.sql
@@ -0,0 +1,7 @@
+CREATE TABLE {$NAMESPACE}_badges.badges_badgename_ngrams (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  objectID INT UNSIGNED NOT NULL,
+  ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
+  KEY `key_object` (objectID),
+  KEY `key_ngram` (ngram, objectID)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160406.columns.1.php b/resources/sql/autopatches/20160406.columns.1.php
new file mode 100644
index 000000000..8be75ebbe
--- /dev/null
+++ b/resources/sql/autopatches/20160406.columns.1.php
@@ -0,0 +1,84 @@
+<?php
+
+$table = new ManiphestTransaction();
+$conn_w = $table->establishConnection('w');
+
+foreach (new LiskMigrationIterator($table) as $xaction) {
+  $type = $xaction->getTransactionType();
+  $id = $xaction->getID();
+
+  // This is an old ManiphestTransaction::TYPE_COLUMN. It did not do anything
+  // on its own and was hidden from the UI, so we're just going to remove it.
+  if ($type == 'column') {
+    queryfx(
+      $conn_w,
+      'DELETE FROM %T WHERE id = %d',
+      $table->getTableName(),
+      $id);
+    continue;
+  }
+
+  // This is an old ManiphestTransaction::TYPE_PROJECT_COLUMN. It moved
+  // tasks between board columns; we're going to replace it with a modern
+  // PhabricatorTransactions::TYPE_COLUMNS transaction.
+  if ($type == 'projectcolumn') {
+    try {
+      $new = $xaction->getNewValue();
+      if (!$new || !is_array($new)) {
+        continue;
+      }
+
+      $column_phids = idx($new, 'columnPHIDs');
+      if (!is_array($column_phids) || !$column_phids) {
+        continue;
+      }
+
+      $column_phid = head($column_phids);
+      if (!$column_phid) {
+        continue;
+      }
+
+      $board_phid = idx($new, 'projectPHID');
+      if (!$board_phid) {
+        continue;
+      }
+
+      $before_phid = idx($new, 'beforePHID');
+      $after_phid = idx($new, 'afterPHID');
+
+      $old = $xaction->getOldValue();
+      if ($old && is_array($old)) {
+        $from_phids = idx($old, 'columnPHIDs');
+        $from_phids = array_values($from_phids);
+      } else {
+        $from_phids = array();
+      }
+
+      $replacement = array(
+        'columnPHID' => $column_phid,
+        'boardPHID' => $board_phid,
+        'fromColumnPHIDs' => $from_phids,
+      );
+
+      if ($before_phid) {
+        $replacement['beforePHID'] = $before_phid;
+      } else if ($after_phid) {
+        $replacement['afterPHID'] = $after_phid;
+      }
+
+      queryfx(
+        $conn_w,
+        'UPDATE %T SET transactionType = %s, oldValue = %s, newValue = %s
+          WHERE id = %d',
+        $table->getTableName(),
+        PhabricatorTransactions::TYPE_COLUMNS,
+        'null',
+        phutil_json_encode(array($replacement)),
+        $id);
+    } catch (Exception $ex) {
+      // If anything went awry, just move on.
+    }
+  }
+
+
+}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index c4bc803f5..596901a36 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,8853 +1,8882 @@
 <?php
 
 /**
  * This file is automatically generated. Use 'arc liberate' to rebuild it.
  *
  * @generated
  * @phutil-library-version 2
  */
 phutil_register_library_map(array(
   '__library_version__' => 2,
   'class' => array(
     'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
     'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php',
     'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php',
     'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
     'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
     'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
     'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php',
     'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php',
     'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php',
     'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
     'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php',
     'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php',
     'AlmanacBindingsSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacBindingsSearchEngineAttachment.php',
     'AlmanacClusterDatabaseServiceType' => 'applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php',
     'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php',
     'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php',
     'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
     'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
     'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
     'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php',
     'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
     'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
     'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php',
     'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
     'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
     'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
     'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php',
     'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php',
     'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
     'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
     'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php',
     'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
     'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php',
     'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
     'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php',
     'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
     'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
     'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
     'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
     'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php',
     'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
     'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
     'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
     'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php',
     'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
     'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
     'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
     'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php',
     'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php',
     'AlmanacManageClusterServicesCapability' => 'applications/almanac/capability/AlmanacManageClusterServicesCapability.php',
     'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php',
     'AlmanacManagementTrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php',
     'AlmanacManagementUntrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementUntrustKeyWorkflow.php',
     'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php',
     'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php',
     'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php',
     'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php',
     'AlmanacNamespaceController' => 'applications/almanac/controller/AlmanacNamespaceController.php',
     'AlmanacNamespaceEditController' => 'applications/almanac/controller/AlmanacNamespaceEditController.php',
     'AlmanacNamespaceEditEngine' => 'applications/almanac/editor/AlmanacNamespaceEditEngine.php',
     'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php',
     'AlmanacNamespaceListController' => 'applications/almanac/controller/AlmanacNamespaceListController.php',
     'AlmanacNamespaceNameNgrams' => 'applications/almanac/storage/AlmanacNamespaceNameNgrams.php',
     'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php',
     'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php',
     'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php',
     'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php',
     'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php',
     'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php',
     'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php',
     'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php',
     'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php',
     'AlmanacNetworkEditEngine' => 'applications/almanac/editor/AlmanacNetworkEditEngine.php',
     'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php',
     'AlmanacNetworkListController' => 'applications/almanac/controller/AlmanacNetworkListController.php',
     'AlmanacNetworkNameNgrams' => 'applications/almanac/storage/AlmanacNetworkNameNgrams.php',
     'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php',
     'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php',
     'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php',
     'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php',
     'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php',
     'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php',
     'AlmanacPropertiesDestructionEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php',
     'AlmanacPropertiesSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacPropertiesSearchEngineAttachment.php',
     'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php',
     'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php',
     'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php',
     'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php',
     'AlmanacPropertyEditEngine' => 'applications/almanac/editor/AlmanacPropertyEditEngine.php',
     'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php',
     'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php',
     'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php',
     'AlmanacSchemaSpec' => 'applications/almanac/storage/AlmanacSchemaSpec.php',
     'AlmanacSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacSearchEngineAttachment.php',
     'AlmanacService' => 'applications/almanac/storage/AlmanacService.php',
     'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
     'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php',
     'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
     'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php',
     'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php',
     'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php',
     'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php',
     'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php',
     'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php',
     'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php',
     'AlmanacServiceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacServiceSearchConduitAPIMethod.php',
     'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
     'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
     'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php',
     'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php',
     'AlmanacServiceTypeDatasource' => 'applications/almanac/typeahead/AlmanacServiceTypeDatasource.php',
     'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php',
     'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
     'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php',
     'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php',
     'Aphront304Response' => 'aphront/response/Aphront304Response.php',
     'Aphront400Response' => 'aphront/response/Aphront400Response.php',
     'Aphront403Response' => 'aphront/response/Aphront403Response.php',
     'Aphront404Response' => 'aphront/response/Aphront404Response.php',
     'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
     'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
     'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
     'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php',
     'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php',
     'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
     'AphrontController' => 'aphront/AphrontController.php',
     'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php',
     'AphrontDefaultApplicationConfiguration' => 'aphront/configuration/AphrontDefaultApplicationConfiguration.php',
     'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php',
     'AphrontDialogView' => 'view/AphrontDialogView.php',
+    'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php',
     'AphrontException' => 'aphront/exception/AphrontException.php',
     'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
     'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
     'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
     'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
     'AphrontFormDateControlValue' => 'view/form/control/AphrontFormDateControlValue.php',
     'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
     'AphrontFormFileControl' => 'view/form/control/AphrontFormFileControl.php',
     'AphrontFormHandlesControl' => 'view/form/control/AphrontFormHandlesControl.php',
     'AphrontFormMarkupControl' => 'view/form/control/AphrontFormMarkupControl.php',
     'AphrontFormPasswordControl' => 'view/form/control/AphrontFormPasswordControl.php',
     'AphrontFormPolicyControl' => 'view/form/control/AphrontFormPolicyControl.php',
     'AphrontFormRadioButtonControl' => 'view/form/control/AphrontFormRadioButtonControl.php',
     'AphrontFormRecaptchaControl' => 'view/form/control/AphrontFormRecaptchaControl.php',
     'AphrontFormSelectControl' => 'view/form/control/AphrontFormSelectControl.php',
     'AphrontFormStaticControl' => 'view/form/control/AphrontFormStaticControl.php',
     'AphrontFormSubmitControl' => 'view/form/control/AphrontFormSubmitControl.php',
     'AphrontFormTextAreaControl' => 'view/form/control/AphrontFormTextAreaControl.php',
     'AphrontFormTextControl' => 'view/form/control/AphrontFormTextControl.php',
     'AphrontFormTextWithSubmitControl' => 'view/form/control/AphrontFormTextWithSubmitControl.php',
     'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php',
     'AphrontFormTypeaheadControl' => 'view/form/control/AphrontFormTypeaheadControl.php',
     'AphrontFormView' => 'view/form/AphrontFormView.php',
     'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php',
     'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php',
     'AphrontHTTPParameterType' => 'aphront/httpparametertype/AphrontHTTPParameterType.php',
     'AphrontHTTPProxyResponse' => 'aphront/response/AphrontHTTPProxyResponse.php',
     'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php',
     'AphrontHTTPSinkTestCase' => 'aphront/sink/__tests__/AphrontHTTPSinkTestCase.php',
     'AphrontIntHTTPParameterType' => 'aphront/httpparametertype/AphrontIntHTTPParameterType.php',
     'AphrontIsolatedDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php',
     'AphrontIsolatedHTTPSink' => 'aphront/sink/AphrontIsolatedHTTPSink.php',
     'AphrontJSONResponse' => 'aphront/response/AphrontJSONResponse.php',
     'AphrontJavelinView' => 'view/AphrontJavelinView.php',
     'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php',
     'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php',
     'AphrontListHTTPParameterType' => 'aphront/httpparametertype/AphrontListHTTPParameterType.php',
     'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php',
     'AphrontMoreView' => 'view/layout/AphrontMoreView.php',
     'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php',
     'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php',
     'AphrontNullView' => 'view/AphrontNullView.php',
     'AphrontPHIDHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDHTTPParameterType.php',
     'AphrontPHIDListHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDListHTTPParameterType.php',
     'AphrontPHPHTTPSink' => 'aphront/sink/AphrontPHPHTTPSink.php',
     'AphrontPageView' => 'view/page/AphrontPageView.php',
     'AphrontPlainTextResponse' => 'aphront/response/AphrontPlainTextResponse.php',
     'AphrontProgressBarView' => 'view/widget/bars/AphrontProgressBarView.php',
     'AphrontProjectListHTTPParameterType' => 'aphront/httpparametertype/AphrontProjectListHTTPParameterType.php',
     'AphrontProxyResponse' => 'aphront/response/AphrontProxyResponse.php',
     'AphrontRedirectResponse' => 'aphront/response/AphrontRedirectResponse.php',
     'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php',
     'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
     'AphrontRequest' => 'aphront/AphrontRequest.php',
     'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php',
     'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php',
     'AphrontResponse' => 'aphront/response/AphrontResponse.php',
     'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php',
     'AphrontRoutingMap' => 'aphront/site/AphrontRoutingMap.php',
     'AphrontRoutingResult' => 'aphront/site/AphrontRoutingResult.php',
     'AphrontSelectHTTPParameterType' => 'aphront/httpparametertype/AphrontSelectHTTPParameterType.php',
     'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php',
     'AphrontSite' => 'aphront/site/AphrontSite.php',
     'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php',
     'AphrontStandaloneHTMLResponse' => 'aphront/response/AphrontStandaloneHTMLResponse.php',
     'AphrontStringHTTPParameterType' => 'aphront/httpparametertype/AphrontStringHTTPParameterType.php',
     'AphrontStringListHTTPParameterType' => 'aphront/httpparametertype/AphrontStringListHTTPParameterType.php',
     'AphrontTableView' => 'view/control/AphrontTableView.php',
     'AphrontTagView' => 'view/AphrontTagView.php',
     'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php',
     'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php',
     'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php',
     'AphrontUserListHTTPParameterType' => 'aphront/httpparametertype/AphrontUserListHTTPParameterType.php',
     'AphrontView' => 'view/AphrontView.php',
     'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
     'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php',
     'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php',
     'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php',
     'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php',
     'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php',
     'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php',
     'CelerityAPI' => 'applications/celerity/CelerityAPI.php',
     'CelerityDefaultPostprocessor' => 'applications/celerity/postprocessor/CelerityDefaultPostprocessor.php',
     'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php',
     'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php',
     'CelerityManagementMapWorkflow' => 'applications/celerity/management/CelerityManagementMapWorkflow.php',
     'CelerityManagementWorkflow' => 'applications/celerity/management/CelerityManagementWorkflow.php',
     'CelerityPhabricatorResourceController' => 'applications/celerity/controller/CelerityPhabricatorResourceController.php',
     'CelerityPhabricatorResources' => 'applications/celerity/resources/CelerityPhabricatorResources.php',
     'CelerityPhysicalResources' => 'applications/celerity/resources/CelerityPhysicalResources.php',
     'CelerityPhysicalResourcesTestCase' => 'applications/celerity/resources/__tests__/CelerityPhysicalResourcesTestCase.php',
     'CelerityPostprocessor' => 'applications/celerity/postprocessor/CelerityPostprocessor.php',
     'CelerityPostprocessorTestCase' => 'applications/celerity/__tests__/CelerityPostprocessorTestCase.php',
     'CelerityResourceController' => 'applications/celerity/controller/CelerityResourceController.php',
     'CelerityResourceGraph' => 'applications/celerity/CelerityResourceGraph.php',
     'CelerityResourceMap' => 'applications/celerity/CelerityResourceMap.php',
     'CelerityResourceMapGenerator' => 'applications/celerity/CelerityResourceMapGenerator.php',
     'CelerityResourceTransformer' => 'applications/celerity/CelerityResourceTransformer.php',
     'CelerityResourceTransformerTestCase' => 'applications/celerity/__tests__/CelerityResourceTransformerTestCase.php',
     'CelerityResources' => 'applications/celerity/resources/CelerityResources.php',
     'CelerityResourcesOnDisk' => 'applications/celerity/resources/CelerityResourcesOnDisk.php',
     'CeleritySpriteGenerator' => 'applications/celerity/CeleritySpriteGenerator.php',
     'CelerityStaticResourceResponse' => 'applications/celerity/CelerityStaticResourceResponse.php',
     'ChatLogConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogConduitAPIMethod.php',
     'ChatLogQueryConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogQueryConduitAPIMethod.php',
     'ChatLogRecordConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogRecordConduitAPIMethod.php',
     'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php',
     'ConduitAPIMethodTestCase' => 'applications/conduit/method/__tests__/ConduitAPIMethodTestCase.php',
     'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php',
     'ConduitAPIResponse' => 'applications/conduit/protocol/ConduitAPIResponse.php',
     'ConduitApplicationNotInstalledException' => 'applications/conduit/protocol/exception/ConduitApplicationNotInstalledException.php',
     'ConduitBoolParameterType' => 'applications/conduit/parametertype/ConduitBoolParameterType.php',
     'ConduitCall' => 'applications/conduit/call/ConduitCall.php',
     'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php',
+    'ConduitColumnsParameterType' => 'applications/conduit/parametertype/ConduitColumnsParameterType.php',
     'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php',
     'ConduitEpochParameterType' => 'applications/conduit/parametertype/ConduitEpochParameterType.php',
     'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php',
     'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php',
     'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php',
     'ConduitIntListParameterType' => 'applications/conduit/parametertype/ConduitIntListParameterType.php',
     'ConduitIntParameterType' => 'applications/conduit/parametertype/ConduitIntParameterType.php',
     'ConduitListParameterType' => 'applications/conduit/parametertype/ConduitListParameterType.php',
     'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php',
     'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php',
     'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php',
     'ConduitPHIDListParameterType' => 'applications/conduit/parametertype/ConduitPHIDListParameterType.php',
     'ConduitPHIDParameterType' => 'applications/conduit/parametertype/ConduitPHIDParameterType.php',
     'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php',
     'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php',
     'ConduitPointsParameterType' => 'applications/conduit/parametertype/ConduitPointsParameterType.php',
     'ConduitProjectListParameterType' => 'applications/conduit/parametertype/ConduitProjectListParameterType.php',
     'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php',
     'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php',
     'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php',
     'ConduitStringListParameterType' => 'applications/conduit/parametertype/ConduitStringListParameterType.php',
     'ConduitStringParameterType' => 'applications/conduit/parametertype/ConduitStringParameterType.php',
     'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php',
     'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php',
     'ConduitUserParameterType' => 'applications/conduit/parametertype/ConduitUserParameterType.php',
     'ConduitWildParameterType' => 'applications/conduit/parametertype/ConduitWildParameterType.php',
     'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php',
     'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php',
     'ConpherenceConfigOptions' => 'applications/conpherence/config/ConpherenceConfigOptions.php',
     'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php',
     'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
     'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php',
     'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
     'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php',
     'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
     'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php',
     'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php',
     'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php',
     'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php',
     'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php',
     'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
     'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
     'ConpherenceNewRoomController' => 'applications/conpherence/controller/ConpherenceNewRoomController.php',
     'ConpherenceNotificationPanelController' => 'applications/conpherence/controller/ConpherenceNotificationPanelController.php',
     'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
     'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php',
     'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
     'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
     'ConpherencePeopleWidgetView' => 'applications/conpherence/view/ConpherencePeopleWidgetView.php',
     'ConpherencePicCropControl' => 'applications/conpherence/view/ConpherencePicCropControl.php',
     'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php',
     'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php',
     'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
     'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php',
     'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php',
     'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php',
     'ConpherenceSettings' => 'applications/conpherence/constants/ConpherenceSettings.php',
     'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php',
     'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php',
     'ConpherenceThreadIndexEngineExtension' => 'applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php',
     'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php',
     'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php',
     'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php',
     'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php',
     'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php',
     'ConpherenceThreadSearchEngine' => 'applications/conpherence/query/ConpherenceThreadSearchEngine.php',
     'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php',
     'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php',
     'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php',
     'ConpherenceTransactionRenderer' => 'applications/conpherence/ConpherenceTransactionRenderer.php',
     'ConpherenceTransactionView' => 'applications/conpherence/view/ConpherenceTransactionView.php',
     'ConpherenceUpdateActions' => 'applications/conpherence/constants/ConpherenceUpdateActions.php',
     'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php',
     'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php',
     'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php',
     'ConpherenceWidgetConfigConstants' => 'applications/conpherence/constants/ConpherenceWidgetConfigConstants.php',
     'ConpherenceWidgetController' => 'applications/conpherence/controller/ConpherenceWidgetController.php',
     'ConpherenceWidgetView' => 'applications/conpherence/view/ConpherenceWidgetView.php',
     'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php',
     'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php',
     'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php',
     'DarkConsoleErrorLogPlugin' => 'applications/console/plugin/DarkConsoleErrorLogPlugin.php',
     'DarkConsoleErrorLogPluginAPI' => 'applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php',
     'DarkConsoleEventPlugin' => 'applications/console/plugin/DarkConsoleEventPlugin.php',
     'DarkConsoleEventPluginAPI' => 'applications/console/plugin/event/DarkConsoleEventPluginAPI.php',
     'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php',
     'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php',
     'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php',
     'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php',
     'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php',
     'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php',
     'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php',
     'DefaultDatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DefaultDatabaseConfigurationProvider.php',
     'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php',
     'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
     'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php',
     'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php',
     'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
     'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
     'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php',
     'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php',
     'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php',
     'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php',
     'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php',
     'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
     'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
     'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
     'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php',
     'DifferentialChangesSinceLastUpdateField' => 'applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php',
     'DifferentialChangeset' => 'applications/differential/storage/DifferentialChangeset.php',
     'DifferentialChangesetDetailView' => 'applications/differential/view/DifferentialChangesetDetailView.php',
     'DifferentialChangesetFileTreeSideNavBuilder' => 'applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php',
     'DifferentialChangesetHTMLRenderer' => 'applications/differential/render/DifferentialChangesetHTMLRenderer.php',
     'DifferentialChangesetListView' => 'applications/differential/view/DifferentialChangesetListView.php',
     'DifferentialChangesetOneUpRenderer' => 'applications/differential/render/DifferentialChangesetOneUpRenderer.php',
     'DifferentialChangesetOneUpTestRenderer' => 'applications/differential/render/DifferentialChangesetOneUpTestRenderer.php',
     'DifferentialChangesetParser' => 'applications/differential/parser/DifferentialChangesetParser.php',
     'DifferentialChangesetParserTestCase' => 'applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php',
     'DifferentialChangesetQuery' => 'applications/differential/query/DifferentialChangesetQuery.php',
     'DifferentialChangesetRenderer' => 'applications/differential/render/DifferentialChangesetRenderer.php',
     'DifferentialChangesetTestRenderer' => 'applications/differential/render/DifferentialChangesetTestRenderer.php',
     'DifferentialChangesetTwoUpRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpRenderer.php',
     'DifferentialChangesetTwoUpTestRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php',
     'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php',
     'DifferentialCloseConduitAPIMethod' => 'applications/differential/conduit/DifferentialCloseConduitAPIMethod.php',
     'DifferentialCommentPreviewController' => 'applications/differential/controller/DifferentialCommentPreviewController.php',
     'DifferentialCommentSaveController' => 'applications/differential/controller/DifferentialCommentSaveController.php',
     'DifferentialCommitMessageParser' => 'applications/differential/parser/DifferentialCommitMessageParser.php',
     'DifferentialCommitMessageParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCommitMessageParserTestCase.php',
     'DifferentialCommitsField' => 'applications/differential/customfield/DifferentialCommitsField.php',
     'DifferentialConduitAPIMethod' => 'applications/differential/conduit/DifferentialConduitAPIMethod.php',
     'DifferentialConflictsField' => 'applications/differential/customfield/DifferentialConflictsField.php',
     'DifferentialController' => 'applications/differential/controller/DifferentialController.php',
     'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php',
     'DifferentialCreateCommentConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php',
     'DifferentialCreateDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php',
     'DifferentialCreateInlineConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateInlineConduitAPIMethod.php',
     'DifferentialCreateMailReceiver' => 'applications/differential/mail/DifferentialCreateMailReceiver.php',
     'DifferentialCreateRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php',
     'DifferentialCreateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php',
     'DifferentialCustomField' => 'applications/differential/customfield/DifferentialCustomField.php',
     'DifferentialCustomFieldDependsOnParser' => 'applications/differential/parser/DifferentialCustomFieldDependsOnParser.php',
     'DifferentialCustomFieldDependsOnParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php',
     'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php',
     'DifferentialCustomFieldRevertsParser' => 'applications/differential/parser/DifferentialCustomFieldRevertsParser.php',
     'DifferentialCustomFieldRevertsParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldRevertsParserTestCase.php',
     'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php',
     'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php',
     'DifferentialDAO' => 'applications/differential/storage/DifferentialDAO.php',
     'DifferentialDefaultViewCapability' => 'applications/differential/capability/DifferentialDefaultViewCapability.php',
     'DifferentialDependenciesField' => 'applications/differential/customfield/DifferentialDependenciesField.php',
     'DifferentialDependsOnField' => 'applications/differential/customfield/DifferentialDependsOnField.php',
     'DifferentialDiff' => 'applications/differential/storage/DifferentialDiff.php',
     'DifferentialDiffAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialDiffAffectedFilesHeraldField.php',
     'DifferentialDiffAuthorHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorHeraldField.php',
     'DifferentialDiffAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php',
     'DifferentialDiffContentAddedHeraldField' => 'applications/differential/herald/DifferentialDiffContentAddedHeraldField.php',
     'DifferentialDiffContentHeraldField' => 'applications/differential/herald/DifferentialDiffContentHeraldField.php',
     'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php',
     'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php',
     'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php',
     'DifferentialDiffExtractionEngine' => 'applications/differential/engine/DifferentialDiffExtractionEngine.php',
     'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php',
     'DifferentialDiffHeraldFieldGroup' => 'applications/differential/herald/DifferentialDiffHeraldFieldGroup.php',
     'DifferentialDiffInlineCommentQuery' => 'applications/differential/query/DifferentialDiffInlineCommentQuery.php',
     'DifferentialDiffPHIDType' => 'applications/differential/phid/DifferentialDiffPHIDType.php',
     'DifferentialDiffProperty' => 'applications/differential/storage/DifferentialDiffProperty.php',
     'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php',
     'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php',
     'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php',
     'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php',
     'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php',
     'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php',
     'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php',
     'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
     'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php',
     'DifferentialEditPolicyField' => 'applications/differential/customfield/DifferentialEditPolicyField.php',
     'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php',
     'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php',
     'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php',
     'DifferentialGetAllDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetAllDiffsConduitAPIMethod.php',
     'DifferentialGetCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php',
     'DifferentialGetCommitPathsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitPathsConduitAPIMethod.php',
     'DifferentialGetDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetDiffConduitAPIMethod.php',
     'DifferentialGetRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRawDiffConduitAPIMethod.php',
     'DifferentialGetRevisionCommentsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionCommentsConduitAPIMethod.php',
     'DifferentialGetRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php',
     'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php',
     'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php',
     'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php',
     'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php',
     'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php',
     'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php',
     'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php',
     'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php',
     'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php',
     'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php',
     'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php',
     'DifferentialHunkParserTestCase' => 'applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php',
     'DifferentialHunkQuery' => 'applications/differential/query/DifferentialHunkQuery.php',
     'DifferentialHunkTestCase' => 'applications/differential/storage/__tests__/DifferentialHunkTestCase.php',
     'DifferentialInlineComment' => 'applications/differential/storage/DifferentialInlineComment.php',
     'DifferentialInlineCommentEditController' => 'applications/differential/controller/DifferentialInlineCommentEditController.php',
     'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/DifferentialInlineCommentPreviewController.php',
     'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php',
     'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php',
     'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php',
     'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php',
     'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php',
     'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php',
     'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
     'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
     'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
     'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
     'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php',
     'DifferentialNextStepField' => 'applications/differential/customfield/DifferentialNextStepField.php',
     'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
     'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
     'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
     'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php',
     'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php',
     'DifferentialProjectsField' => 'applications/differential/customfield/DifferentialProjectsField.php',
     'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php',
     'DifferentialQueryDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryDiffsConduitAPIMethod.php',
     'DifferentialRawDiffRenderer' => 'applications/differential/render/DifferentialRawDiffRenderer.php',
     'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php',
     'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php',
     'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php',
     'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
     'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
     'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php',
     'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
     'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php',
     'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
     'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
     'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
     'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
     'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php',
     'DifferentialReviewersAddReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php',
     'DifferentialReviewersAddSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php',
     'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php',
     'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php',
     'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
     'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
     'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php',
     'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php',
     'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php',
     'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php',
     'DifferentialRevisionContentAddedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentAddedHeraldField.php',
     'DifferentialRevisionContentHeraldField' => 'applications/differential/herald/DifferentialRevisionContentHeraldField.php',
     'DifferentialRevisionContentRemovedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentRemovedHeraldField.php',
     'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php',
     'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php',
     'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php',
     'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php',
     'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php',
     'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php',
     'DifferentialRevisionHasReviewerEdgeType' => 'applications/differential/edge/DifferentialRevisionHasReviewerEdgeType.php',
     'DifferentialRevisionHasTaskEdgeType' => 'applications/differential/edge/DifferentialRevisionHasTaskEdgeType.php',
     'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php',
     'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php',
     'DifferentialRevisionIDField' => 'applications/differential/customfield/DifferentialRevisionIDField.php',
     'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php',
     'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
     'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
     'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
     'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php',
     'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php',
     'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
     'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
     'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
     'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
     'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php',
     'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
     'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
     'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
     'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php',
     'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php',
     'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php',
     'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php',
     'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php',
     'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php',
     'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php',
     'DifferentialSubscribersField' => 'applications/differential/customfield/DifferentialSubscribersField.php',
     'DifferentialSummaryField' => 'applications/differential/customfield/DifferentialSummaryField.php',
     'DifferentialTestPlanField' => 'applications/differential/customfield/DifferentialTestPlanField.php',
     'DifferentialTitleField' => 'applications/differential/customfield/DifferentialTitleField.php',
     'DifferentialTransaction' => 'applications/differential/storage/DifferentialTransaction.php',
     'DifferentialTransactionComment' => 'applications/differential/storage/DifferentialTransactionComment.php',
     'DifferentialTransactionEditor' => 'applications/differential/editor/DifferentialTransactionEditor.php',
     'DifferentialTransactionQuery' => 'applications/differential/query/DifferentialTransactionQuery.php',
     'DifferentialTransactionView' => 'applications/differential/view/DifferentialTransactionView.php',
     'DifferentialUnitField' => 'applications/differential/customfield/DifferentialUnitField.php',
     'DifferentialUnitStatus' => 'applications/differential/constants/DifferentialUnitStatus.php',
     'DifferentialUnitTestResult' => 'applications/differential/constants/DifferentialUnitTestResult.php',
     'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php',
     'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php',
     'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php',
     'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php',
     'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php',
     'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php',
     'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php',
     'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php',
     'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php',
     'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php',
     'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php',
     'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php',
     'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php',
     'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php',
     'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php',
     'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php',
     'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php',
     'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php',
     'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php',
     'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php',
     'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php',
     'DiffusionCommitAuthorHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorHeraldField.php',
     'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php',
     'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php',
     'DiffusionCommitBranchesHeraldField' => 'applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php',
     'DiffusionCommitCommitterHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterHeraldField.php',
     'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php',
     'DiffusionCommitDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentAddedHeraldField.php',
     'DiffusionCommitDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentHeraldField.php',
     'DiffusionCommitDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentRemovedHeraldField.php',
     'DiffusionCommitDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffEnormousHeraldField.php',
     'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php',
     'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php',
     'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php',
     'DiffusionCommitHasTaskEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasTaskEdgeType.php',
     'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php',
     'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php',
     'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php',
     'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
     'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php',
     'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php',
     'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php',
     'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php',
     'DiffusionCommitPackageHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageHeraldField.php',
     'DiffusionCommitPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageOwnerHeraldField.php',
     'DiffusionCommitParentsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitParentsQueryConduitAPIMethod.php',
     'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php',
     'DiffusionCommitRef' => 'applications/diffusion/data/DiffusionCommitRef.php',
     'DiffusionCommitRemarkupRule' => 'applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php',
     'DiffusionCommitRemarkupRuleTestCase' => 'applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php',
     'DiffusionCommitRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryHeraldField.php',
     'DiffusionCommitRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryProjectsHeraldField.php',
     'DiffusionCommitRevertedByCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php',
     'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php',
     'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php',
     'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php',
     'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php',
     'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php',
     'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php',
     'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
     'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php',
     'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php',
     'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php',
     'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php',
     'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php',
     'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php',
     'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php',
     'DiffusionDiffController' => 'applications/diffusion/controller/DiffusionDiffController.php',
     'DiffusionDiffInlineCommentQuery' => 'applications/diffusion/query/DiffusionDiffInlineCommentQuery.php',
     'DiffusionDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionDiffQueryConduitAPIMethod.php',
     'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php',
     'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php',
     'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php',
     'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php',
     'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php',
     'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php',
     'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php',
     'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php',
     'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php',
     'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php',
     'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php',
     'DiffusionGitBlameQuery' => 'applications/diffusion/query/blame/DiffusionGitBlameQuery.php',
     'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php',
     'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php',
     'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
     'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php',
     'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php',
     'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php',
     'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
     'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php',
     'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
     'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
     'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php',
     'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php',
     'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
     'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php',
     'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
     'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
     'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
     'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php',
     'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
     'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
     'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php',
     'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php',
     'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php',
     'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php',
     'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php',
     'DiffusionLowLevelCommitQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php',
     'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php',
     'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php',
     'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php',
     'DiffusionLowLevelMercurialPathsQueryTests' => 'applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php',
     'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php',
     'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php',
     'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php',
     'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php',
     'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php',
     'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php',
     'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php',
     'DiffusionMercurialResponse' => 'applications/diffusion/response/DiffusionMercurialResponse.php',
     'DiffusionMercurialSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialSSHWorkflow.php',
     'DiffusionMercurialServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php',
     'DiffusionMercurialWireClientSSHProtocolChannel' => 'applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php',
     'DiffusionMercurialWireProtocol' => 'applications/diffusion/protocol/DiffusionMercurialWireProtocol.php',
     'DiffusionMercurialWireProtocolTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialWireProtocolTests.php',
     'DiffusionMercurialWireSSHTestCase' => 'applications/diffusion/ssh/__tests__/DiffusionMercurialWireSSHTestCase.php',
     'DiffusionMergedCommitsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php',
     'DiffusionMirrorDeleteController' => 'applications/diffusion/controller/DiffusionMirrorDeleteController.php',
     'DiffusionMirrorEditController' => 'applications/diffusion/controller/DiffusionMirrorEditController.php',
     'DiffusionPathChange' => 'applications/diffusion/data/DiffusionPathChange.php',
     'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php',
     'DiffusionPathCompleteController' => 'applications/diffusion/controller/DiffusionPathCompleteController.php',
     'DiffusionPathIDQuery' => 'applications/diffusion/query/pathid/DiffusionPathIDQuery.php',
     'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php',
     'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php',
     'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php',
     'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php',
     'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php',
     'DiffusionPreCommitContentAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAffectedFilesHeraldField.php',
     'DiffusionPreCommitContentAuthorHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorHeraldField.php',
     'DiffusionPreCommitContentAuthorRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorRawHeraldField.php',
     'DiffusionPreCommitContentBranchesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentBranchesHeraldField.php',
     'DiffusionPreCommitContentCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterHeraldField.php',
     'DiffusionPreCommitContentCommitterRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterRawHeraldField.php',
     'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentAddedHeraldField.php',
     'DiffusionPreCommitContentDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentHeraldField.php',
     'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentRemovedHeraldField.php',
     'DiffusionPreCommitContentDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffEnormousHeraldField.php',
     'DiffusionPreCommitContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentHeraldField.php',
     'DiffusionPreCommitContentMergeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMergeHeraldField.php',
     'DiffusionPreCommitContentMessageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMessageHeraldField.php',
     'DiffusionPreCommitContentPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherHeraldField.php',
     'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherIsCommitterHeraldField.php',
     'DiffusionPreCommitContentPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherProjectsHeraldField.php',
     'DiffusionPreCommitContentRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryHeraldField.php',
     'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryProjectsHeraldField.php',
     'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php',
     'DiffusionPreCommitContentRevisionHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionHeraldField.php',
     'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php',
     'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionSubscribersHeraldField.php',
     'DiffusionPreCommitRefChangeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefChangeHeraldField.php',
     'DiffusionPreCommitRefHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldField.php',
     'DiffusionPreCommitRefHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldFieldGroup.php',
     'DiffusionPreCommitRefNameHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefNameHeraldField.php',
     'DiffusionPreCommitRefPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherHeraldField.php',
     'DiffusionPreCommitRefPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherProjectsHeraldField.php',
     'DiffusionPreCommitRefRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryHeraldField.php',
     'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryProjectsHeraldField.php',
     'DiffusionPreCommitRefTypeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php',
     'DiffusionPullEventGarbageCollector' => 'applications/diffusion/garbagecollector/DiffusionPullEventGarbageCollector.php',
     'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php',
     'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php',
     'DiffusionPushLogController' => 'applications/diffusion/controller/DiffusionPushLogController.php',
     'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php',
     'DiffusionPushLogListView' => 'applications/diffusion/view/DiffusionPushLogListView.php',
     'DiffusionPythonExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php',
     'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php',
     'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php',
     'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php',
     'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php',
     'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php',
     'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php',
     'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php',
     'DiffusionRefDatasource' => 'applications/diffusion/typeahead/DiffusionRefDatasource.php',
     'DiffusionRefNotFoundException' => 'applications/diffusion/exception/DiffusionRefNotFoundException.php',
     'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php',
     'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php',
     'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
     'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php',
     'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
     'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php',
     'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php',
     'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
     'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php',
     'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
     'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php',
     'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
     'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php',
     'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
     'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php',
     'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php',
     'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php',
     'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php',
     'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php',
     'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php',
     'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php',
     'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php',
     'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php',
     'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php',
     'DiffusionRepositoryNewController' => 'applications/diffusion/controller/DiffusionRepositoryNewController.php',
     'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
     'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php',
     'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
     'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php',
     'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
     'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
     'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
     'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
     'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
     'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
     'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
     'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php',
     'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
     'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
     'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
     'DiffusionSubversionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionSSHWorkflow.php',
     'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php',
     'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php',
     'DiffusionSubversionWireProtocolTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php',
     'DiffusionSvnBlameQuery' => 'applications/diffusion/query/blame/DiffusionSvnBlameQuery.php',
     'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php',
     'DiffusionSvnRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionSvnRawDiffQuery.php',
     'DiffusionSvnRequest' => 'applications/diffusion/request/DiffusionSvnRequest.php',
     'DiffusionSymbolController' => 'applications/diffusion/controller/DiffusionSymbolController.php',
     'DiffusionSymbolDatasource' => 'applications/diffusion/typeahead/DiffusionSymbolDatasource.php',
     'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php',
     'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
     'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
     'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
     'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php',
     'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php',
     'DiffusionView' => 'applications/diffusion/view/DiffusionView.php',
     'DivinerArticleAtomizer' => 'applications/diviner/atomizer/DivinerArticleAtomizer.php',
     'DivinerAtom' => 'applications/diviner/atom/DivinerAtom.php',
     'DivinerAtomCache' => 'applications/diviner/cache/DivinerAtomCache.php',
     'DivinerAtomController' => 'applications/diviner/controller/DivinerAtomController.php',
     'DivinerAtomListController' => 'applications/diviner/controller/DivinerAtomListController.php',
     'DivinerAtomPHIDType' => 'applications/diviner/phid/DivinerAtomPHIDType.php',
     'DivinerAtomQuery' => 'applications/diviner/query/DivinerAtomQuery.php',
     'DivinerAtomRef' => 'applications/diviner/atom/DivinerAtomRef.php',
     'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php',
     'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php',
     'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php',
     'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php',
     'DivinerBookDatasource' => 'applications/diviner/typeahead/DivinerBookDatasource.php',
     'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php',
     'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php',
     'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php',
     'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php',
     'DivinerController' => 'applications/diviner/controller/DivinerController.php',
     'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php',
     'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php',
     'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php',
     'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php',
     'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php',
     'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php',
     'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php',
     'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php',
     'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php',
     'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php',
     'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php',
     'DivinerLiveBookFulltextEngine' => 'applications/diviner/search/DivinerLiveBookFulltextEngine.php',
     'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php',
     'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php',
     'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php',
     'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php',
     'DivinerLiveSymbolFulltextEngine' => 'applications/diviner/search/DivinerLiveSymbolFulltextEngine.php',
     'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php',
     'DivinerPHPAtomizer' => 'applications/diviner/atomizer/DivinerPHPAtomizer.php',
     'DivinerParameterTableView' => 'applications/diviner/view/DivinerParameterTableView.php',
     'DivinerPublishCache' => 'applications/diviner/cache/DivinerPublishCache.php',
     'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php',
     'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php',
     'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php',
     'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php',
     'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php',
     'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php',
     'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php',
     'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php',
     'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php',
     'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php',
     'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php',
     'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php',
     'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php',
     'DoorkeeperBridgeGitHubIssue' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php',
     'DoorkeeperBridgeGitHubUser' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php',
     'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php',
     'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php',
     'DoorkeeperBridgedObjectCurtainExtension' => 'applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php',
     'DoorkeeperBridgedObjectInterface' => 'applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php',
     'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php',
     'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php',
     'DoorkeeperExternalObjectPHIDType' => 'applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php',
     'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php',
     'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php',
     'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php',
     'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php',
     'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php',
     'DoorkeeperJIRARemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php',
     'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php',
     'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php',
     'DoorkeeperRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php',
     'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php',
     'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php',
     'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
     'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
     'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
     'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php',
     'DrydockAuthorizationAuthorizeController' => 'applications/drydock/controller/DrydockAuthorizationAuthorizeController.php',
     'DrydockAuthorizationListController' => 'applications/drydock/controller/DrydockAuthorizationListController.php',
     'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php',
     'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php',
     'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php',
     'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php',
     'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php',
     'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
     'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
     'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php',
     'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php',
     'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php',
     'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php',
     'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
     'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php',
     'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php',
     'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
     'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php',
     'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
     'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php',
     'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php',
     'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
     'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php',
     'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php',
     'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php',
     'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php',
     'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php',
     'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php',
     'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php',
     'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php',
     'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php',
     'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
     'DrydockController' => 'applications/drydock/controller/DrydockController.php',
     'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php',
     'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php',
     'DrydockDefaultEditCapability' => 'applications/drydock/capability/DrydockDefaultEditCapability.php',
     'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php',
     'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
     'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
     'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php',
     'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
     'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
     'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php',
     'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php',
     'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php',
     'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php',
     'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php',
     'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php',
     'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
     'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php',
     'DrydockLeaseNoAuthorizationsLogType' => 'applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php',
     'DrydockLeaseNoBlueprintsLogType' => 'applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php',
     'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php',
     'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php',
     'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php',
     'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php',
     'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php',
     'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php',
     'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php',
     'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
     'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php',
     'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
     'DrydockLeaseWaitingForResourcesLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php',
     'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
     'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
     'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php',
     'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php',
     'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php',
     'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
     'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
     'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php',
     'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
     'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
     'DrydockManagementReclaimWorkflow' => 'applications/drydock/management/DrydockManagementReclaimWorkflow.php',
     'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php',
     'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php',
     'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php',
     'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php',
     'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
     'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php',
     'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
     'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php',
     'DrydockRepositoryOperationController' => 'applications/drydock/controller/DrydockRepositoryOperationController.php',
     'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php',
     'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php',
     'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php',
     'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php',
     'DrydockRepositoryOperationSearchEngine' => 'applications/drydock/query/DrydockRepositoryOperationSearchEngine.php',
     'DrydockRepositoryOperationStatusController' => 'applications/drydock/controller/DrydockRepositoryOperationStatusController.php',
     'DrydockRepositoryOperationStatusView' => 'applications/drydock/view/DrydockRepositoryOperationStatusView.php',
     'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php',
     'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php',
     'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php',
     'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
     'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php',
     'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php',
     'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php',
     'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php',
     'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php',
     'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php',
     'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php',
     'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php',
     'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php',
     'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php',
     'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php',
     'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php',
     'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php',
     'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
     'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
     'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
     'DrydockSchemaSpec' => 'applications/drydock/storage/DrydockSchemaSpec.php',
     'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
     'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
     'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
     'DrydockTestRepositoryOperation' => 'applications/drydock/operation/DrydockTestRepositoryOperation.php',
     'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
     'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
     'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
     'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php',
     'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php',
     'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php',
     'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php',
     'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php',
     'FeedQueryConduitAPIMethod' => 'applications/feed/conduit/FeedQueryConduitAPIMethod.php',
     'FeedStoryNotificationGarbageCollector' => 'applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php',
     'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php',
     'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php',
     'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
     'FileDownloadConduitAPIMethod' => 'applications/files/conduit/FileDownloadConduitAPIMethod.php',
     'FileInfoConduitAPIMethod' => 'applications/files/conduit/FileInfoConduitAPIMethod.php',
     'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
     'FileQueryChunksConduitAPIMethod' => 'applications/files/conduit/FileQueryChunksConduitAPIMethod.php',
     'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
     'FileUploadChunkConduitAPIMethod' => 'applications/files/conduit/FileUploadChunkConduitAPIMethod.php',
     'FileUploadConduitAPIMethod' => 'applications/files/conduit/FileUploadConduitAPIMethod.php',
     'FileUploadHashConduitAPIMethod' => 'applications/files/conduit/FileUploadHashConduitAPIMethod.php',
     'FilesDefaultViewCapability' => 'applications/files/capability/FilesDefaultViewCapability.php',
     'FlagConduitAPIMethod' => 'applications/flag/conduit/FlagConduitAPIMethod.php',
     'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php',
     'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php',
     'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php',
     'FundBacker' => 'applications/fund/storage/FundBacker.php',
     'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php',
     'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
     'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php',
     'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php',
     'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php',
     'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php',
     'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php',
     'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php',
     'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php',
     'FundController' => 'applications/fund/controller/FundController.php',
     'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php',
     'FundDAO' => 'applications/fund/storage/FundDAO.php',
     'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php',
     'FundInitiative' => 'applications/fund/storage/FundInitiative.php',
     'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php',
     'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php',
     'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php',
     'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php',
     'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php',
     'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php',
     'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php',
     'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php',
     'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php',
     'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php',
     'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php',
     'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php',
     'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php',
     'FundInitiativeTransactionComment' => 'applications/fund/storage/FundInitiativeTransactionComment.php',
     'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php',
     'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php',
     'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php',
     'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php',
     'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php',
     'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php',
     'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php',
     'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
     'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php',
     'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php',
     'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php',
     'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
     'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php',
     'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php',
     'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php',
     'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php',
     'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php',
     'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php',
     'HarbormasterBuildFailureException' => 'applications/harbormaster/exception/HarbormasterBuildFailureException.php',
     'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php',
     'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php',
     'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
     'HarbormasterBuildLogChunk' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php',
     'HarbormasterBuildLogChunkIterator' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php',
     'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php',
     'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php',
     'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php',
     'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php',
     'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
     'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
     'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
     'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
     'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
     'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php',
     'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
     'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php',
     'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
     'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
     'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
     'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
     'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
     'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
     'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php',
     'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
     'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
     'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
     'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
     'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php',
     'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php',
     'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php',
     'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php',
     'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
     'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
     'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
     'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
     'HarbormasterBuildTargetPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildTargetPHIDType.php',
     'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
     'HarbormasterBuildTransaction' => 'applications/harbormaster/storage/HarbormasterBuildTransaction.php',
     'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php',
     'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
     'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
     'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
     'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
     'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
     'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php',
     'HarbormasterBuildableAdapterInterface' => 'applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php',
     'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php',
     'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
     'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php',
     'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
     'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php',
     'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php',
     'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php',
     'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php',
     'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
     'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php',
     'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php',
     'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php',
     'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php',
     'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
     'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
     'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
     'HarbormasterCreatePlansCapability' => 'applications/harbormaster/capability/HarbormasterCreatePlansCapability.php',
     'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
     'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php',
     'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
     'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
     'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php',
     'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
     'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
     'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
     'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
     'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
     'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
     'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
     'HarbormasterManagementArchiveLogsWorkflow' => 'applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php',
     'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
     'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
     'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
     'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php',
     'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
     'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php',
     'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
     'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php',
     'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
     'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
     'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php',
     'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
     'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php',
     'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php',
     'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php',
     'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php',
     'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
     'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
     'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
     'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
     'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
     'HarbormasterSendMessageConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php',
     'HarbormasterSleepBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php',
     'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
     'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
     'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
     'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php',
     'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
     'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
     'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php',
     'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
     'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
     'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php',
     'HarbormasterUnitMessageListController' => 'applications/harbormaster/controller/HarbormasterUnitMessageListController.php',
     'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php',
     'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php',
     'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php',
     'HarbormasterUnitSummaryView' => 'applications/harbormaster/view/HarbormasterUnitSummaryView.php',
     'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
     'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
     'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
     'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php',
     'HeraldAction' => 'applications/herald/action/HeraldAction.php',
     'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php',
     'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php',
     'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
     'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php',
     'HeraldAnotherRuleField' => 'applications/herald/field/HeraldAnotherRuleField.php',
     'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php',
     'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php',
     'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php',
     'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php',
     'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
     'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
     'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php',
     'HeraldController' => 'applications/herald/controller/HeraldController.php',
     'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php',
     'HeraldDifferentialAdapter' => 'applications/differential/herald/HeraldDifferentialAdapter.php',
     'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php',
     'HeraldDifferentialRevisionAdapter' => 'applications/differential/herald/HeraldDifferentialRevisionAdapter.php',
     'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php',
     'HeraldDoNothingAction' => 'applications/herald/action/HeraldDoNothingAction.php',
     'HeraldEditFieldGroup' => 'applications/herald/field/HeraldEditFieldGroup.php',
     'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php',
     'HeraldEmptyFieldValue' => 'applications/herald/value/HeraldEmptyFieldValue.php',
     'HeraldEngine' => 'applications/herald/engine/HeraldEngine.php',
     'HeraldExactProjectsField' => 'applications/project/herald/HeraldExactProjectsField.php',
     'HeraldField' => 'applications/herald/field/HeraldField.php',
     'HeraldFieldGroup' => 'applications/herald/field/HeraldFieldGroup.php',
     'HeraldFieldTestCase' => 'applications/herald/field/__tests__/HeraldFieldTestCase.php',
     'HeraldFieldValue' => 'applications/herald/value/HeraldFieldValue.php',
     'HeraldGroup' => 'applications/herald/group/HeraldGroup.php',
     'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php',
     'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php',
     'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php',
     'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php',
     'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php',
     'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php',
     'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php',
     'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php',
     'HeraldPhameBlogAdapter' => 'applications/phame/herald/HeraldPhameBlogAdapter.php',
     'HeraldPhamePostAdapter' => 'applications/phame/herald/HeraldPhamePostAdapter.php',
     'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php',
     'HeraldPonderQuestionAdapter' => 'applications/ponder/herald/HeraldPonderQuestionAdapter.php',
     'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php',
     'HeraldPreCommitContentAdapter' => 'applications/diffusion/herald/HeraldPreCommitContentAdapter.php',
     'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php',
     'HeraldPreventActionGroup' => 'applications/herald/action/HeraldPreventActionGroup.php',
     'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php',
     'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php',
     'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php',
     'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php',
     'HeraldRepetitionPolicyConfig' => 'applications/herald/config/HeraldRepetitionPolicyConfig.php',
     'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
     'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php',
     'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
     'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
     'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php',
     'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
     'HeraldRuleSearchEngine' => 'applications/herald/query/HeraldRuleSearchEngine.php',
     'HeraldRuleSerializer' => 'applications/herald/editor/HeraldRuleSerializer.php',
     'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php',
     'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php',
     'HeraldRuleTransactionComment' => 'applications/herald/storage/HeraldRuleTransactionComment.php',
     'HeraldRuleTranscript' => 'applications/herald/storage/transcript/HeraldRuleTranscript.php',
     'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php',
     'HeraldRuleViewController' => 'applications/herald/controller/HeraldRuleViewController.php',
     'HeraldSchemaSpec' => 'applications/herald/storage/HeraldSchemaSpec.php',
     'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php',
     'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php',
     'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php',
     'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php',
     'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php',
     'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php',
     'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php',
     'HeraldTokenizerFieldValue' => 'applications/herald/value/HeraldTokenizerFieldValue.php',
     'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php',
     'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php',
     'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php',
     'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php',
     'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php',
     'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php',
     'HeraldTranscriptPHIDType' => 'applications/herald/phid/HeraldTranscriptPHIDType.php',
     'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php',
     'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php',
     'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php',
     'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php',
     'Javelin' => 'infrastructure/javelin/Javelin.php',
     'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php',
     'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php',
     'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php',
     'JavelinViewUIExample' => 'applications/uiexample/examples/JavelinViewUIExample.php',
     'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php',
     'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php',
     'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php',
     'LegalpadDefaultEditCapability' => 'applications/legalpad/capability/LegalpadDefaultEditCapability.php',
     'LegalpadDefaultViewCapability' => 'applications/legalpad/capability/LegalpadDefaultViewCapability.php',
     'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php',
     'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php',
     'LegalpadDocumentCommentController' => 'applications/legalpad/controller/LegalpadDocumentCommentController.php',
     'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php',
     'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php',
     'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php',
     'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php',
     'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php',
     'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php',
     'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php',
     'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php',
     'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
     'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php',
     'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php',
     'LegalpadDocumentSignatureAddController' => 'applications/legalpad/controller/LegalpadDocumentSignatureAddController.php',
     'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php',
     'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php',
     'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php',
     'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php',
     'LegalpadDocumentSignatureViewController' => 'applications/legalpad/controller/LegalpadDocumentSignatureViewController.php',
     'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php',
     'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php',
     'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php',
     'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php',
     'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php',
     'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php',
     'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php',
     'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
     'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
     'LegalpadTransactionView' => 'applications/legalpad/view/LegalpadTransactionView.php',
     'LiskChunkTestCase' => 'infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php',
     'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php',
     'LiskDAOSet' => 'infrastructure/storage/lisk/LiskDAOSet.php',
     'LiskDAOTestCase' => 'infrastructure/storage/lisk/__tests__/LiskDAOTestCase.php',
     'LiskEphemeralObjectException' => 'infrastructure/storage/lisk/LiskEphemeralObjectException.php',
     'LiskFixtureTestCase' => 'infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php',
     'LiskIsolationTestCase' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestCase.php',
     'LiskIsolationTestDAO' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php',
     'LiskIsolationTestDAOException' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAOException.php',
     'LiskMigrationIterator' => 'infrastructure/storage/lisk/LiskMigrationIterator.php',
     'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php',
     'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php',
     'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php',
     'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php',
     'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php',
     'ManiphestAssigneeDatasource' => 'applications/maniphest/typeahead/ManiphestAssigneeDatasource.php',
     'ManiphestBatchEditController' => 'applications/maniphest/controller/ManiphestBatchEditController.php',
     'ManiphestBulkEditCapability' => 'applications/maniphest/capability/ManiphestBulkEditCapability.php',
     'ManiphestClaimEmailCommand' => 'applications/maniphest/command/ManiphestClaimEmailCommand.php',
     'ManiphestCloseEmailCommand' => 'applications/maniphest/command/ManiphestCloseEmailCommand.php',
     'ManiphestConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestConduitAPIMethod.php',
     'ManiphestConfiguredCustomField' => 'applications/maniphest/field/ManiphestConfiguredCustomField.php',
     'ManiphestConstants' => 'applications/maniphest/constants/ManiphestConstants.php',
     'ManiphestController' => 'applications/maniphest/controller/ManiphestController.php',
     'ManiphestCreateMailReceiver' => 'applications/maniphest/mail/ManiphestCreateMailReceiver.php',
     'ManiphestCreateTaskConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php',
     'ManiphestCustomField' => 'applications/maniphest/field/ManiphestCustomField.php',
     'ManiphestCustomFieldNumericIndex' => 'applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php',
     'ManiphestCustomFieldStatusParser' => 'applications/maniphest/field/parser/ManiphestCustomFieldStatusParser.php',
     'ManiphestCustomFieldStatusParserTestCase' => 'applications/maniphest/field/parser/__tests__/ManiphestCustomFieldStatusParserTestCase.php',
     'ManiphestCustomFieldStorage' => 'applications/maniphest/storage/ManiphestCustomFieldStorage.php',
     'ManiphestCustomFieldStringIndex' => 'applications/maniphest/storage/ManiphestCustomFieldStringIndex.php',
     'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php',
     'ManiphestDefaultEditCapability' => 'applications/maniphest/capability/ManiphestDefaultEditCapability.php',
     'ManiphestDefaultViewCapability' => 'applications/maniphest/capability/ManiphestDefaultViewCapability.php',
     'ManiphestEditAssignCapability' => 'applications/maniphest/capability/ManiphestEditAssignCapability.php',
     'ManiphestEditConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestEditConduitAPIMethod.php',
     'ManiphestEditEngine' => 'applications/maniphest/editor/ManiphestEditEngine.php',
     'ManiphestEditPoliciesCapability' => 'applications/maniphest/capability/ManiphestEditPoliciesCapability.php',
     'ManiphestEditPriorityCapability' => 'applications/maniphest/capability/ManiphestEditPriorityCapability.php',
     'ManiphestEditProjectsCapability' => 'applications/maniphest/capability/ManiphestEditProjectsCapability.php',
     'ManiphestEditStatusCapability' => 'applications/maniphest/capability/ManiphestEditStatusCapability.php',
     'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php',
     'ManiphestExcelDefaultFormat' => 'applications/maniphest/export/ManiphestExcelDefaultFormat.php',
     'ManiphestExcelFormat' => 'applications/maniphest/export/ManiphestExcelFormat.php',
     'ManiphestExcelFormatTestCase' => 'applications/maniphest/export/__tests__/ManiphestExcelFormatTestCase.php',
     'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php',
     'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php',
     'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
     'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
     'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
     'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php',
     'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php',
     'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php',
     'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php',
     'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php',
     'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php',
     'ManiphestRemarkupRule' => 'applications/maniphest/remarkup/ManiphestRemarkupRule.php',
     'ManiphestReplyHandler' => 'applications/maniphest/mail/ManiphestReplyHandler.php',
     'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
     'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php',
     'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php',
     'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php',
     'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php',
     'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php',
     'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php',
     'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php',
     'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php',
     'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php',
     'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php',
     'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php',
     'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php',
     'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php',
     'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php',
     'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php',
     'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php',
     'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php',
     'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php',
     'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
     'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
     'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
     'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php',
     'ManiphestTaskHasRevisionEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasRevisionEdgeType.php',
     'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php',
     'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php',
     'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
     'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
     'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
     'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
     'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
     'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
     'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
     'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.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',
     'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php',
     'ManiphestTaskResultListView' => 'applications/maniphest/view/ManiphestTaskResultListView.php',
     'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php',
     'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php',
     'ManiphestTaskStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php',
     'ManiphestTaskStatusFunctionDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusFunctionDatasource.php',
     'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php',
     'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php',
     'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php',
     'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php',
     'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php',
     'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php',
     'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php',
     'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php',
     'ManiphestTransactionQuery' => 'applications/maniphest/query/ManiphestTransactionQuery.php',
     'ManiphestUpdateConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php',
     'ManiphestView' => 'applications/maniphest/view/ManiphestView.php',
     'MetaMTAEmailTransactionCommand' => 'applications/metamta/command/MetaMTAEmailTransactionCommand.php',
     'MetaMTAEmailTransactionCommandTestCase' => 'applications/metamta/command/__tests__/MetaMTAEmailTransactionCommandTestCase.php',
     'MetaMTAMailReceivedGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php',
     'MetaMTAMailSentGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php',
     'MetaMTAReceivedMailStatus' => 'applications/metamta/constants/MetaMTAReceivedMailStatus.php',
     'MultimeterContext' => 'applications/multimeter/storage/MultimeterContext.php',
     'MultimeterControl' => 'applications/multimeter/data/MultimeterControl.php',
     'MultimeterController' => 'applications/multimeter/controller/MultimeterController.php',
     'MultimeterDAO' => 'applications/multimeter/storage/MultimeterDAO.php',
     'MultimeterDimension' => 'applications/multimeter/storage/MultimeterDimension.php',
     'MultimeterEvent' => 'applications/multimeter/storage/MultimeterEvent.php',
     'MultimeterEventGarbageCollector' => 'applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php',
     'MultimeterHost' => 'applications/multimeter/storage/MultimeterHost.php',
     'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php',
     'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php',
     'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php',
     'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php',
     'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php',
     'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php',
     'NuanceController' => 'applications/nuance/controller/NuanceController.php',
     'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php',
     'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php',
     'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php',
     'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php',
     'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php',
     'NuanceGitHubRawEventTestCase' => 'applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php',
     'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php',
     'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php',
     'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php',
     'NuanceImportCursorData' => 'applications/nuance/storage/NuanceImportCursorData.php',
     'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php',
     'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php',
     'NuanceItem' => 'applications/nuance/storage/NuanceItem.php',
     'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php',
     'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php',
     'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php',
     'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php',
     'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php',
     'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php',
     'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php',
     'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php',
     'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php',
     'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php',
     'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php',
     'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php',
     'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php',
     'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php',
     'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php',
     'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php',
     'NuanceManagementImportWorkflow' => 'applications/nuance/management/NuanceManagementImportWorkflow.php',
     'NuanceManagementUpdateWorkflow' => 'applications/nuance/management/NuanceManagementUpdateWorkflow.php',
     'NuanceManagementWorkflow' => 'applications/nuance/management/NuanceManagementWorkflow.php',
     'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php',
     'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php',
     'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php',
     'NuanceQueueController' => 'applications/nuance/controller/NuanceQueueController.php',
     'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php',
     'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php',
     'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php',
     'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php',
     'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php',
     'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php',
     'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php',
     'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php',
     'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php',
     'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php',
     'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php',
     'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php',
     'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php',
     'NuanceSource' => 'applications/nuance/storage/NuanceSource.php',
     'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php',
     'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php',
     'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php',
     'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php',
     'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php',
     'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php',
     'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php',
     'NuanceSourceEditEngine' => 'applications/nuance/editor/NuanceSourceEditEngine.php',
     'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php',
     'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php',
     'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php',
     'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php',
     'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php',
     'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php',
     'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php',
     'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php',
     'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php',
     'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php',
     'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php',
     'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php',
     'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php',
     'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php',
     'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php',
     'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php',
     'OwnersQueryConduitAPIMethod' => 'applications/owners/conduit/OwnersQueryConduitAPIMethod.php',
     'OwnersSearchConduitAPIMethod' => 'applications/owners/conduit/OwnersSearchConduitAPIMethod.php',
     'PHIDConduitAPIMethod' => 'applications/phid/conduit/PHIDConduitAPIMethod.php',
     'PHIDInfoConduitAPIMethod' => 'applications/phid/conduit/PHIDInfoConduitAPIMethod.php',
     'PHIDLookupConduitAPIMethod' => 'applications/phid/conduit/PHIDLookupConduitAPIMethod.php',
     'PHIDQueryConduitAPIMethod' => 'applications/phid/conduit/PHIDQueryConduitAPIMethod.php',
     'PHUI' => 'view/phui/PHUI.php',
     'PHUIActionPanelExample' => 'applications/uiexample/examples/PHUIActionPanelExample.php',
     'PHUIActionPanelView' => 'view/phui/PHUIActionPanelView.php',
     'PHUIApplicationMenuView' => 'view/layout/PHUIApplicationMenuView.php',
     'PHUIBadgeBoxView' => 'view/phui/PHUIBadgeBoxView.php',
     'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php',
     'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php',
     'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php',
     'PHUIBigInfoView' => 'view/phui/PHUIBigInfoView.php',
     'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php',
     'PHUIBoxView' => 'view/phui/PHUIBoxView.php',
     'PHUIButtonBarExample' => 'applications/uiexample/examples/PHUIButtonBarExample.php',
     'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php',
     'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php',
     'PHUIButtonView' => 'view/phui/PHUIButtonView.php',
     'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php',
     'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php',
     'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php',
     'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php',
     'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php',
     'PHUICrumbView' => 'view/phui/PHUICrumbView.php',
     'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php',
     'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php',
     'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php',
     'PHUICurtainView' => 'view/layout/PHUICurtainView.php',
     'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php',
     'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php',
     'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php',
     'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php',
     'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php',
     'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php',
     'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php',
     'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php',
     'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php',
     'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php',
     'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php',
     'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php',
     'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php',
     'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php',
     'PHUIDocumentViewPro' => 'view/phui/PHUIDocumentViewPro.php',
     'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php',
     'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php',
     'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php',
     'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php',
     'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php',
     'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php',
     'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
     'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php',
     'PHUIFormPageView' => 'view/form/PHUIFormPageView.php',
     'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
     'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
     'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
     'PHUIHeadThingView' => 'view/phui/PHUIHeadThingView.php',
     'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php',
     'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php',
     'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php',
     'PHUIIconCircleView' => 'view/phui/PHUIIconCircleView.php',
     'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php',
     'PHUIIconView' => 'view/phui/PHUIIconView.php',
     'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php',
     'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php',
     'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php',
     'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php',
     'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php',
     'PHUIInfoView' => 'view/form/PHUIInfoView.php',
     'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
     'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
     'PHUIListView' => 'view/phui/PHUIListView.php',
     'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php',
     'PHUIMainMenuView' => 'view/phui/PHUIMainMenuView.php',
     'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php',
     'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php',
     'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php',
     'PHUIObjectItemView' => 'view/phui/PHUIObjectItemView.php',
     'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php',
     'PHUIPagerView' => 'view/phui/PHUIPagerView.php',
     'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php',
     'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php',
     'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php',
     'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php',
     'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
     'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
     'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php',
     'PHUISegmentBarSegmentView' => 'view/phui/PHUISegmentBarSegmentView.php',
     'PHUISegmentBarView' => 'view/phui/PHUISegmentBarView.php',
     'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php',
     'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
     'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
     'PHUITagExample' => 'applications/uiexample/examples/PHUITagExample.php',
     'PHUITagView' => 'view/phui/PHUITagView.php',
     'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php',
     'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php',
     'PHUITimelineView' => 'view/phui/PHUITimelineView.php',
     'PHUITwoColumnView' => 'view/phui/PHUITwoColumnView.php',
     'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php',
     'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php',
     'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php',
     'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php',
     'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php',
     'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php',
     'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php',
     'PassphraseCredentialAuthorPolicyRule' => 'applications/passphrase/policyrule/PassphraseCredentialAuthorPolicyRule.php',
     'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php',
     'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php',
     'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php',
     'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
     'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
     'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php',
     'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
     'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php',
     'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php',
     'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
     'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
     'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
     'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
     'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php',
     'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php',
     'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
     'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
     'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php',
     'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php',
     'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php',
     'PassphraseDefaultEditCapability' => 'applications/passphrase/capability/PassphraseDefaultEditCapability.php',
     'PassphraseDefaultViewCapability' => 'applications/passphrase/capability/PassphraseDefaultViewCapability.php',
     'PassphraseNoteCredentialType' => 'applications/passphrase/credentialtype/PassphraseNoteCredentialType.php',
     'PassphrasePasswordCredentialType' => 'applications/passphrase/credentialtype/PassphrasePasswordCredentialType.php',
     'PassphrasePasswordKey' => 'applications/passphrase/keys/PassphrasePasswordKey.php',
     'PassphraseQueryConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php',
     'PassphraseRemarkupRule' => 'applications/passphrase/remarkup/PassphraseRemarkupRule.php',
     'PassphraseSSHGeneratedKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHGeneratedKeyCredentialType.php',
     'PassphraseSSHKey' => 'applications/passphrase/keys/PassphraseSSHKey.php',
     'PassphraseSSHPrivateKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyCredentialType.php',
     'PassphraseSSHPrivateKeyFileCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyFileCredentialType.php',
     'PassphraseSSHPrivateKeyTextCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php',
     'PassphraseSchemaSpec' => 'applications/passphrase/storage/PassphraseSchemaSpec.php',
     'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php',
     'PassphraseTokenCredentialType' => 'applications/passphrase/credentialtype/PassphraseTokenCredentialType.php',
     'PasteConduitAPIMethod' => 'applications/paste/conduit/PasteConduitAPIMethod.php',
     'PasteCreateConduitAPIMethod' => 'applications/paste/conduit/PasteCreateConduitAPIMethod.php',
     'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
     'PasteDefaultEditCapability' => 'applications/paste/capability/PasteDefaultEditCapability.php',
     'PasteDefaultViewCapability' => 'applications/paste/capability/PasteDefaultViewCapability.php',
     'PasteEditConduitAPIMethod' => 'applications/paste/conduit/PasteEditConduitAPIMethod.php',
     'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php',
     'PasteInfoConduitAPIMethod' => 'applications/paste/conduit/PasteInfoConduitAPIMethod.php',
     'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php',
     'PasteQueryConduitAPIMethod' => 'applications/paste/conduit/PasteQueryConduitAPIMethod.php',
     'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php',
     'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
     'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
     'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
     'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
     'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
     'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
     'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
     'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php',
     'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
     'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
     'PhabricatorAccountSettingsPanel' => 'applications/settings/panel/PhabricatorAccountSettingsPanel.php',
     'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
     'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
     'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
     'PhabricatorAdministratorsPolicyRule' => 'applications/people/policyrule/PhabricatorAdministratorsPolicyRule.php',
     'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php',
     'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
     'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
     'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
     'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php',
     'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php',
     'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php',
     'PhabricatorAphlictManagementStatusWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php',
     'PhabricatorAphlictManagementStopWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php',
     'PhabricatorAphlictManagementWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php',
     'PhabricatorAphlictSetupCheck' => 'applications/notification/setup/PhabricatorAphlictSetupCheck.php',
     'PhabricatorAphrontBarUIExample' => 'applications/uiexample/examples/PhabricatorAphrontBarUIExample.php',
     'PhabricatorAphrontViewTestCase' => 'view/__tests__/PhabricatorAphrontViewTestCase.php',
     'PhabricatorAppSearchEngine' => 'applications/meta/query/PhabricatorAppSearchEngine.php',
     'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php',
     'PhabricatorApplicationApplicationPHIDType' => 'applications/meta/phid/PhabricatorApplicationApplicationPHIDType.php',
     'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php',
     'PhabricatorApplicationConfigurationPanel' => 'applications/meta/panel/PhabricatorApplicationConfigurationPanel.php',
     'PhabricatorApplicationConfigurationPanelTestCase' => 'applications/meta/panel/__tests__/PhabricatorApplicationConfigurationPanelTestCase.php',
     'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php',
     'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
     'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
     'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php',
     'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
     'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
     'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php',
     'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php',
     'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php',
     'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php',
     'PhabricatorApplicationSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorApplicationSearchEngineTestCase.php',
     'PhabricatorApplicationSearchResultView' => 'applications/search/view/PhabricatorApplicationSearchResultView.php',
     'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php',
     'PhabricatorApplicationTestCase' => 'applications/base/__tests__/PhabricatorApplicationTestCase.php',
     'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php',
     'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php',
     'PhabricatorApplicationTransactionCommentEditController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php',
     'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php',
     'PhabricatorApplicationTransactionCommentHistoryController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php',
     'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php',
     'PhabricatorApplicationTransactionCommentQuoteController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php',
     'PhabricatorApplicationTransactionCommentRawController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php',
     'PhabricatorApplicationTransactionCommentRemoveController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php',
     'PhabricatorApplicationTransactionCommentView' => 'applications/transactions/view/PhabricatorApplicationTransactionCommentView.php',
     'PhabricatorApplicationTransactionController' => 'applications/transactions/controller/PhabricatorApplicationTransactionController.php',
     'PhabricatorApplicationTransactionDetailController' => 'applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php',
     'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php',
     'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php',
     'PhabricatorApplicationTransactionInterface' => 'applications/transactions/interface/PhabricatorApplicationTransactionInterface.php',
     'PhabricatorApplicationTransactionNoEffectException' => 'applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php',
     'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php',
     'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php',
     'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php',
     'PhabricatorApplicationTransactionRemarkupPreviewController' => 'applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php',
     'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php',
     'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php',
     'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php',
     'PhabricatorApplicationTransactionStructureException' => 'applications/transactions/exception/PhabricatorApplicationTransactionStructureException.php',
     'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionTemplatedCommentQuery.php',
     'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php',
     'PhabricatorApplicationTransactionTransactionPHIDType' => 'applications/transactions/phid/PhabricatorApplicationTransactionTransactionPHIDType.php',
     'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php',
     'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php',
     'PhabricatorApplicationTransactionValidationResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionValidationResponse.php',
     'PhabricatorApplicationTransactionValueController' => 'applications/transactions/controller/PhabricatorApplicationTransactionValueController.php',
     'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php',
     'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php',
     'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php',
     'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php',
     'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php',
     'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php',
     'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php',
     'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php',
     'PhabricatorAsanaTaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaTaskHasObjectEdgeType.php',
     'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php',
     'PhabricatorAuditAddCommentController' => 'applications/audit/controller/PhabricatorAuditAddCommentController.php',
     'PhabricatorAuditApplication' => 'applications/audit/application/PhabricatorAuditApplication.php',
     'PhabricatorAuditCommentEditor' => 'applications/audit/editor/PhabricatorAuditCommentEditor.php',
     'PhabricatorAuditCommitStatusConstants' => 'applications/audit/constants/PhabricatorAuditCommitStatusConstants.php',
     'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php',
     'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php',
     'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php',
     'PhabricatorAuditListController' => 'applications/audit/controller/PhabricatorAuditListController.php',
     'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php',
     'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php',
     'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php',
     'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php',
     'PhabricatorAuditPreviewController' => 'applications/audit/controller/PhabricatorAuditPreviewController.php',
     'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php',
     'PhabricatorAuditStatusConstants' => 'applications/audit/constants/PhabricatorAuditStatusConstants.php',
     'PhabricatorAuditTransaction' => 'applications/audit/storage/PhabricatorAuditTransaction.php',
     'PhabricatorAuditTransactionComment' => 'applications/audit/storage/PhabricatorAuditTransactionComment.php',
     'PhabricatorAuditTransactionQuery' => 'applications/audit/query/PhabricatorAuditTransactionQuery.php',
     'PhabricatorAuditTransactionView' => 'applications/audit/view/PhabricatorAuditTransactionView.php',
     'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php',
     'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php',
     'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php',
     'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php',
     'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php',
     'PhabricatorAuthConfirmLinkController' => 'applications/auth/controller/PhabricatorAuthConfirmLinkController.php',
     'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
     'PhabricatorAuthDAO' => 'applications/auth/storage/PhabricatorAuthDAO.php',
     'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php',
     'PhabricatorAuthDowngradeSessionController' => 'applications/auth/controller/PhabricatorAuthDowngradeSessionController.php',
     'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
     'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php',
     'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
     'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
     'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
     'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php',
     'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php',
     'PhabricatorAuthInvite' => 'applications/auth/storage/PhabricatorAuthInvite.php',
     'PhabricatorAuthInviteAccountException' => 'applications/auth/exception/PhabricatorAuthInviteAccountException.php',
     'PhabricatorAuthInviteAction' => 'applications/auth/data/PhabricatorAuthInviteAction.php',
     'PhabricatorAuthInviteActionTableView' => 'applications/auth/view/PhabricatorAuthInviteActionTableView.php',
     'PhabricatorAuthInviteController' => 'applications/auth/controller/PhabricatorAuthInviteController.php',
     'PhabricatorAuthInviteDialogException' => 'applications/auth/exception/PhabricatorAuthInviteDialogException.php',
     'PhabricatorAuthInviteEngine' => 'applications/auth/engine/PhabricatorAuthInviteEngine.php',
     'PhabricatorAuthInviteException' => 'applications/auth/exception/PhabricatorAuthInviteException.php',
     'PhabricatorAuthInviteInvalidException' => 'applications/auth/exception/PhabricatorAuthInviteInvalidException.php',
     'PhabricatorAuthInviteLoginException' => 'applications/auth/exception/PhabricatorAuthInviteLoginException.php',
     'PhabricatorAuthInvitePHIDType' => 'applications/auth/phid/PhabricatorAuthInvitePHIDType.php',
     'PhabricatorAuthInviteQuery' => 'applications/auth/query/PhabricatorAuthInviteQuery.php',
     'PhabricatorAuthInviteRegisteredException' => 'applications/auth/exception/PhabricatorAuthInviteRegisteredException.php',
     'PhabricatorAuthInviteSearchEngine' => 'applications/auth/query/PhabricatorAuthInviteSearchEngine.php',
     'PhabricatorAuthInviteTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php',
     'PhabricatorAuthInviteVerifyException' => 'applications/auth/exception/PhabricatorAuthInviteVerifyException.php',
     'PhabricatorAuthInviteWorker' => 'applications/auth/worker/PhabricatorAuthInviteWorker.php',
     'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
     'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
     'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
     'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
+    'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php',
     'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php',
     'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
     'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
     'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php',
     'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php',
     'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php',
     'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php',
     'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php',
     'PhabricatorAuthManagementUnlimitWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlimitWorkflow.php',
     'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php',
     'PhabricatorAuthManagementVerifyWorkflow' => 'applications/auth/management/PhabricatorAuthManagementVerifyWorkflow.php',
     'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php',
     'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php',
     'PhabricatorAuthNeedsMultiFactorController' => 'applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php',
     'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php',
     'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
     'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php',
     'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php',
     'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php',
     'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
     'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
     'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
     'PhabricatorAuthProviderConfigEditor' => 'applications/auth/editor/PhabricatorAuthProviderConfigEditor.php',
     'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php',
     'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php',
     'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
     'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php',
     'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
     'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
     'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
     'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.php',
     'PhabricatorAuthSSHKeyDeleteController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php',
     'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php',
     'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
     'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
     'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php',
     'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php',
     'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php',
     'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
     'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
+    'PhabricatorAuthSessionEngineExtension' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtension.php',
+    'PhabricatorAuthSessionEngineExtensionModule' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php',
     'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
     'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php',
     'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php',
     'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php',
     'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php',
     'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php',
     'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php',
     'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php',
     'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php',
     'PhabricatorAuthTemporaryTokenTypeModule' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php',
     'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php',
     'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php',
     'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php',
     'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php',
     'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
     'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php',
     'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php',
     'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php',
     'PhabricatorBadgesArchiveController' => 'applications/badges/controller/PhabricatorBadgesArchiveController.php',
     'PhabricatorBadgesAward' => 'applications/badges/storage/PhabricatorBadgesAward.php',
     'PhabricatorBadgesAwardController' => 'applications/badges/controller/PhabricatorBadgesAwardController.php',
     'PhabricatorBadgesAwardQuery' => 'applications/badges/query/PhabricatorBadgesAwardQuery.php',
     'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php',
+    'PhabricatorBadgesBadgeNameNgrams' => 'applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php',
     'PhabricatorBadgesCommentController' => 'applications/badges/controller/PhabricatorBadgesCommentController.php',
     'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php',
     'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php',
     'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php',
     'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php',
+    'PhabricatorBadgesEditConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php',
     'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php',
     'PhabricatorBadgesEditEngine' => 'applications/badges/editor/PhabricatorBadgesEditEngine.php',
     'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php',
     'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php',
     'PhabricatorBadgesIconSet' => 'applications/badges/icon/PhabricatorBadgesIconSet.php',
     'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php',
     'PhabricatorBadgesMailReceiver' => 'applications/badges/mail/PhabricatorBadgesMailReceiver.php',
     'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php',
     'PhabricatorBadgesQuality' => 'applications/badges/constants/PhabricatorBadgesQuality.php',
     'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php',
     'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php',
     'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php',
     'PhabricatorBadgesReplyHandler' => 'applications/badges/mail/PhabricatorBadgesReplyHandler.php',
     'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php',
+    'PhabricatorBadgesSearchConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php',
     'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php',
     'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php',
     'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php',
     'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php',
     'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php',
     'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php',
     'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
     'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php',
     'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php',
     'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php',
     'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
     'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
     'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
     'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
     'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
     'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
     'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
     'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php',
     'PhabricatorBotFlowdockProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php',
     'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php',
     'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php',
     'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php',
     'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php',
     'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php',
     'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php',
     'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php',
     'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php',
     'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php',
     'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php',
     'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php',
     'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php',
     'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php',
     'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
     'PhabricatorCacheGeneralGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php',
     'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php',
     'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php',
     'PhabricatorCacheMarkupGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php',
     'PhabricatorCacheSchemaSpec' => 'applications/cache/storage/PhabricatorCacheSchemaSpec.php',
     'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php',
     'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php',
     'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php',
     'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php',
     'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php',
     'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php',
     'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php',
     'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php',
     'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
     'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
     'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php',
     'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
     'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
     'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php',
     'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php',
     'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php',
     'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php',
     'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php',
     'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php',
     'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php',
     'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php',
     'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php',
     'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php',
     'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php',
     'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php',
     'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php',
     'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php',
     'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php',
     'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
     'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
     'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
     'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php',
     'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php',
     'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php',
     'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php',
     'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php',
     'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php',
     'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php',
     'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php',
     'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
     'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
     'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
     'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
     'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php',
     'PhabricatorChatLogChannelQuery' => 'applications/chatlog/query/PhabricatorChatLogChannelQuery.php',
     'PhabricatorChatLogController' => 'applications/chatlog/controller/PhabricatorChatLogController.php',
     'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php',
     'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php',
     'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
     'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
     'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
     'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
+    'PhabricatorColumnsEditField' => 'applications/transactions/editfield/PhabricatorColumnsEditField.php',
     'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
     'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php',
     'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php',
     'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php',
     'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php',
     'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php',
     'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php',
     'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php',
     'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php',
     'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php',
     'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php',
     'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php',
     'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/PhabricatorConduitCertificateToken.php',
     'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/PhabricatorConduitConnectionLog.php',
     'PhabricatorConduitConsoleController' => 'applications/conduit/controller/PhabricatorConduitConsoleController.php',
     'PhabricatorConduitContentSource' => 'infrastructure/contentsource/PhabricatorConduitContentSource.php',
     'PhabricatorConduitController' => 'applications/conduit/controller/PhabricatorConduitController.php',
     'PhabricatorConduitDAO' => 'applications/conduit/storage/PhabricatorConduitDAO.php',
     'PhabricatorConduitEditField' => 'applications/transactions/editfield/PhabricatorConduitEditField.php',
     'PhabricatorConduitListController' => 'applications/conduit/controller/PhabricatorConduitListController.php',
     'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php',
     'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
     'PhabricatorConduitLogSearchEngine' => 'applications/conduit/query/PhabricatorConduitLogSearchEngine.php',
     'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
     'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
     'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
     'PhabricatorConduitResultInterface' => 'applications/conduit/interface/PhabricatorConduitResultInterface.php',
     'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
     'PhabricatorConduitSearchFieldSpecification' => 'applications/conduit/interface/PhabricatorConduitSearchFieldSpecification.php',
     'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
     'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
     'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
     'PhabricatorConduitTokenEditController' => 'applications/conduit/controller/PhabricatorConduitTokenEditController.php',
     'PhabricatorConduitTokenHandshakeController' => 'applications/conduit/controller/PhabricatorConduitTokenHandshakeController.php',
     'PhabricatorConduitTokenQuery' => 'applications/conduit/query/PhabricatorConduitTokenQuery.php',
     'PhabricatorConduitTokenTerminateController' => 'applications/conduit/controller/PhabricatorConduitTokenTerminateController.php',
     'PhabricatorConduitTokensSettingsPanel' => 'applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php',
     'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php',
     'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php',
     'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php',
     'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
     'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
     'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
     'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php',
     'PhabricatorConfigCoreSchemaSpec' => 'applications/config/schema/PhabricatorConfigCoreSchemaSpec.php',
     'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php',
     'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/PhabricatorConfigDatabaseIssueController.php',
     'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php',
     'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php',
     'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/PhabricatorConfigDatabaseStatusController.php',
     'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php',
     'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php',
     'PhabricatorConfigEdgeModule' => 'applications/config/module/PhabricatorConfigEdgeModule.php',
     'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php',
     'PhabricatorConfigEditor' => 'applications/config/editor/PhabricatorConfigEditor.php',
     'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php',
     'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php',
     'PhabricatorConfigEntryQuery' => 'applications/config/query/PhabricatorConfigEntryQuery.php',
     'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php',
     'PhabricatorConfigGroupController' => 'applications/config/controller/PhabricatorConfigGroupController.php',
     'PhabricatorConfigHTTPParameterTypesModule' => 'applications/config/module/PhabricatorConfigHTTPParameterTypesModule.php',
     'PhabricatorConfigHistoryController' => 'applications/config/controller/PhabricatorConfigHistoryController.php',
     'PhabricatorConfigIgnoreController' => 'applications/config/controller/PhabricatorConfigIgnoreController.php',
     'PhabricatorConfigIssueListController' => 'applications/config/controller/PhabricatorConfigIssueListController.php',
     'PhabricatorConfigIssueViewController' => 'applications/config/controller/PhabricatorConfigIssueViewController.php',
     'PhabricatorConfigJSON' => 'applications/config/json/PhabricatorConfigJSON.php',
     'PhabricatorConfigJSONOptionType' => 'applications/config/custom/PhabricatorConfigJSONOptionType.php',
     'PhabricatorConfigKeySchema' => 'applications/config/schema/PhabricatorConfigKeySchema.php',
     'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php',
     'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php',
     'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php',
     'PhabricatorConfigManagementGetWorkflow' => 'applications/config/management/PhabricatorConfigManagementGetWorkflow.php',
     'PhabricatorConfigManagementListWorkflow' => 'applications/config/management/PhabricatorConfigManagementListWorkflow.php',
     'PhabricatorConfigManagementMigrateWorkflow' => 'applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php',
     'PhabricatorConfigManagementSetWorkflow' => 'applications/config/management/PhabricatorConfigManagementSetWorkflow.php',
     'PhabricatorConfigManagementWorkflow' => 'applications/config/management/PhabricatorConfigManagementWorkflow.php',
     'PhabricatorConfigModule' => 'applications/config/module/PhabricatorConfigModule.php',
     'PhabricatorConfigModuleController' => 'applications/config/controller/PhabricatorConfigModuleController.php',
     'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php',
     'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php',
     'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php',
     'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
     'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php',
     'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
     'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
     'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
     'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
     'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php',
     'PhabricatorConfigSiteModule' => 'applications/config/module/PhabricatorConfigSiteModule.php',
     'PhabricatorConfigSiteSource' => 'infrastructure/env/PhabricatorConfigSiteSource.php',
     'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php',
     'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php',
     'PhabricatorConfigStorageSchema' => 'applications/config/schema/PhabricatorConfigStorageSchema.php',
     'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php',
     'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php',
     'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php',
     'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php',
     'PhabricatorConfigVersionsModule' => 'applications/config/module/PhabricatorConfigVersionsModule.php',
     'PhabricatorConfigWelcomeController' => 'applications/config/controller/PhabricatorConfigWelcomeController.php',
     'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php',
     'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php',
     'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php',
     'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php',
     'PhabricatorConsoleContentSource' => 'infrastructure/contentsource/PhabricatorConsoleContentSource.php',
     'PhabricatorContentSource' => 'infrastructure/contentsource/PhabricatorContentSource.php',
     'PhabricatorContentSourceModule' => 'infrastructure/contentsource/PhabricatorContentSourceModule.php',
     'PhabricatorContentSourceView' => 'infrastructure/contentsource/PhabricatorContentSourceView.php',
     'PhabricatorContributedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorContributedToObjectEdgeType.php',
     'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
     'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php',
     'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
     'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
     'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
-    'PhabricatorCountdownCommentController' => 'applications/countdown/controller/PhabricatorCountdownCommentController.php',
     'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
     'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php',
     'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php',
     'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php',
     'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php',
     'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php',
     'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php',
+    'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php',
     'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php',
     'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php',
     'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php',
     'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php',
     'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php',
     'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php',
     'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php',
     'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php',
     'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php',
     'PhabricatorCountdownTransactionComment' => 'applications/countdown/storage/PhabricatorCountdownTransactionComment.php',
     'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
     'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
     'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
     'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php',
     'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
     'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
     'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
     'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php',
     'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php',
     'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldEditEngineExtension.php',
     'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php',
     'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php',
     'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php',
     'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php',
     'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php',
     'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php',
     'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php',
     'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php',
     'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php',
     'PhabricatorCustomFieldMonogramParser' => 'infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php',
     'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php',
     'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php',
     'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php',
     'PhabricatorCustomFieldSearchEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldSearchEngineExtension.php',
     'PhabricatorCustomFieldStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php',
     'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php',
     'PhabricatorCustomHeaderConfigType' => 'applications/config/custom/PhabricatorCustomHeaderConfigType.php',
     'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php',
     'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php',
     'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php',
     'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php',
     'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php',
     'PhabricatorDaemonContentSource' => 'infrastructure/daemon/contentsource/PhabricatorDaemonContentSource.php',
     'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php',
     'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php',
     'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php',
     'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
     'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
     'PhabricatorDaemonLogEventGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php',
     'PhabricatorDaemonLogEventViewController' => 'applications/daemon/controller/PhabricatorDaemonLogEventViewController.php',
     'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/PhabricatorDaemonLogEventsView.php',
     'PhabricatorDaemonLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php',
     'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
     'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
     'PhabricatorDaemonLogQuery' => 'applications/daemon/query/PhabricatorDaemonLogQuery.php',
     'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/PhabricatorDaemonLogViewController.php',
     'PhabricatorDaemonManagementDebugWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php',
     'PhabricatorDaemonManagementLaunchWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLaunchWorkflow.php',
     'PhabricatorDaemonManagementListWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementListWorkflow.php',
     'PhabricatorDaemonManagementLogWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php',
     'PhabricatorDaemonManagementReloadWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementReloadWorkflow.php',
     'PhabricatorDaemonManagementRestartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php',
     'PhabricatorDaemonManagementStartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php',
     'PhabricatorDaemonManagementStatusWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStatusWorkflow.php',
     'PhabricatorDaemonManagementStopWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php',
     'PhabricatorDaemonManagementWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementWorkflow.php',
     'PhabricatorDaemonOverseerModule' => 'infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php',
     'PhabricatorDaemonReference' => 'infrastructure/daemon/control/PhabricatorDaemonReference.php',
     'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php',
     'PhabricatorDaemonTasksTableView' => 'applications/daemon/view/PhabricatorDaemonTasksTableView.php',
     'PhabricatorDaemonsApplication' => 'applications/daemon/application/PhabricatorDaemonsApplication.php',
     'PhabricatorDaemonsSetupCheck' => 'applications/config/check/PhabricatorDaemonsSetupCheck.php',
     'PhabricatorDailyRoutineTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php',
     'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
     'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
     'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
     'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardArchiveController.php',
     'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
     'PhabricatorDashboardCopyController' => 'applications/dashboard/controller/PhabricatorDashboardCopyController.php',
     'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php',
     'PhabricatorDashboardDashboardHasPanelEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php',
     'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php',
     'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php',
-    'PhabricatorDashboardHistoryController' => 'applications/dashboard/controller/PhabricatorDashboardHistoryController.php',
     'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php',
     'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php',
     'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
     'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
     'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php',
     'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php',
     'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php',
     'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php',
     'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php',
     'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php',
     'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php',
     'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php',
     'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php',
     'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php',
     'PhabricatorDashboardPanelQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelQuery.php',
     'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php',
     'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
     'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php',
     'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
     'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php',
     'PhabricatorDashboardPanelTabsCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php',
     'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
     'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
     'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
     'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php',
     'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php',
     'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php',
     'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php',
     'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php',
     'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php',
     'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php',
     'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php',
     'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php',
     'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php',
     'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php',
     'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php',
     'PhabricatorDashboardTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php',
     'PhabricatorDashboardTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardTransactionQuery.php',
     'PhabricatorDashboardUninstallController' => 'applications/dashboard/controller/PhabricatorDashboardUninstallController.php',
     'PhabricatorDashboardViewController' => 'applications/dashboard/controller/PhabricatorDashboardViewController.php',
     'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php',
     'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php',
     'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
     'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php',
     'PhabricatorDatasourceEditType' => 'applications/transactions/edittype/PhabricatorDatasourceEditType.php',
     'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
     'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
     'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
     'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php',
     'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
     'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
     'PhabricatorDestructionEngineExtension' => 'applications/system/engine/PhabricatorDestructionEngineExtension.php',
     'PhabricatorDestructionEngineExtensionModule' => 'applications/system/engine/PhabricatorDestructionEngineExtensionModule.php',
     'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php',
     'PhabricatorDeveloperPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDeveloperPreferencesSettingsPanel.php',
     'PhabricatorDiffInlineCommentQuery' => 'infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php',
     'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php',
     'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php',
     'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php',
     'PhabricatorDifferentialAttachCommitWorkflow' => 'applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php',
     'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php',
     'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php',
     'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php',
     'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php',
     'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php',
     'PhabricatorDiffusionConfigOptions' => 'applications/diffusion/config/PhabricatorDiffusionConfigOptions.php',
     'PhabricatorDisabledUserController' => 'applications/auth/controller/PhabricatorDisabledUserController.php',
     'PhabricatorDisplayPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDisplayPreferencesSettingsPanel.php',
     'PhabricatorDisqusAuthProvider' => 'applications/auth/provider/PhabricatorDisqusAuthProvider.php',
     'PhabricatorDividerProfilePanel' => 'applications/search/profilepanel/PhabricatorDividerProfilePanel.php',
     'PhabricatorDivinerApplication' => 'applications/diviner/application/PhabricatorDivinerApplication.php',
     'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php',
     'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php',
     'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
     'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
     'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
     'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php',
     'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php',
     'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php',
     'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php',
     'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
     'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
     'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php',
     'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php',
     'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php',
     'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php',
     'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php',
     'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php',
+    'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php',
     'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php',
     'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php',
     'PhabricatorEditEngineConfigurationDefaultCreateController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php',
     'PhabricatorEditEngineConfigurationDefaultsController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php',
     'PhabricatorEditEngineConfigurationDisableController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php',
     'PhabricatorEditEngineConfigurationEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php',
     'PhabricatorEditEngineConfigurationEditEngine' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php',
     'PhabricatorEditEngineConfigurationEditor' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php',
     'PhabricatorEditEngineConfigurationIsEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationIsEditController.php',
     'PhabricatorEditEngineConfigurationListController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php',
     'PhabricatorEditEngineConfigurationLockController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php',
     'PhabricatorEditEngineConfigurationPHIDType' => 'applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php',
     'PhabricatorEditEngineConfigurationQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php',
     'PhabricatorEditEngineConfigurationReorderController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php',
     'PhabricatorEditEngineConfigurationSaveController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php',
     'PhabricatorEditEngineConfigurationSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php',
     'PhabricatorEditEngineConfigurationSortController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php',
     'PhabricatorEditEngineConfigurationTransaction' => 'applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php',
     'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php',
     'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php',
     'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php',
     'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php',
     'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
     'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
     'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
     'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
     'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
     'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
     'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
     'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
     'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
     'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
     'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php',
     'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php',
     'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php',
     'PhabricatorEmailContentSource' => 'applications/metamta/contentsource/PhabricatorEmailContentSource.php',
     'PhabricatorEmailFormatSettingsPanel' => 'applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php',
     'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php',
     'PhabricatorEmailPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php',
     'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php',
     'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php',
     'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php',
     'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php',
     'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php',
     'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php',
+    'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php',
     'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php',
     'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
     'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php',
     'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
     'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
     'PhabricatorExecFutureFileUploadSource' => 'applications/files/uploadsource/PhabricatorExecFutureFileUploadSource.php',
     'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php',
     'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php',
     'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php',
     'PhabricatorExternalAccount' => 'applications/people/storage/PhabricatorExternalAccount.php',
     'PhabricatorExternalAccountQuery' => 'applications/auth/query/PhabricatorExternalAccountQuery.php',
     'PhabricatorExternalAccountsSettingsPanel' => 'applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php',
     'PhabricatorExtraConfigSetupCheck' => 'applications/config/check/PhabricatorExtraConfigSetupCheck.php',
     'PhabricatorFacebookAuthProvider' => 'applications/auth/provider/PhabricatorFacebookAuthProvider.php',
     'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
     'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
     'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
     'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
     'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
     'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
     'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
     'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php',
     'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php',
     'PhabricatorFactEngineTestCase' => 'applications/fact/engine/__tests__/PhabricatorFactEngineTestCase.php',
     'PhabricatorFactHomeController' => 'applications/fact/controller/PhabricatorFactHomeController.php',
     'PhabricatorFactLastUpdatedEngine' => 'applications/fact/engine/PhabricatorFactLastUpdatedEngine.php',
     'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php',
     'PhabricatorFactManagementCursorsWorkflow' => 'applications/fact/management/PhabricatorFactManagementCursorsWorkflow.php',
     'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php',
     'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php',
     'PhabricatorFactManagementStatusWorkflow' => 'applications/fact/management/PhabricatorFactManagementStatusWorkflow.php',
     'PhabricatorFactManagementWorkflow' => 'applications/fact/management/PhabricatorFactManagementWorkflow.php',
     'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php',
     'PhabricatorFactSimpleSpec' => 'applications/fact/spec/PhabricatorFactSimpleSpec.php',
     'PhabricatorFactSpec' => 'applications/fact/spec/PhabricatorFactSpec.php',
     'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php',
     'PhabricatorFaxContentSource' => 'infrastructure/contentsource/PhabricatorFaxContentSource.php',
     'PhabricatorFeedApplication' => 'applications/feed/application/PhabricatorFeedApplication.php',
     'PhabricatorFeedBuilder' => 'applications/feed/builder/PhabricatorFeedBuilder.php',
     'PhabricatorFeedConfigOptions' => 'applications/feed/config/PhabricatorFeedConfigOptions.php',
     'PhabricatorFeedController' => 'applications/feed/controller/PhabricatorFeedController.php',
     'PhabricatorFeedDAO' => 'applications/feed/storage/PhabricatorFeedDAO.php',
     'PhabricatorFeedDetailController' => 'applications/feed/controller/PhabricatorFeedDetailController.php',
     'PhabricatorFeedListController' => 'applications/feed/controller/PhabricatorFeedListController.php',
     'PhabricatorFeedManagementRepublishWorkflow' => 'applications/feed/management/PhabricatorFeedManagementRepublishWorkflow.php',
     'PhabricatorFeedManagementWorkflow' => 'applications/feed/management/PhabricatorFeedManagementWorkflow.php',
     'PhabricatorFeedQuery' => 'applications/feed/query/PhabricatorFeedQuery.php',
     'PhabricatorFeedSearchEngine' => 'applications/feed/query/PhabricatorFeedSearchEngine.php',
     'PhabricatorFeedStory' => 'applications/feed/story/PhabricatorFeedStory.php',
     'PhabricatorFeedStoryData' => 'applications/feed/storage/PhabricatorFeedStoryData.php',
     'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php',
     'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
     'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
     'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
-    'PhabricatorFileAccessTemporaryTokenType' => 'applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php',
     'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
     'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php',
     'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php',
     'PhabricatorFileChunkQuery' => 'applications/files/query/PhabricatorFileChunkQuery.php',
     'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php',
     'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php',
     'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php',
     'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php',
     'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
     'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
     'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
     'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
     'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php',
     'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php',
     'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php',
     'PhabricatorFileIconSetSelectController' => 'applications/files/controller/PhabricatorFileIconSetSelectController.php',
     'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php',
     'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
     'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
     'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
     'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
     'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
     'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php',
     'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php',
     'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php',
     'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
     'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
     'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php',
     'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php',
     'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
     'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php',
     'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php',
     'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php',
     'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php',
     'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php',
     'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php',
     'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
     'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php',
     'PhabricatorFileTransformTestCase' => 'applications/files/transform/__tests__/PhabricatorFileTransformTestCase.php',
     'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
     'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php',
     'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
     'PhabricatorFileUploadSource' => 'applications/files/uploadsource/PhabricatorFileUploadSource.php',
     'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php',
     'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php',
     'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php',
     'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php',
     'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php',
     'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php',
     'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php',
     'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php',
     'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
     'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
     'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php',
     'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
     'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php',
     'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php',
     'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php',
     'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php',
     'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php',
     'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php',
     'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php',
     'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php',
     'PhabricatorFlagDAO' => 'applications/flag/storage/PhabricatorFlagDAO.php',
     'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php',
     'PhabricatorFlagDestructionEngineExtension' => 'applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php',
     'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php',
     'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php',
     'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php',
     'PhabricatorFlagSearchEngine' => 'applications/flag/query/PhabricatorFlagSearchEngine.php',
     'PhabricatorFlagSelectControl' => 'applications/flag/view/PhabricatorFlagSelectControl.php',
     'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php',
     'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php',
     'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php',
     'PhabricatorFulltextEngine' => 'applications/search/index/PhabricatorFulltextEngine.php',
     'PhabricatorFulltextEngineExtension' => 'applications/search/index/PhabricatorFulltextEngineExtension.php',
     'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php',
     'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php',
     'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php',
     'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php',
     'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php',
     'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php',
     'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php',
     'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php',
     'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php',
     'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php',
     'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php',
     'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
     'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php',
     'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
     'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
     'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php',
     'PhabricatorHTTPParameterTypeTableView' => 'applications/config/view/PhabricatorHTTPParameterTypeTableView.php',
     'PhabricatorHandleList' => 'applications/phid/handle/pool/PhabricatorHandleList.php',
     'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
     'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php',
     'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php',
     'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php',
     'PhabricatorHandleRemarkupRule' => 'applications/phid/remarkup/PhabricatorHandleRemarkupRule.php',
     'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php',
     'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php',
     'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php',
     'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
     'PhabricatorHashTestCase' => 'infrastructure/util/__tests__/PhabricatorHashTestCase.php',
     'PhabricatorHelpApplication' => 'applications/help/application/PhabricatorHelpApplication.php',
     'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
     'PhabricatorHelpDocumentationController' => 'applications/help/controller/PhabricatorHelpDocumentationController.php',
     'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php',
     'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
     'PhabricatorHelpMainMenuBarExtension' => 'applications/help/extension/PhabricatorHelpMainMenuBarExtension.php',
     'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
     'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
     'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
     'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
     'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
     'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php',
     'PhabricatorHomePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorHomePreferencesSettingsPanel.php',
     'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php',
     'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php',
     'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php',
     'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php',
     'PhabricatorHunksManagementWorkflow' => 'applications/differential/management/PhabricatorHunksManagementWorkflow.php',
     'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php',
     'PhabricatorIDsSearchField' => 'applications/search/field/PhabricatorIDsSearchField.php',
     'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php',
     'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php',
     'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php',
     'PhabricatorIconSetEditField' => 'applications/transactions/editfield/PhabricatorIconSetEditField.php',
     'PhabricatorIconSetIcon' => 'applications/files/iconset/PhabricatorIconSetIcon.php',
     'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php',
     'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
     'PhabricatorImagemagickSetupCheck' => 'applications/config/check/PhabricatorImagemagickSetupCheck.php',
     'PhabricatorIndexEngine' => 'applications/search/index/PhabricatorIndexEngine.php',
     'PhabricatorIndexEngineExtension' => 'applications/search/index/PhabricatorIndexEngineExtension.php',
     'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php',
     'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php',
     'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php',
     'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php',
     'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php',
     'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php',
     'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php',
     'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php',
     'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php',
     'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php',
     'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php',
     'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php',
     'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
     'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
     'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
     'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
     'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
     'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
     'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php',
     'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php',
     'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
     'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php',
     'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php',
     'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php',
     'PhabricatorLinkProfilePanel' => 'applications/search/profilepanel/PhabricatorLinkProfilePanel.php',
     'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
     'PhabricatorLipsumContentSource' => 'infrastructure/contentsource/PhabricatorLipsumContentSource.php',
     'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
     'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
     'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
     'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
     'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php',
     'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php',
     'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php',
     'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php',
     'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
     'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
     'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php',
     'PhabricatorLocaleScopeGuardTestCase' => 'infrastructure/internationalization/scope/__tests__/PhabricatorLocaleScopeGuardTestCase.php',
     'PhabricatorLogTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorLogTriggerAction.php',
     'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
     'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php',
     'PhabricatorMacroApplication' => 'applications/macro/application/PhabricatorMacroApplication.php',
     'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php',
     'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php',
     'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php',
     'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php',
     'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php',
     'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php',
     'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php',
     'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php',
     'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php',
     'PhabricatorMacroMacroPHIDType' => 'applications/macro/phid/PhabricatorMacroMacroPHIDType.php',
     'PhabricatorMacroMailReceiver' => 'applications/macro/mail/PhabricatorMacroMailReceiver.php',
     'PhabricatorMacroManageCapability' => 'applications/macro/capability/PhabricatorMacroManageCapability.php',
     'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php',
     'PhabricatorMacroMemeDialogController' => 'applications/macro/controller/PhabricatorMacroMemeDialogController.php',
     'PhabricatorMacroQuery' => 'applications/macro/query/PhabricatorMacroQuery.php',
     'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php',
     'PhabricatorMacroSearchEngine' => 'applications/macro/query/PhabricatorMacroSearchEngine.php',
     'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php',
     'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php',
     'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php',
     'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php',
     'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php',
     'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php',
     'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php',
     'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
     'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
     'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php',
     'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php',
     'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php',
     'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
     'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php',
     'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php',
     'PhabricatorMailManagementListOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php',
     'PhabricatorMailManagementReceiveTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementReceiveTestWorkflow.php',
     'PhabricatorMailManagementResendWorkflow' => 'applications/metamta/management/PhabricatorMailManagementResendWorkflow.php',
     'PhabricatorMailManagementSendTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php',
     'PhabricatorMailManagementShowInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php',
     'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php',
     'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php',
     'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php',
     'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php',
     'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php',
     'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php',
     'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php',
     'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php',
     'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
     'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
     'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
     'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php',
     'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php',
     'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php',
     'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php',
     'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php',
     'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php',
     'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php',
     'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php',
     'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php',
     'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php',
     'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php',
     'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
     'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php',
     'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
     'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php',
     'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php',
     'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php',
     'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php',
     'PhabricatorMentionableInterface' => 'applications/transactions/interface/PhabricatorMentionableInterface.php',
     'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php',
     'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php',
     'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php',
     'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
     'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php',
     'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php',
     'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php',
     'PhabricatorMetaMTAApplicationEmailHeraldField' => 'applications/metamta/herald/PhabricatorMetaMTAApplicationEmailHeraldField.php',
     'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php',
     'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php',
     'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php',
     'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php',
     'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php',
     'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
     'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
     'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
     'PhabricatorMetaMTADAO' => 'applications/metamta/storage/PhabricatorMetaMTADAO.php',
     'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php',
     'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php',
     'PhabricatorMetaMTAEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php',
     'PhabricatorMetaMTAEmailOthersHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php',
     'PhabricatorMetaMTAEmailSelfHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php',
     'PhabricatorMetaMTAErrorMailAction' => 'applications/metamta/action/PhabricatorMetaMTAErrorMailAction.php',
     'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php',
     'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
     'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
     'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php',
     'PhabricatorMetaMTAMailListController' => 'applications/metamta/controller/PhabricatorMetaMTAMailListController.php',
     'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php',
     'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php',
     'PhabricatorMetaMTAMailSearchEngine' => 'applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php',
     'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php',
     'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
     'PhabricatorMetaMTAMailViewController' => 'applications/metamta/controller/PhabricatorMetaMTAMailViewController.php',
     'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php',
     'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php',
     'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
     'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
     'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
     'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
     'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php',
     'PhabricatorMetaMTAReceivedMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php',
     'PhabricatorMetaMTASchemaSpec' => 'applications/metamta/storage/PhabricatorMetaMTASchemaSpec.php',
     'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php',
     'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php',
     'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
     'PhabricatorMotivatorProfilePanel' => 'applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php',
     'PhabricatorMultiColumnUIExample' => 'applications/uiexample/examples/PhabricatorMultiColumnUIExample.php',
     'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php',
     'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php',
     'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php',
     'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php',
     'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
     'PhabricatorMySQLFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php',
     'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php',
     'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php',
     'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
     'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
     'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php',
     'PhabricatorNgramsIndexEngineExtension' => 'applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php',
     'PhabricatorNgramsInterface' => 'applications/search/interface/PhabricatorNgramsInterface.php',
     'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
     'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php',
     'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php',
     'PhabricatorNotificationConfigOptions' => 'applications/config/option/PhabricatorNotificationConfigOptions.php',
     'PhabricatorNotificationController' => 'applications/notification/controller/PhabricatorNotificationController.php',
     'PhabricatorNotificationDestructionEngineExtension' => 'applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php',
     'PhabricatorNotificationIndividualController' => 'applications/notification/controller/PhabricatorNotificationIndividualController.php',
     'PhabricatorNotificationListController' => 'applications/notification/controller/PhabricatorNotificationListController.php',
     'PhabricatorNotificationPanelController' => 'applications/notification/controller/PhabricatorNotificationPanelController.php',
     'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php',
     'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php',
     'PhabricatorNotificationStatusController' => 'applications/notification/controller/PhabricatorNotificationStatusController.php',
     'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php',
     'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php',
     'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php',
     'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php',
     'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php',
     'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php',
     'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php',
     'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php',
     'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php',
     'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php',
     'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php',
     'PhabricatorOAuthClientAuthorizationQuery' => 'applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php',
     'PhabricatorOAuthClientController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientController.php',
-    'PhabricatorOAuthClientDeleteController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php',
+    'PhabricatorOAuthClientDisableController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php',
     'PhabricatorOAuthClientEditController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php',
     'PhabricatorOAuthClientListController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php',
     'PhabricatorOAuthClientSecretController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php',
+    'PhabricatorOAuthClientTestController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php',
     'PhabricatorOAuthClientViewController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php',
     'PhabricatorOAuthResponse' => 'applications/oauthserver/PhabricatorOAuthResponse.php',
     'PhabricatorOAuthServer' => 'applications/oauthserver/PhabricatorOAuthServer.php',
     'PhabricatorOAuthServerAccessToken' => 'applications/oauthserver/storage/PhabricatorOAuthServerAccessToken.php',
     'PhabricatorOAuthServerApplication' => 'applications/oauthserver/application/PhabricatorOAuthServerApplication.php',
     'PhabricatorOAuthServerAuthController' => 'applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php',
     'PhabricatorOAuthServerAuthorizationCode' => 'applications/oauthserver/storage/PhabricatorOAuthServerAuthorizationCode.php',
     'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php',
     'PhabricatorOAuthServerClient' => 'applications/oauthserver/storage/PhabricatorOAuthServerClient.php',
     'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientAuthorizationPHIDType.php',
     'PhabricatorOAuthServerClientPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientPHIDType.php',
     'PhabricatorOAuthServerClientQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php',
     'PhabricatorOAuthServerClientSearchEngine' => 'applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php',
     'PhabricatorOAuthServerController' => 'applications/oauthserver/controller/PhabricatorOAuthServerController.php',
     'PhabricatorOAuthServerCreateClientsCapability' => 'applications/oauthserver/capability/PhabricatorOAuthServerCreateClientsCapability.php',
     'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php',
+    'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php',
+    'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php',
     'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php',
     'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php',
-    'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTestController.php',
     'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php',
+    'PhabricatorOAuthServerTransaction' => 'applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php',
+    'PhabricatorOAuthServerTransactionQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php',
     'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php',
     'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php',
     'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php',
     'PhabricatorObjectHasContributorEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasContributorEdgeType.php',
     'PhabricatorObjectHasFileEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php',
     'PhabricatorObjectHasJiraIssueEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasJiraIssueEdgeType.php',
     'PhabricatorObjectHasSubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasSubscriberEdgeType.php',
     'PhabricatorObjectHasUnsubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasUnsubscriberEdgeType.php',
     'PhabricatorObjectHasWatcherEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasWatcherEdgeType.php',
     'PhabricatorObjectListQuery' => 'applications/phid/query/PhabricatorObjectListQuery.php',
     'PhabricatorObjectListQueryTestCase' => 'applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php',
     'PhabricatorObjectMailReceiver' => 'applications/metamta/receiver/PhabricatorObjectMailReceiver.php',
     'PhabricatorObjectMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php',
     'PhabricatorObjectMentionedByObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionedByObjectEdgeType.php',
     'PhabricatorObjectMentionsObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionsObjectEdgeType.php',
     'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
     'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
     'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
     'PhabricatorObjectUsesCredentialsEdgeType' => 'applications/transactions/edges/PhabricatorObjectUsesCredentialsEdgeType.php',
     'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
     'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php',
     'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php',
     'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php',
     'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php',
     'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php',
     'PhabricatorOwnersArchiveController' => 'applications/owners/controller/PhabricatorOwnersArchiveController.php',
     'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php',
     'PhabricatorOwnersConfiguredCustomField' => 'applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php',
     'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php',
     'PhabricatorOwnersCustomField' => 'applications/owners/customfield/PhabricatorOwnersCustomField.php',
     'PhabricatorOwnersCustomFieldNumericIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldNumericIndex.php',
     'PhabricatorOwnersCustomFieldStorage' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStorage.php',
     'PhabricatorOwnersCustomFieldStringIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStringIndex.php',
     'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php',
     'PhabricatorOwnersDefaultEditCapability' => 'applications/owners/capability/PhabricatorOwnersDefaultEditCapability.php',
     'PhabricatorOwnersDefaultViewCapability' => 'applications/owners/capability/PhabricatorOwnersDefaultViewCapability.php',
     'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php',
     'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php',
     'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php',
     'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
     'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
     'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php',
     'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php',
     'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php',
     'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php',
     'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php',
     'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
     'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
     'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
     'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
     'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
     'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php',
     'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php',
     'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php',
     'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php',
     'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php',
     'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php',
     'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php',
     'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php',
     'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
     'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
     'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
     'PhabricatorPHIDInterface' => 'applications/phid/interface/PhabricatorPHIDInterface.php',
     'PhabricatorPHIDListEditField' => 'applications/transactions/editfield/PhabricatorPHIDListEditField.php',
     'PhabricatorPHIDListEditType' => 'applications/transactions/edittype/PhabricatorPHIDListEditType.php',
     'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php',
     'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php',
     'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php',
     'PhabricatorPHIDsSearchField' => 'applications/search/field/PhabricatorPHIDsSearchField.php',
     'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php',
     'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php',
     'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php',
     'PhabricatorPagedFormUIExample' => 'applications/uiexample/examples/PhabricatorPagedFormUIExample.php',
     'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php',
     'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php',
     'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php',
     'PhabricatorPasswordHasher' => 'infrastructure/util/password/PhabricatorPasswordHasher.php',
     'PhabricatorPasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorPasswordHasherTestCase.php',
     'PhabricatorPasswordHasherUnavailableException' => 'infrastructure/util/password/PhabricatorPasswordHasherUnavailableException.php',
     'PhabricatorPasswordSettingsPanel' => 'applications/settings/panel/PhabricatorPasswordSettingsPanel.php',
     'PhabricatorPaste' => 'applications/paste/storage/PhabricatorPaste.php',
     'PhabricatorPasteApplication' => 'applications/paste/application/PhabricatorPasteApplication.php',
     'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php',
     'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php',
     'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php',
     'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
     'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
     'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
     'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
     'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
     'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php',
     'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
     'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
     'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
     'PhabricatorPasteRawController' => 'applications/paste/controller/PhabricatorPasteRawController.php',
     'PhabricatorPasteRemarkupRule' => 'applications/paste/remarkup/PhabricatorPasteRemarkupRule.php',
     'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
     'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
     'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php',
     'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php',
     'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php',
     'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
     'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
     'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
     'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php',
     'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
     'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
     'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
     'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php',
     'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
     'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
     'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
     'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php',
     'PhabricatorPeopleDetailsProfilePanel' => 'applications/people/profilepanel/PhabricatorPeopleDetailsProfilePanel.php',
     'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
     'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php',
     'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php',
     'PhabricatorPeopleIconSet' => 'applications/people/icon/PhabricatorPeopleIconSet.php',
     'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
     'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php',
     'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php',
     'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php',
     'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php',
     'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php',
     'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php',
     'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php',
     'PhabricatorPeopleMainMenuBarExtension' => 'applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php',
     'PhabricatorPeopleManageProfilePanel' => 'applications/people/profilepanel/PhabricatorPeopleManageProfilePanel.php',
     'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php',
     'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php',
     'PhabricatorPeopleOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleOwnerDatasource.php',
     'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php',
     'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php',
     'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php',
     'PhabricatorPeopleProfilePanelEngine' => 'applications/people/engine/PhabricatorPeopleProfilePanelEngine.php',
     'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php',
     'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php',
     'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php',
     'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php',
     'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php',
     'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php',
     'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php',
     'PhabricatorPeopleUserFunctionDatasource' => 'applications/people/typeahead/PhabricatorPeopleUserFunctionDatasource.php',
     'PhabricatorPeopleUserPHIDType' => 'applications/people/phid/PhabricatorPeopleUserPHIDType.php',
     'PhabricatorPeopleWelcomeController' => 'applications/people/controller/PhabricatorPeopleWelcomeController.php',
     'PhabricatorPersonaAuthProvider' => 'applications/auth/provider/PhabricatorPersonaAuthProvider.php',
     'PhabricatorPhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorPhabricatorAuthProvider.php',
     'PhabricatorPhameApplication' => 'applications/phame/application/PhabricatorPhameApplication.php',
     'PhabricatorPhameBlogPHIDType' => 'applications/phame/phid/PhabricatorPhameBlogPHIDType.php',
     'PhabricatorPhamePostPHIDType' => 'applications/phame/phid/PhabricatorPhamePostPHIDType.php',
     'PhabricatorPhluxApplication' => 'applications/phlux/application/PhabricatorPhluxApplication.php',
     'PhabricatorPholioApplication' => 'applications/pholio/application/PhabricatorPholioApplication.php',
     'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php',
     'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php',
     'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php',
     'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php',
     'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php',
     'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php',
     'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php',
     'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php',
     'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php',
     'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php',
     'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php',
     'PhabricatorPhurlApplication' => 'applications/phurl/application/PhabricatorPhurlApplication.php',
     'PhabricatorPhurlConfigOptions' => 'applications/config/option/PhabricatorPhurlConfigOptions.php',
     'PhabricatorPhurlController' => 'applications/phurl/controller/PhabricatorPhurlController.php',
     'PhabricatorPhurlDAO' => 'applications/phurl/storage/PhabricatorPhurlDAO.php',
     'PhabricatorPhurlLinkRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php',
     'PhabricatorPhurlRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlRemarkupRule.php',
     'PhabricatorPhurlSchemaSpec' => 'applications/phurl/storage/PhabricatorPhurlSchemaSpec.php',
     'PhabricatorPhurlShortURLController' => 'applications/phurl/controller/PhabricatorPhurlShortURLController.php',
     'PhabricatorPhurlShortURLDefaultController' => 'applications/phurl/controller/PhabricatorPhurlShortURLDefaultController.php',
     'PhabricatorPhurlURL' => 'applications/phurl/storage/PhabricatorPhurlURL.php',
     'PhabricatorPhurlURLAccessController' => 'applications/phurl/controller/PhabricatorPhurlURLAccessController.php',
     'PhabricatorPhurlURLCommentController' => 'applications/phurl/controller/PhabricatorPhurlURLCommentController.php',
     'PhabricatorPhurlURLCreateCapability' => 'applications/phurl/capability/PhabricatorPhurlURLCreateCapability.php',
     'PhabricatorPhurlURLEditController' => 'applications/phurl/controller/PhabricatorPhurlURLEditController.php',
     'PhabricatorPhurlURLEditor' => 'applications/phurl/editor/PhabricatorPhurlURLEditor.php',
     'PhabricatorPhurlURLListController' => 'applications/phurl/controller/PhabricatorPhurlURLListController.php',
     'PhabricatorPhurlURLMailReceiver' => 'applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php',
     'PhabricatorPhurlURLPHIDType' => 'applications/phurl/phid/PhabricatorPhurlURLPHIDType.php',
     'PhabricatorPhurlURLQuery' => 'applications/phurl/query/PhabricatorPhurlURLQuery.php',
     'PhabricatorPhurlURLReplyHandler' => 'applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php',
     'PhabricatorPhurlURLSearchEngine' => 'applications/phurl/query/PhabricatorPhurlURLSearchEngine.php',
     'PhabricatorPhurlURLTransaction' => 'applications/phurl/storage/PhabricatorPhurlURLTransaction.php',
     'PhabricatorPhurlURLTransactionComment' => 'applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php',
     'PhabricatorPhurlURLTransactionQuery' => 'applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php',
     'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php',
     'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php',
     'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
     'PhabricatorPointsEditField' => 'applications/transactions/editfield/PhabricatorPointsEditField.php',
     'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
     'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
     'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php',
     'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php',
     'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php',
     'PhabricatorPolicyCanEditCapability' => 'applications/policy/capability/PhabricatorPolicyCanEditCapability.php',
     'PhabricatorPolicyCanJoinCapability' => 'applications/policy/capability/PhabricatorPolicyCanJoinCapability.php',
     'PhabricatorPolicyCanViewCapability' => 'applications/policy/capability/PhabricatorPolicyCanViewCapability.php',
     'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
     'PhabricatorPolicyCapabilityTestCase' => 'applications/policy/capability/__tests__/PhabricatorPolicyCapabilityTestCase.php',
     'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
     'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
     'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
     'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php',
     'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php',
     'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php',
     'PhabricatorPolicyEditEngineExtension' => 'applications/policy/editor/PhabricatorPolicyEditEngineExtension.php',
     'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php',
     'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php',
     'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php',
     'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php',
     'PhabricatorPolicyInterface' => 'applications/policy/interface/PhabricatorPolicyInterface.php',
     'PhabricatorPolicyManagementShowWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php',
     'PhabricatorPolicyManagementUnlockWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementUnlockWorkflow.php',
     'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
     'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
     'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
     'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
     'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
     'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
     'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
     'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
     'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
     'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php',
     'PhabricatorProfilePanel' => 'applications/search/profilepanel/PhabricatorProfilePanel.php',
     'PhabricatorProfilePanelConfiguration' => 'applications/search/storage/PhabricatorProfilePanelConfiguration.php',
     'PhabricatorProfilePanelConfigurationQuery' => 'applications/search/query/PhabricatorProfilePanelConfigurationQuery.php',
     'PhabricatorProfilePanelConfigurationTransaction' => 'applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php',
     'PhabricatorProfilePanelConfigurationTransactionQuery' => 'applications/search/query/PhabricatorProfilePanelConfigurationTransactionQuery.php',
     'PhabricatorProfilePanelEditEngine' => 'applications/search/editor/PhabricatorProfilePanelEditEngine.php',
     'PhabricatorProfilePanelEditor' => 'applications/search/editor/PhabricatorProfilePanelEditor.php',
     'PhabricatorProfilePanelEngine' => 'applications/search/engine/PhabricatorProfilePanelEngine.php',
     'PhabricatorProfilePanelIconSet' => 'applications/search/profilepanel/PhabricatorProfilePanelIconSet.php',
     'PhabricatorProfilePanelPHIDType' => 'applications/search/phidtype/PhabricatorProfilePanelPHIDType.php',
     'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
     'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
     'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
     'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
     'PhabricatorProjectBoardBackgroundController' => 'applications/project/controller/PhabricatorProjectBoardBackgroundController.php',
     'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
     'PhabricatorProjectBoardDisableController' => 'applications/project/controller/PhabricatorProjectBoardDisableController.php',
     'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
     'PhabricatorProjectBoardManageController' => 'applications/project/controller/PhabricatorProjectBoardManageController.php',
     'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
     'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
     'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php',
     'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php',
     'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
     'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
     'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
     'PhabricatorProjectColumnHideController' => 'applications/project/controller/PhabricatorProjectColumnHideController.php',
     'PhabricatorProjectColumnPHIDType' => 'applications/project/phid/PhabricatorProjectColumnPHIDType.php',
     'PhabricatorProjectColumnPosition' => 'applications/project/storage/PhabricatorProjectColumnPosition.php',
     'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php',
     'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php',
     'PhabricatorProjectColumnTransaction' => 'applications/project/storage/PhabricatorProjectColumnTransaction.php',
     'PhabricatorProjectColumnTransactionEditor' => 'applications/project/editor/PhabricatorProjectColumnTransactionEditor.php',
     'PhabricatorProjectColumnTransactionQuery' => 'applications/project/query/PhabricatorProjectColumnTransactionQuery.php',
     'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php',
     'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
     'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
     'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
     'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php',
     'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
     'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
     'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
     'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php',
     'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
     'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
     'PhabricatorProjectDefaultController' => 'applications/project/controller/PhabricatorProjectDefaultController.php',
     'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
     'PhabricatorProjectDetailsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php',
     'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
     'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php',
     'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
     'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php',
     'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php',
     'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php',
     'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php',
     'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php',
     'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php',
     'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php',
     'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php',
     'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php',
     'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php',
     'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php',
     'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php',
     'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php',
     'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php',
     'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
     'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
     'PhabricatorProjectManageController' => 'applications/project/controller/PhabricatorProjectManageController.php',
     'PhabricatorProjectManageProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectManageProfilePanel.php',
     'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php',
     'PhabricatorProjectMemberListView' => 'applications/project/view/PhabricatorProjectMemberListView.php',
     'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php',
     'PhabricatorProjectMembersAddController' => 'applications/project/controller/PhabricatorProjectMembersAddController.php',
     'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
     'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
     'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
     'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
     'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php',
     'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
     'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php',
     'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php',
     'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php',
     'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
     'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
     'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
     'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
     'PhabricatorProjectPointsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php',
     'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
     'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php',
     'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
     'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
     'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php',
     'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php',
     'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php',
     'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php',
     'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php',
     'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php',
     'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php',
     'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php',
     'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php',
     'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php',
     'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php',
     'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
     'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
     'PhabricatorProjectSubprojectsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php',
     'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
     'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
     'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
     'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php',
     'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
     'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
     'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
     'PhabricatorProjectUserListView' => 'applications/project/view/PhabricatorProjectUserListView.php',
     'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
     'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
     'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php',
     'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php',
     'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php',
     'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php',
     'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
     'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
     'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
     'PhabricatorProjectsMembersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsMembersSearchEngineAttachment.php',
     'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php',
     'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php',
     'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php',
     'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php',
     'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php',
     'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php',
     'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
     'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
     'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php',
     'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php',
     'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
     'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
     'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php',
     'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
     'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php',
     'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
     'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
     'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php',
     'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php',
     'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php',
     'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php',
     'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php',
     'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php',
     'PhabricatorRemarkupCustomInlineRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomInlineRule.php',
     'PhabricatorRemarkupEditField' => 'applications/transactions/editfield/PhabricatorRemarkupEditField.php',
     'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php',
     'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php',
     'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php',
     'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php',
     'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php',
     'PhabricatorRepositoryBranch' => 'applications/repository/storage/PhabricatorRepositoryBranch.php',
     'PhabricatorRepositoryCommit' => 'applications/repository/storage/PhabricatorRepositoryCommit.php',
     'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php',
     'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php',
     'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php',
     'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php',
     'PhabricatorRepositoryCommitOwnersWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php',
     'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php',
     'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php',
     'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php',
     'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php',
     'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php',
     'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php',
     'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php',
     'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php',
     'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php',
     'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php',
     'PhabricatorRepositoryGitLFSRef' => 'applications/repository/storage/PhabricatorRepositoryGitLFSRef.php',
     'PhabricatorRepositoryGitLFSRefQuery' => 'applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php',
     'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php',
     'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php',
     'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
     'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
     'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php',
     'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
     'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
     'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
     'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
     'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
     'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php',
     'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php',
     'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php',
     'PhabricatorRepositoryManagementPullWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php',
     'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php',
     'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php',
     'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php',
     'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php',
     'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php',
     'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php',
     'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php',
     'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php',
     'PhabricatorRepositoryMirrorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php',
     'PhabricatorRepositoryMirrorQuery' => 'applications/repository/query/PhabricatorRepositoryMirrorQuery.php',
     'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php',
     'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php',
     'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php',
     'PhabricatorRepositoryPullEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php',
     'PhabricatorRepositoryPullEventQuery' => 'applications/repository/query/PhabricatorRepositoryPullEventQuery.php',
     'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php',
     'PhabricatorRepositoryPushEvent' => 'applications/repository/storage/PhabricatorRepositoryPushEvent.php',
     'PhabricatorRepositoryPushEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushEventPHIDType.php',
     'PhabricatorRepositoryPushEventQuery' => 'applications/repository/query/PhabricatorRepositoryPushEventQuery.php',
     'PhabricatorRepositoryPushLog' => 'applications/repository/storage/PhabricatorRepositoryPushLog.php',
     'PhabricatorRepositoryPushLogPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushLogPHIDType.php',
     'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php',
     'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php',
     'PhabricatorRepositoryPushMailWorker' => 'applications/repository/worker/PhabricatorRepositoryPushMailWorker.php',
     'PhabricatorRepositoryPushReplyHandler' => 'applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php',
     'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php',
     'PhabricatorRepositoryRefCursor' => 'applications/repository/storage/PhabricatorRepositoryRefCursor.php',
     'PhabricatorRepositoryRefCursorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRefCursorPHIDType.php',
     'PhabricatorRepositoryRefCursorQuery' => 'applications/repository/query/PhabricatorRepositoryRefCursorQuery.php',
     'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php',
     'PhabricatorRepositoryRepositoryPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php',
     'PhabricatorRepositorySchemaSpec' => 'applications/repository/storage/PhabricatorRepositorySchemaSpec.php',
     'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php',
     'PhabricatorRepositoryStatusMessage' => 'applications/repository/storage/PhabricatorRepositoryStatusMessage.php',
     'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php',
     'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositorySvnCommitMessageParserWorker.php',
     'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php',
     'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php',
     'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
     'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
     'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
     'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
     'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
     'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
     'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
     'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
     'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
     'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php',
     'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php',
     'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php',
     'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
     'PhabricatorSMS' => 'infrastructure/sms/storage/PhabricatorSMS.php',
     'PhabricatorSMSConfigOptions' => 'applications/config/option/PhabricatorSMSConfigOptions.php',
     'PhabricatorSMSDAO' => 'infrastructure/sms/storage/PhabricatorSMSDAO.php',
     'PhabricatorSMSDemultiplexWorker' => 'infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php',
     'PhabricatorSMSImplementationAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php',
     'PhabricatorSMSImplementationTestBlackholeAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php',
     'PhabricatorSMSImplementationTwilioAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php',
     'PhabricatorSMSManagementListOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php',
     'PhabricatorSMSManagementSendTestWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php',
     'PhabricatorSMSManagementShowOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php',
     'PhabricatorSMSManagementWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php',
     'PhabricatorSMSSendWorker' => 'infrastructure/sms/worker/PhabricatorSMSSendWorker.php',
     'PhabricatorSMSWorker' => 'infrastructure/sms/worker/PhabricatorSMSWorker.php',
     'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
     'PhabricatorSSHKeyGenerator' => 'infrastructure/util/PhabricatorSSHKeyGenerator.php',
     'PhabricatorSSHKeysSettingsPanel' => 'applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php',
     'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php',
     'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php',
     'PhabricatorSSHPublicKeyInterface' => 'applications/auth/sshkey/PhabricatorSSHPublicKeyInterface.php',
     'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
     'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php',
     'PhabricatorSavedQueryQuery' => 'applications/search/query/PhabricatorSavedQueryQuery.php',
     'PhabricatorScheduleTaskTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php',
     'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php',
     'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php',
     'PhabricatorSearchApplication' => 'applications/search/application/PhabricatorSearchApplication.php',
     'PhabricatorSearchApplicationSearchEngine' => 'applications/search/query/PhabricatorSearchApplicationSearchEngine.php',
     'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php',
     'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php',
     'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
     'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php',
     'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php',
     'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php',
     'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php',
     'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php',
     'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php',
     'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php',
     'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php',
     'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php',
     'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php',
     'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php',
     'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php',
     'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php',
     'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php',
     'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php',
     'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php',
     'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php',
     'PhabricatorSearchEngineAPIMethod' => 'applications/search/engine/PhabricatorSearchEngineAPIMethod.php',
     'PhabricatorSearchEngineAttachment' => 'applications/search/engineextension/PhabricatorSearchEngineAttachment.php',
     'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php',
     'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php',
     'PhabricatorSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php',
     'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php',
     'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php',
     'PhabricatorSearchIndexVersion' => 'applications/search/storage/PhabricatorSearchIndexVersion.php',
     'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php',
     'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php',
     'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php',
     'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php',
     'PhabricatorSearchNgrams' => 'applications/search/ngrams/PhabricatorSearchNgrams.php',
     'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php',
     'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php',
     'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
     'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php',
     'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
     'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
     'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
     'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
     'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
     'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php',
     'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php',
     'PhabricatorSearchThreeStateField' => 'applications/search/field/PhabricatorSearchThreeStateField.php',
     'PhabricatorSearchTokenizerField' => 'applications/search/field/PhabricatorSearchTokenizerField.php',
     'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php',
     'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php',
     'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php',
     'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php',
     'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php',
     'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php',
     'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php',
     'PhabricatorSettingsAdjustController' => 'applications/settings/controller/PhabricatorSettingsAdjustController.php',
     'PhabricatorSettingsApplication' => 'applications/settings/application/PhabricatorSettingsApplication.php',
     'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php',
     'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php',
     'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php',
     'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
     'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php',
     'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
     'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php',
     'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php',
     'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php',
     'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php',
     'PhabricatorSite' => 'aphront/site/PhabricatorSite.php',
     'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php',
     'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
     'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.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',
     'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php',
     'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php',
     'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php',
     'PhabricatorSlowvoteMailReceiver' => 'applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php',
     'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/PhabricatorSlowvoteOption.php',
     'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/PhabricatorSlowvotePoll.php',
     'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php',
     'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php',
     'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php',
     'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php',
     'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php',
     'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php',
     'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php',
     'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php',
     'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php',
     'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php',
     'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php',
     'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php',
     'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php',
     'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
     'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php',
     'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
     'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
     'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
     'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
     'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
     'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php',
     'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php',
     'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php',
     'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php',
     'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php',
     'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php',
     'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php',
     'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php',
     'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php',
     'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php',
     'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php',
     'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php',
     'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php',
     'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php',
     'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php',
     'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php',
     'PhabricatorSpacesSearchEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php',
     'PhabricatorSpacesSearchField' => 'applications/spaces/searchfield/PhabricatorSpacesSearchField.php',
     'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php',
     'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php',
     'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php',
     'PhabricatorStandardCustomFieldBlueprints' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php',
     'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php',
     'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php',
     'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php',
     'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php',
     'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php',
     'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php',
     'PhabricatorStandardCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorStandardCustomFieldInterface.php',
     'PhabricatorStandardCustomFieldLink' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php',
     'PhabricatorStandardCustomFieldPHIDs' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php',
     'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php',
     'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php',
     'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php',
     'PhabricatorStandardCustomFieldTokenizer' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php',
     'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
     'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
     'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
     'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php',
     'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
     'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php',
     'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php',
     'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php',
     'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php',
     'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php',
     'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php',
     'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php',
     'PhabricatorStorageManagementProbeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php',
     'PhabricatorStorageManagementQuickstartWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php',
     'PhabricatorStorageManagementRenamespaceWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php',
     'PhabricatorStorageManagementShellWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php',
     'PhabricatorStorageManagementStatusWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php',
     'PhabricatorStorageManagementUpgradeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php',
     'PhabricatorStorageManagementWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php',
     'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php',
     'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php',
     'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php',
     'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php',
     'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php',
     'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
     'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php',
     'PhabricatorSubscribersEditField' => 'applications/transactions/editfield/PhabricatorSubscribersEditField.php',
     'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php',
     'PhabricatorSubscriptionTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorSubscriptionTriggerClock.php',
     'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php',
     'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php',
     'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php',
     'PhabricatorSubscriptionsCurtainExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php',
     'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
     'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php',
     'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php',
     'PhabricatorSubscriptionsFulltextEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php',
     'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php',
     'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php',
     'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php',
     'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php',
     'PhabricatorSubscriptionsSearchEngineAttachment' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineAttachment.php',
     'PhabricatorSubscriptionsSearchEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineExtension.php',
     'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php',
     'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php',
     'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php',
     'PhabricatorSubscriptionsUIEventListener' => 'applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php',
     'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsUnsubscribeEmailCommand.php',
     'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php',
     'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php',
     'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php',
     'PhabricatorSystemAction' => 'applications/system/action/PhabricatorSystemAction.php',
     'PhabricatorSystemActionEngine' => 'applications/system/engine/PhabricatorSystemActionEngine.php',
     'PhabricatorSystemActionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php',
     'PhabricatorSystemActionLog' => 'applications/system/storage/PhabricatorSystemActionLog.php',
     'PhabricatorSystemActionRateLimitException' => 'applications/system/exception/PhabricatorSystemActionRateLimitException.php',
     'PhabricatorSystemApplication' => 'applications/system/application/PhabricatorSystemApplication.php',
     'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php',
     'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php',
     'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php',
     'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php',
     'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php',
     'PhabricatorSystemRemoveWorkflow' => 'applications/system/management/PhabricatorSystemRemoveWorkflow.php',
     'PhabricatorSystemSelectEncodingController' => 'applications/system/controller/PhabricatorSystemSelectEncodingController.php',
     'PhabricatorSystemSelectHighlightController' => 'applications/system/controller/PhabricatorSystemSelectHighlightController.php',
     'PhabricatorTOTPAuthFactor' => 'applications/auth/factor/PhabricatorTOTPAuthFactor.php',
     'PhabricatorTOTPAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorTOTPAuthFactorTestCase.php',
     'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php',
     'PhabricatorTestApplication' => 'applications/base/controller/__tests__/PhabricatorTestApplication.php',
     'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php',
     'PhabricatorTestController' => 'applications/base/controller/__tests__/PhabricatorTestController.php',
     'PhabricatorTestDataGenerator' => 'applications/lipsum/generator/PhabricatorTestDataGenerator.php',
     'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php',
     'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php',
     'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
     'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php',
     'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php',
     'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php',
     'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php',
     'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php',
     'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php',
     'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php',
     'PhabricatorTokenController' => 'applications/tokens/controller/PhabricatorTokenController.php',
     'PhabricatorTokenCount' => 'applications/tokens/storage/PhabricatorTokenCount.php',
     'PhabricatorTokenCountQuery' => 'applications/tokens/query/PhabricatorTokenCountQuery.php',
     'PhabricatorTokenDAO' => 'applications/tokens/storage/PhabricatorTokenDAO.php',
     'PhabricatorTokenDestructionEngineExtension' => 'applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php',
     'PhabricatorTokenGiveController' => 'applications/tokens/controller/PhabricatorTokenGiveController.php',
     'PhabricatorTokenGiven' => 'applications/tokens/storage/PhabricatorTokenGiven.php',
     'PhabricatorTokenGivenController' => 'applications/tokens/controller/PhabricatorTokenGivenController.php',
     'PhabricatorTokenGivenEditor' => 'applications/tokens/editor/PhabricatorTokenGivenEditor.php',
     'PhabricatorTokenGivenFeedStory' => 'applications/tokens/feed/PhabricatorTokenGivenFeedStory.php',
     'PhabricatorTokenGivenQuery' => 'applications/tokens/query/PhabricatorTokenGivenQuery.php',
     'PhabricatorTokenLeaderController' => 'applications/tokens/controller/PhabricatorTokenLeaderController.php',
     'PhabricatorTokenQuery' => 'applications/tokens/query/PhabricatorTokenQuery.php',
     'PhabricatorTokenReceiverInterface' => 'applications/tokens/interface/PhabricatorTokenReceiverInterface.php',
     'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php',
     'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php',
     'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php',
     'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php',
     'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php',
     'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php',
     'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
     'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php',
     'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
     'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
     'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php',
     'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php',
     'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php',
     'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php',
     'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php',
     'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php',
     'PhabricatorTriggerClockTestCase' => 'infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php',
     'PhabricatorTriggerDaemon' => 'infrastructure/daemon/workers/PhabricatorTriggerDaemon.php',
     'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php',
     'PhabricatorTwitchAuthProvider' => 'applications/auth/provider/PhabricatorTwitchAuthProvider.php',
     'PhabricatorTwitterAuthProvider' => 'applications/auth/provider/PhabricatorTwitterAuthProvider.php',
     'PhabricatorTypeaheadApplication' => 'applications/typeahead/application/PhabricatorTypeaheadApplication.php',
     'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php',
     'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php',
     'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php',
     'PhabricatorTypeaheadFunctionHelpController' => 'applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php',
     'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php',
     'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
     'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
     'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
     'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
     'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php',
     'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php',
     'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php',
     'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php',
     'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php',
     'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php',
     'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php',
     'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php',
     'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
     'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
     'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
     'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
     'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php',
     'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php',
     'PhabricatorUserConfiguredCustomField' => 'applications/people/customfield/PhabricatorUserConfiguredCustomField.php',
     'PhabricatorUserConfiguredCustomFieldStorage' => 'applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php',
     'PhabricatorUserCustomField' => 'applications/people/customfield/PhabricatorUserCustomField.php',
     'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php',
     'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php',
     'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php',
     'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php',
     'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php',
     'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php',
     'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php',
     'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php',
     'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php',
     'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php',
     'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php',
     'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php',
     'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php',
     'PhabricatorUserProfile' => 'applications/people/storage/PhabricatorUserProfile.php',
     'PhabricatorUserProfileEditor' => 'applications/people/editor/PhabricatorUserProfileEditor.php',
     'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php',
     'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php',
     'PhabricatorUserSchemaSpec' => 'applications/people/storage/PhabricatorUserSchemaSpec.php',
     'PhabricatorUserSinceField' => 'applications/people/customfield/PhabricatorUserSinceField.php',
     'PhabricatorUserStatusField' => 'applications/people/customfield/PhabricatorUserStatusField.php',
     'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php',
     'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php',
     'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php',
     'PhabricatorUsersEditField' => 'applications/transactions/editfield/PhabricatorUsersEditField.php',
     'PhabricatorUsersPolicyRule' => 'applications/people/policyrule/PhabricatorUsersPolicyRule.php',
     'PhabricatorUsersSearchField' => 'applications/people/searchfield/PhabricatorUsersSearchField.php',
     'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php',
     'PhabricatorVersionedDraft' => 'applications/draft/storage/PhabricatorVersionedDraft.php',
     'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php',
     'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php',
     'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php',
     'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php',
     'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php',
     'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php',
     'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php',
     'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
     'PhabricatorWorkerArchiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php',
     'PhabricatorWorkerBulkJob' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php',
     'PhabricatorWorkerBulkJobCreateWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php',
     'PhabricatorWorkerBulkJobEditor' => 'infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php',
     'PhabricatorWorkerBulkJobPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php',
     'PhabricatorWorkerBulkJobQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php',
     'PhabricatorWorkerBulkJobSearchEngine' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php',
     'PhabricatorWorkerBulkJobTaskWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php',
     'PhabricatorWorkerBulkJobTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php',
     'PhabricatorWorkerBulkJobTransaction' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php',
     'PhabricatorWorkerBulkJobTransactionQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php',
     'PhabricatorWorkerBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php',
     'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php',
     'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php',
     'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php',
     'PhabricatorWorkerDestructionEngineExtension' => 'infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php',
     'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
     'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
     'PhabricatorWorkerManagementExecuteWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php',
     'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php',
     'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php',
     'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
     'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
     'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
     'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php',
     'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
     'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
     'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
     'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php',
     'PhabricatorWorkerTrigger' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php',
     'PhabricatorWorkerTriggerEvent' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTriggerEvent.php',
     'PhabricatorWorkerTriggerManagementFireWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php',
     'PhabricatorWorkerTriggerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php',
     'PhabricatorWorkerTriggerPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerTriggerPHIDType.php',
     'PhabricatorWorkerTriggerQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php',
     'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php',
     'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
     'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php',
     'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php',
     'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php',
     'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php',
     'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php',
     'PhabricatorXHPASTViewFrameController' => 'applications/phpast/controller/PhabricatorXHPASTViewFrameController.php',
     'PhabricatorXHPASTViewFramesetController' => 'applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php',
     'PhabricatorXHPASTViewInputController' => 'applications/phpast/controller/PhabricatorXHPASTViewInputController.php',
     'PhabricatorXHPASTViewPanelController' => 'applications/phpast/controller/PhabricatorXHPASTViewPanelController.php',
     'PhabricatorXHPASTViewRunController' => 'applications/phpast/controller/PhabricatorXHPASTViewRunController.php',
     'PhabricatorXHPASTViewStreamController' => 'applications/phpast/controller/PhabricatorXHPASTViewStreamController.php',
     'PhabricatorXHPASTViewTreeController' => 'applications/phpast/controller/PhabricatorXHPASTViewTreeController.php',
     'PhabricatorXHProfApplication' => 'applications/xhprof/application/PhabricatorXHProfApplication.php',
     'PhabricatorXHProfController' => 'applications/xhprof/controller/PhabricatorXHProfController.php',
     'PhabricatorXHProfDAO' => 'applications/xhprof/storage/PhabricatorXHProfDAO.php',
     'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/PhabricatorXHProfProfileController.php',
     'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php',
     'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php',
     'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php',
     'PhabricatorXHProfSample' => 'applications/xhprof/storage/PhabricatorXHProfSample.php',
     'PhabricatorXHProfSampleListController' => 'applications/xhprof/controller/PhabricatorXHProfSampleListController.php',
     'PhabricatorYoutubeRemarkupRule' => 'infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php',
     'PhameBlog' => 'applications/phame/storage/PhameBlog.php',
     'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php',
     'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php',
     'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php',
     'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php',
     'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php',
     'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php',
     'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php',
     'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php',
     'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
     'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php',
     'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php',
     'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php',
     'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
     'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php',
     'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php',
     'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php',
     'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php',
     'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php',
     'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php',
     'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php',
     'PhameConstants' => 'applications/phame/constants/PhameConstants.php',
     'PhameController' => 'applications/phame/controller/PhameController.php',
     'PhameDAO' => 'applications/phame/storage/PhameDAO.php',
     'PhameDescriptionView' => 'applications/phame/view/PhameDescriptionView.php',
     'PhameDraftListView' => 'applications/phame/view/PhameDraftListView.php',
     'PhameHomeController' => 'applications/phame/controller/PhameHomeController.php',
     'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php',
     'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php',
     'PhamePost' => 'applications/phame/storage/PhamePost.php',
     'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php',
     'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php',
     'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php',
     'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
     'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php',
     'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php',
     'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php',
     'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php',
     'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
     'PhamePostMailReceiver' => 'applications/phame/mail/PhamePostMailReceiver.php',
     'PhamePostMoveController' => 'applications/phame/controller/post/PhamePostMoveController.php',
     'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php',
     'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php',
     'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php',
     'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php',
     'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php',
     'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php',
     'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php',
     'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php',
     'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php',
     'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php',
     'PhameSite' => 'applications/phame/site/PhameSite.php',
     'PhluxController' => 'applications/phlux/controller/PhluxController.php',
     'PhluxDAO' => 'applications/phlux/storage/PhluxDAO.php',
     'PhluxEditController' => 'applications/phlux/controller/PhluxEditController.php',
     'PhluxListController' => 'applications/phlux/controller/PhluxListController.php',
     'PhluxTransaction' => 'applications/phlux/storage/PhluxTransaction.php',
     'PhluxTransactionQuery' => 'applications/phlux/query/PhluxTransactionQuery.php',
     'PhluxVariable' => 'applications/phlux/storage/PhluxVariable.php',
     'PhluxVariableEditor' => 'applications/phlux/editor/PhluxVariableEditor.php',
     'PhluxVariablePHIDType' => 'applications/phlux/phid/PhluxVariablePHIDType.php',
     'PhluxVariableQuery' => 'applications/phlux/query/PhluxVariableQuery.php',
     'PhluxViewController' => 'applications/phlux/controller/PhluxViewController.php',
     'PholioActionMenuEventListener' => 'applications/pholio/event/PholioActionMenuEventListener.php',
     'PholioController' => 'applications/pholio/controller/PholioController.php',
     'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
     'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php',
     'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php',
     'PholioImage' => 'applications/pholio/storage/PholioImage.php',
     'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php',
     'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php',
     'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php',
     'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php',
     'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php',
     'PholioMock' => 'applications/pholio/storage/PholioMock.php',
     'PholioMockArchiveController' => 'applications/pholio/controller/PholioMockArchiveController.php',
     'PholioMockAuthorHeraldField' => 'applications/pholio/herald/PholioMockAuthorHeraldField.php',
     'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
     'PholioMockDescriptionHeraldField' => 'applications/pholio/herald/PholioMockDescriptionHeraldField.php',
     'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
     'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php',
     'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php',
     'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php',
     'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php',
     'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php',
     'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php',
     'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php',
     'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php',
     'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php',
     'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php',
     'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php',
     'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php',
     'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php',
     'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php',
     'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php',
     'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php',
     'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php',
     'PholioSchemaSpec' => 'applications/pholio/storage/PholioSchemaSpec.php',
     'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php',
     'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php',
     'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php',
     'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php',
     'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php',
     'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php',
     'PhortuneAccountEditController' => 'applications/phortune/controller/PhortuneAccountEditController.php',
     'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php',
     'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php',
     'PhortuneAccountListController' => 'applications/phortune/controller/PhortuneAccountListController.php',
     'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php',
     'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
     'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
     'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
     'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
     'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php',
     'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php',
     'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
     'PhortuneCartAcceptController' => 'applications/phortune/controller/PhortuneCartAcceptController.php',
     'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php',
     'PhortuneCartCheckoutController' => 'applications/phortune/controller/PhortuneCartCheckoutController.php',
     'PhortuneCartController' => 'applications/phortune/controller/PhortuneCartController.php',
     'PhortuneCartEditor' => 'applications/phortune/editor/PhortuneCartEditor.php',
     'PhortuneCartImplementation' => 'applications/phortune/cart/PhortuneCartImplementation.php',
     'PhortuneCartListController' => 'applications/phortune/controller/PhortuneCartListController.php',
     'PhortuneCartPHIDType' => 'applications/phortune/phid/PhortuneCartPHIDType.php',
     'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php',
     'PhortuneCartReplyHandler' => 'applications/phortune/mail/PhortuneCartReplyHandler.php',
     'PhortuneCartSearchEngine' => 'applications/phortune/query/PhortuneCartSearchEngine.php',
     'PhortuneCartTransaction' => 'applications/phortune/storage/PhortuneCartTransaction.php',
     'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php',
     'PhortuneCartUpdateController' => 'applications/phortune/controller/PhortuneCartUpdateController.php',
     'PhortuneCartViewController' => 'applications/phortune/controller/PhortuneCartViewController.php',
     'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
     'PhortuneChargeListController' => 'applications/phortune/controller/PhortuneChargeListController.php',
     'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php',
     'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
     'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php',
     'PhortuneChargeTableView' => 'applications/phortune/view/PhortuneChargeTableView.php',
     'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php',
     'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
     'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
     'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php',
     'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php',
     'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php',
     'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
     'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php',
     'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php',
     'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php',
     'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php',
     'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php',
     'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php',
     'PhortuneMerchantController' => 'applications/phortune/controller/PhortuneMerchantController.php',
     'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php',
     'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php',
     'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php',
     'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php',
     'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php',
     'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php',
     'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php',
     'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php',
     'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php',
     'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php',
     'PhortuneMerchantViewController' => 'applications/phortune/controller/PhortuneMerchantViewController.php',
     'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
     'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php',
     'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php',
     'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
     'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/PhortunePaymentMethodCreateController.php',
     'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/PhortunePaymentMethodDisableController.php',
     'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php',
     'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php',
     'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php',
     'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php',
     'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php',
     'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php',
     'PhortunePaymentProviderConfigQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigQuery.php',
     'PhortunePaymentProviderConfigTransaction' => 'applications/phortune/storage/PhortunePaymentProviderConfigTransaction.php',
     'PhortunePaymentProviderConfigTransactionQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigTransactionQuery.php',
     'PhortunePaymentProviderPHIDType' => 'applications/phortune/phid/PhortunePaymentProviderPHIDType.php',
     'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php',
     'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
     'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php',
     'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php',
     'PhortuneProductPHIDType' => 'applications/phortune/phid/PhortuneProductPHIDType.php',
     'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php',
     'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php',
     'PhortuneProviderActionController' => 'applications/phortune/controller/PhortuneProviderActionController.php',
     'PhortuneProviderDisableController' => 'applications/phortune/controller/PhortuneProviderDisableController.php',
     'PhortuneProviderEditController' => 'applications/phortune/controller/PhortuneProviderEditController.php',
     'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
     'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php',
     'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php',
     'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php',
     'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php',
     'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php',
     'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php',
     'PhortuneSubscriptionEditController' => 'applications/phortune/controller/PhortuneSubscriptionEditController.php',
     'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php',
     'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php',
     'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php',
     'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php',
     'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php',
     'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php',
     'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php',
     'PhortuneSubscriptionViewController' => 'applications/phortune/controller/PhortuneSubscriptionViewController.php',
     'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php',
     'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
     'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php',
     'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php',
     'PhragmentCanCreateCapability' => 'applications/phragment/capability/PhragmentCanCreateCapability.php',
     'PhragmentConduitAPIMethod' => 'applications/phragment/conduit/PhragmentConduitAPIMethod.php',
     'PhragmentController' => 'applications/phragment/controller/PhragmentController.php',
     'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php',
     'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php',
     'PhragmentFragment' => 'applications/phragment/storage/PhragmentFragment.php',
     'PhragmentFragmentPHIDType' => 'applications/phragment/phid/PhragmentFragmentPHIDType.php',
     'PhragmentFragmentQuery' => 'applications/phragment/query/PhragmentFragmentQuery.php',
     'PhragmentFragmentVersion' => 'applications/phragment/storage/PhragmentFragmentVersion.php',
     'PhragmentFragmentVersionPHIDType' => 'applications/phragment/phid/PhragmentFragmentVersionPHIDType.php',
     'PhragmentFragmentVersionQuery' => 'applications/phragment/query/PhragmentFragmentVersionQuery.php',
     'PhragmentGetPatchConduitAPIMethod' => 'applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php',
     'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php',
     'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php',
     'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php',
     'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php',
     'PhragmentQueryFragmentsConduitAPIMethod' => 'applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php',
     'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php',
     'PhragmentSchemaSpec' => 'applications/phragment/storage/PhragmentSchemaSpec.php',
     'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php',
     'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php',
     'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php',
     'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php',
     'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php',
     'PhragmentSnapshotPHIDType' => 'applications/phragment/phid/PhragmentSnapshotPHIDType.php',
     'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php',
     'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php',
     'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php',
     'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php',
     'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php',
     'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php',
     'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php',
     'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php',
     'PhrequentCurtainExtension' => 'applications/phrequent/engineextension/PhrequentCurtainExtension.php',
     'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php',
     'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php',
     'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php',
     'PhrequentPushConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPushConduitAPIMethod.php',
     'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php',
     'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php',
     'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php',
     'PhrequentTimeSlices' => 'applications/phrequent/storage/PhrequentTimeSlices.php',
     'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php',
     'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php',
     'PhrequentTrackingConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentTrackingConduitAPIMethod.php',
     'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php',
     'PhrequentUIEventListener' => 'applications/phrequent/event/PhrequentUIEventListener.php',
     'PhrequentUserTime' => 'applications/phrequent/storage/PhrequentUserTime.php',
     'PhrequentUserTimeQuery' => 'applications/phrequent/query/PhrequentUserTimeQuery.php',
     'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php',
     'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php',
     'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php',
     'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php',
     'PhrictionController' => 'applications/phriction/controller/PhrictionController.php',
     'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php',
     'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php',
     'PhrictionDeleteController' => 'applications/phriction/controller/PhrictionDeleteController.php',
     'PhrictionDiffController' => 'applications/phriction/controller/PhrictionDiffController.php',
     'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php',
     'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php',
     'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php',
     'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php',
     'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php',
     'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php',
     'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php',
     'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php',
     'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php',
     'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php',
     'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php',
     'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php',
     'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php',
     'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php',
     'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php',
     'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php',
     'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php',
     'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php',
     'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php',
     'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php',
     'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php',
     'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php',
     'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php',
     'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php',
     'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php',
     'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php',
     'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php',
     'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php',
     'PhrictionTransactionQuery' => 'applications/phriction/query/PhrictionTransactionQuery.php',
     'PolicyLockOptionType' => 'applications/policy/config/PolicyLockOptionType.php',
     'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php',
     'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php',
     'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php',
     'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php',
     'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php',
     'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php',
     'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php',
     'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php',
     'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php',
     'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php',
     'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php',
     'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php',
     'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php',
     'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php',
     'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php',
     'PonderAnswerView' => 'applications/ponder/view/PonderAnswerView.php',
     'PonderConstants' => 'applications/ponder/constants/PonderConstants.php',
     'PonderController' => 'applications/ponder/controller/PonderController.php',
     'PonderDAO' => 'applications/ponder/storage/PonderDAO.php',
     'PonderDefaultViewCapability' => 'applications/ponder/capability/PonderDefaultViewCapability.php',
     'PonderEditor' => 'applications/ponder/editor/PonderEditor.php',
     'PonderFooterView' => 'applications/ponder/view/PonderFooterView.php',
     'PonderModerateCapability' => 'applications/ponder/capability/PonderModerateCapability.php',
     'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php',
     'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php',
     'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php',
     'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php',
     'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php',
     'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php',
     'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php',
     'PonderQuestionMailReceiver' => 'applications/ponder/mail/PonderQuestionMailReceiver.php',
     'PonderQuestionPHIDType' => 'applications/ponder/phid/PonderQuestionPHIDType.php',
     'PonderQuestionQuery' => 'applications/ponder/query/PonderQuestionQuery.php',
     'PonderQuestionReplyHandler' => 'applications/ponder/mail/PonderQuestionReplyHandler.php',
     'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php',
     'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php',
     'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php',
     'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php',
     'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php',
     'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php',
     'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php',
     'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php',
     'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php',
     'ProjectAddProjectsEmailCommand' => 'applications/project/command/ProjectAddProjectsEmailCommand.php',
     'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php',
     'ProjectCanLockProjectsCapability' => 'applications/project/capability/ProjectCanLockProjectsCapability.php',
     'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php',
     'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php',
     'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php',
     'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php',
     'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php',
     'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php',
     'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php',
     'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
     'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
     'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
     'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php',
     'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php',
     'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
     'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
     'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
     'ReleephBranchAccessController' => 'applications/releeph/controller/branch/ReleephBranchAccessController.php',
     'ReleephBranchCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php',
     'ReleephBranchController' => 'applications/releeph/controller/branch/ReleephBranchController.php',
     'ReleephBranchCreateController' => 'applications/releeph/controller/branch/ReleephBranchCreateController.php',
     'ReleephBranchEditController' => 'applications/releeph/controller/branch/ReleephBranchEditController.php',
     'ReleephBranchEditor' => 'applications/releeph/editor/ReleephBranchEditor.php',
     'ReleephBranchHistoryController' => 'applications/releeph/controller/branch/ReleephBranchHistoryController.php',
     'ReleephBranchNamePreviewController' => 'applications/releeph/controller/branch/ReleephBranchNamePreviewController.php',
     'ReleephBranchPHIDType' => 'applications/releeph/phid/ReleephBranchPHIDType.php',
     'ReleephBranchPreviewView' => 'applications/releeph/view/branch/ReleephBranchPreviewView.php',
     'ReleephBranchQuery' => 'applications/releeph/query/ReleephBranchQuery.php',
     'ReleephBranchSearchEngine' => 'applications/releeph/query/ReleephBranchSearchEngine.php',
     'ReleephBranchTemplate' => 'applications/releeph/view/branch/ReleephBranchTemplate.php',
     'ReleephBranchTransaction' => 'applications/releeph/storage/ReleephBranchTransaction.php',
     'ReleephBranchTransactionQuery' => 'applications/releeph/query/ReleephBranchTransactionQuery.php',
     'ReleephBranchViewController' => 'applications/releeph/controller/branch/ReleephBranchViewController.php',
     'ReleephCommitFinder' => 'applications/releeph/commitfinder/ReleephCommitFinder.php',
     'ReleephCommitFinderException' => 'applications/releeph/commitfinder/ReleephCommitFinderException.php',
     'ReleephCommitMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php',
     'ReleephConduitAPIMethod' => 'applications/releeph/conduit/ReleephConduitAPIMethod.php',
     'ReleephController' => 'applications/releeph/controller/ReleephController.php',
     'ReleephDAO' => 'applications/releeph/storage/ReleephDAO.php',
     'ReleephDefaultFieldSelector' => 'applications/releeph/field/selector/ReleephDefaultFieldSelector.php',
     'ReleephDependsOnFieldSpecification' => 'applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php',
     'ReleephDiffChurnFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php',
     'ReleephDiffMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php',
     'ReleephDiffSizeFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php',
     'ReleephFieldParseException' => 'applications/releeph/field/exception/ReleephFieldParseException.php',
     'ReleephFieldSelector' => 'applications/releeph/field/selector/ReleephFieldSelector.php',
     'ReleephFieldSpecification' => 'applications/releeph/field/specification/ReleephFieldSpecification.php',
     'ReleephGetBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php',
     'ReleephIntentFieldSpecification' => 'applications/releeph/field/specification/ReleephIntentFieldSpecification.php',
     'ReleephLevelFieldSpecification' => 'applications/releeph/field/specification/ReleephLevelFieldSpecification.php',
     'ReleephOriginalCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php',
     'ReleephProductActionController' => 'applications/releeph/controller/product/ReleephProductActionController.php',
     'ReleephProductController' => 'applications/releeph/controller/product/ReleephProductController.php',
     'ReleephProductCreateController' => 'applications/releeph/controller/product/ReleephProductCreateController.php',
     'ReleephProductEditController' => 'applications/releeph/controller/product/ReleephProductEditController.php',
     'ReleephProductEditor' => 'applications/releeph/editor/ReleephProductEditor.php',
     'ReleephProductHistoryController' => 'applications/releeph/controller/product/ReleephProductHistoryController.php',
     'ReleephProductListController' => 'applications/releeph/controller/product/ReleephProductListController.php',
     'ReleephProductPHIDType' => 'applications/releeph/phid/ReleephProductPHIDType.php',
     'ReleephProductQuery' => 'applications/releeph/query/ReleephProductQuery.php',
     'ReleephProductSearchEngine' => 'applications/releeph/query/ReleephProductSearchEngine.php',
     'ReleephProductTransaction' => 'applications/releeph/storage/ReleephProductTransaction.php',
     'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php',
     'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php',
     'ReleephProject' => 'applications/releeph/storage/ReleephProject.php',
     'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php',
     'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php',
     'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php',
     'ReleephReasonFieldSpecification' => 'applications/releeph/field/specification/ReleephReasonFieldSpecification.php',
     'ReleephRequest' => 'applications/releeph/storage/ReleephRequest.php',
     'ReleephRequestActionController' => 'applications/releeph/controller/request/ReleephRequestActionController.php',
     'ReleephRequestCommentController' => 'applications/releeph/controller/request/ReleephRequestCommentController.php',
     'ReleephRequestConduitAPIMethod' => 'applications/releeph/conduit/ReleephRequestConduitAPIMethod.php',
     'ReleephRequestController' => 'applications/releeph/controller/request/ReleephRequestController.php',
     'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php',
     'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php',
     'ReleephRequestMailReceiver' => 'applications/releeph/mail/ReleephRequestMailReceiver.php',
     'ReleephRequestPHIDType' => 'applications/releeph/phid/ReleephRequestPHIDType.php',
     'ReleephRequestQuery' => 'applications/releeph/query/ReleephRequestQuery.php',
     'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php',
     'ReleephRequestSearchEngine' => 'applications/releeph/query/ReleephRequestSearchEngine.php',
     'ReleephRequestStatus' => 'applications/releeph/constants/ReleephRequestStatus.php',
     'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php',
     'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php',
     'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php',
     'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php',
     'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php',
     'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php',
     'ReleephRequestView' => 'applications/releeph/view/ReleephRequestView.php',
     'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php',
     'ReleephRequestorFieldSpecification' => 'applications/releeph/field/specification/ReleephRequestorFieldSpecification.php',
     'ReleephRevisionFieldSpecification' => 'applications/releeph/field/specification/ReleephRevisionFieldSpecification.php',
     'ReleephSeverityFieldSpecification' => 'applications/releeph/field/specification/ReleephSeverityFieldSpecification.php',
     'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php',
     'ReleephWorkCanPushConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php',
     'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php',
     'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php',
     'ReleephWorkGetBranchConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php',
     'ReleephWorkGetCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php',
     'ReleephWorkNextRequestConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php',
     'ReleephWorkRecordConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php',
     'ReleephWorkRecordPickStatusConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php',
     'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php',
     'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php',
     'RepositoryCreateConduitAPIMethod' => 'applications/repository/conduit/RepositoryCreateConduitAPIMethod.php',
     'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php',
     'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php',
     'SlowvoteConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteConduitAPIMethod.php',
     'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
     'SlowvoteInfoConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteInfoConduitAPIMethod.php',
     'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
     'SubscriptionListDialogBuilder' => 'applications/subscriptions/view/SubscriptionListDialogBuilder.php',
     'SubscriptionListStringBuilder' => 'applications/subscriptions/view/SubscriptionListStringBuilder.php',
     'TokenConduitAPIMethod' => 'applications/tokens/conduit/TokenConduitAPIMethod.php',
     'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php',
     'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php',
     'TokenQueryConduitAPIMethod' => 'applications/tokens/conduit/TokenQueryConduitAPIMethod.php',
     'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php',
     'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php',
     'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php',
     'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php',
     'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php',
     'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php',
   ),
   'function' => array(
     'celerity_generate_unique_node_id' => 'applications/celerity/api.php',
     'celerity_get_resource_uri' => 'applications/celerity/api.php',
     'javelin_tag' => 'infrastructure/javelin/markup.php',
     'phabricator_date' => 'view/viewutils.php',
     'phabricator_datetime' => 'view/viewutils.php',
     'phabricator_form' => 'infrastructure/javelin/markup.php',
     'phabricator_format_local_time' => 'view/viewutils.php',
     'phabricator_relative_date' => 'view/viewutils.php',
     'phabricator_time' => 'view/viewutils.php',
     'phid_get_subtype' => 'applications/phid/utils.php',
     'phid_get_type' => 'applications/phid/utils.php',
     'phid_group_by_type' => 'applications/phid/utils.php',
     'require_celerity_resource' => 'applications/celerity/api.php',
   ),
   'xmap' => array(
     'AlmanacAddress' => 'Phobject',
     'AlmanacBinding' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'AlmanacPropertyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'AlmanacBindingDisableController' => 'AlmanacServiceController',
     'AlmanacBindingEditController' => 'AlmanacServiceController',
     'AlmanacBindingEditor' => 'AlmanacEditor',
     'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
     'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine',
     'AlmanacBindingQuery' => 'AlmanacQuery',
     'AlmanacBindingTableView' => 'AphrontView',
     'AlmanacBindingTransaction' => 'AlmanacTransaction',
     'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'AlmanacBindingViewController' => 'AlmanacServiceController',
     'AlmanacBindingsSearchEngineAttachment' => 'AlmanacSearchEngineAttachment',
     'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType',
     'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType',
     'AlmanacClusterServiceType' => 'AlmanacServiceType',
     'AlmanacConsoleController' => 'AlmanacController',
     'AlmanacController' => 'PhabricatorController',
     'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
     'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
     'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
     'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
     'AlmanacCustomServiceType' => 'AlmanacServiceType',
     'AlmanacDAO' => 'PhabricatorLiskDAO',
     'AlmanacDevice' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorProjectInterface',
       'PhabricatorSSHPublicKeyInterface',
       'AlmanacPropertyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorNgramsInterface',
       'PhabricatorConduitResultInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'AlmanacDeviceController' => 'AlmanacController',
     'AlmanacDeviceEditController' => 'AlmanacDeviceController',
     'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine',
     'AlmanacDeviceEditor' => 'AlmanacEditor',
     'AlmanacDeviceListController' => 'AlmanacDeviceController',
     'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams',
     'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
     'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
     'AlmanacDeviceQuery' => 'AlmanacQuery',
     'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'AlmanacDeviceTransaction' => 'AlmanacTransaction',
     'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'AlmanacDeviceViewController' => 'AlmanacDeviceController',
     'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType',
     'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor',
     'AlmanacInterface' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource',
     'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController',
     'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
     'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
     'AlmanacInterfaceQuery' => 'AlmanacQuery',
     'AlmanacInterfaceTableView' => 'AphrontView',
     'AlmanacKeys' => 'Phobject',
     'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability',
     'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow',
     'AlmanacManagementTrustKeyWorkflow' => 'AlmanacManagementWorkflow',
     'AlmanacManagementUntrustKeyWorkflow' => 'AlmanacManagementWorkflow',
     'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'AlmanacNames' => 'Phobject',
     'AlmanacNamesTestCase' => 'PhabricatorTestCase',
     'AlmanacNamespace' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorProjectInterface',
       'AlmanacPropertyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorNgramsInterface',
     ),
     'AlmanacNamespaceController' => 'AlmanacController',
     'AlmanacNamespaceEditController' => 'AlmanacNamespaceController',
     'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine',
     'AlmanacNamespaceEditor' => 'PhabricatorApplicationTransactionEditor',
     'AlmanacNamespaceListController' => 'AlmanacNamespaceController',
     'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams',
     'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType',
     'AlmanacNamespaceQuery' => 'AlmanacQuery',
     'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'AlmanacNamespaceTransaction' => 'PhabricatorApplicationTransaction',
     'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'AlmanacNamespaceViewController' => 'AlmanacNamespaceController',
     'AlmanacNetwork' => array(
       'AlmanacDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorNgramsInterface',
     ),
     'AlmanacNetworkController' => 'AlmanacController',
     'AlmanacNetworkEditController' => 'AlmanacNetworkController',
     'AlmanacNetworkEditEngine' => 'PhabricatorEditEngine',
     'AlmanacNetworkEditor' => 'PhabricatorApplicationTransactionEditor',
     'AlmanacNetworkListController' => 'AlmanacNetworkController',
     'AlmanacNetworkNameNgrams' => 'PhabricatorSearchNgrams',
     'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType',
     'AlmanacNetworkQuery' => 'AlmanacQuery',
     'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction',
     'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'AlmanacNetworkViewController' => 'AlmanacNetworkController',
     'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'AlmanacPropertiesSearchEngineAttachment' => 'AlmanacSearchEngineAttachment',
     'AlmanacProperty' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
     ),
     'AlmanacPropertyController' => 'AlmanacController',
     'AlmanacPropertyDeleteController' => 'AlmanacPropertyController',
     'AlmanacPropertyEditController' => 'AlmanacPropertyController',
     'AlmanacPropertyEditEngine' => 'PhabricatorEditEngine',
     'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'AlmanacSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'AlmanacSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'AlmanacService' => array(
       'AlmanacDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorProjectInterface',
       'AlmanacPropertyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorNgramsInterface',
       'PhabricatorConduitResultInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'AlmanacServiceController' => 'AlmanacController',
     'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource',
     'AlmanacServiceEditController' => 'AlmanacServiceController',
     'AlmanacServiceEditEngine' => 'PhabricatorEditEngine',
     'AlmanacServiceEditor' => 'AlmanacEditor',
     'AlmanacServiceListController' => 'AlmanacServiceController',
     'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams',
     'AlmanacServicePHIDType' => 'PhabricatorPHIDType',
     'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
     'AlmanacServiceQuery' => 'AlmanacQuery',
     'AlmanacServiceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'AlmanacServiceTransaction' => 'AlmanacTransaction',
     'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'AlmanacServiceType' => 'Phobject',
     'AlmanacServiceTypeDatasource' => 'PhabricatorTypeaheadDatasource',
     'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase',
     'AlmanacServiceViewController' => 'AlmanacServiceController',
     'AlmanacTransaction' => 'PhabricatorApplicationTransaction',
     'AphlictDropdownDataQuery' => 'Phobject',
     'Aphront304Response' => 'AphrontResponse',
     'Aphront400Response' => 'AphrontResponse',
     'Aphront403Response' => 'AphrontHTMLResponse',
     'Aphront404Response' => 'AphrontHTMLResponse',
     'AphrontAjaxResponse' => 'AphrontResponse',
     'AphrontApplicationConfiguration' => 'Phobject',
     'AphrontBarView' => 'AphrontView',
     'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontCSRFException' => 'AphrontException',
     'AphrontCalendarEventView' => 'AphrontView',
     'AphrontController' => 'Phobject',
     'AphrontCursorPagerView' => 'AphrontView',
     'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
     'AphrontDialogResponse' => 'AphrontResponse',
     'AphrontDialogView' => array(
       'AphrontView',
       'AphrontResponseProducerInterface',
     ),
+    'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontException' => 'Exception',
     'AphrontFileResponse' => 'AphrontResponse',
     'AphrontFormCheckboxControl' => 'AphrontFormControl',
     'AphrontFormControl' => 'AphrontView',
     'AphrontFormDateControl' => 'AphrontFormControl',
     'AphrontFormDateControlValue' => 'Phobject',
     'AphrontFormDividerControl' => 'AphrontFormControl',
     'AphrontFormFileControl' => 'AphrontFormControl',
     'AphrontFormHandlesControl' => 'AphrontFormControl',
     'AphrontFormMarkupControl' => 'AphrontFormControl',
     'AphrontFormPasswordControl' => 'AphrontFormControl',
     'AphrontFormPolicyControl' => 'AphrontFormControl',
     'AphrontFormRadioButtonControl' => 'AphrontFormControl',
     'AphrontFormRecaptchaControl' => 'AphrontFormControl',
     'AphrontFormSelectControl' => 'AphrontFormControl',
     'AphrontFormStaticControl' => 'AphrontFormControl',
     'AphrontFormSubmitControl' => 'AphrontFormControl',
     'AphrontFormTextAreaControl' => 'AphrontFormControl',
     'AphrontFormTextControl' => 'AphrontFormControl',
     'AphrontFormTextWithSubmitControl' => 'AphrontFormControl',
     'AphrontFormTokenizerControl' => 'AphrontFormControl',
     'AphrontFormTypeaheadControl' => 'AphrontFormControl',
     'AphrontFormView' => 'AphrontView',
     'AphrontGlyphBarView' => 'AphrontBarView',
     'AphrontHTMLResponse' => 'AphrontResponse',
     'AphrontHTTPParameterType' => 'Phobject',
     'AphrontHTTPProxyResponse' => 'AphrontResponse',
     'AphrontHTTPSink' => 'Phobject',
     'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase',
     'AphrontIntHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
     'AphrontIsolatedHTTPSink' => 'AphrontHTTPSink',
     'AphrontJSONResponse' => 'AphrontResponse',
     'AphrontJavelinView' => 'AphrontView',
     'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
     'AphrontListFilterView' => 'AphrontView',
     'AphrontListHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontMalformedRequestException' => 'AphrontException',
     'AphrontMoreView' => 'AphrontView',
     'AphrontMultiColumnView' => 'AphrontView',
     'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase',
     'AphrontNullView' => 'AphrontView',
     'AphrontPHIDHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontPHIDListHTTPParameterType' => 'AphrontListHTTPParameterType',
     'AphrontPHPHTTPSink' => 'AphrontHTTPSink',
     'AphrontPageView' => 'AphrontView',
     'AphrontPlainTextResponse' => 'AphrontResponse',
     'AphrontProgressBarView' => 'AphrontBarView',
     'AphrontProjectListHTTPParameterType' => 'AphrontListHTTPParameterType',
     'AphrontProxyResponse' => array(
       'AphrontResponse',
       'AphrontResponseProducerInterface',
     ),
     'AphrontRedirectResponse' => 'AphrontResponse',
     'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
     'AphrontReloadResponse' => 'AphrontRedirectResponse',
     'AphrontRequest' => 'Phobject',
     'AphrontRequestExceptionHandler' => 'Phobject',
     'AphrontRequestTestCase' => 'PhabricatorTestCase',
     'AphrontResponse' => 'Phobject',
     'AphrontRoutingMap' => 'Phobject',
     'AphrontRoutingResult' => 'Phobject',
     'AphrontSelectHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontSideNavFilterView' => 'AphrontView',
     'AphrontSite' => 'Phobject',
     'AphrontStackTraceView' => 'AphrontView',
     'AphrontStandaloneHTMLResponse' => 'AphrontHTMLResponse',
     'AphrontStringHTTPParameterType' => 'AphrontHTTPParameterType',
     'AphrontStringListHTTPParameterType' => 'AphrontListHTTPParameterType',
     'AphrontTableView' => 'AphrontView',
     'AphrontTagView' => 'AphrontView',
     'AphrontTokenizerTemplateView' => 'AphrontView',
     'AphrontTypeaheadTemplateView' => 'AphrontView',
     'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse',
     'AphrontUserListHTTPParameterType' => 'AphrontListHTTPParameterType',
     'AphrontView' => array(
       'Phobject',
       'PhutilSafeHTMLProducerInterface',
     ),
     'AphrontWebpageResponse' => 'AphrontHTMLResponse',
     'ArcanistConduitAPIMethod' => 'ConduitAPIMethod',
     'AuditConduitAPIMethod' => 'ConduitAPIMethod',
     'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod',
     'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability',
     'CalendarTimeUtil' => 'Phobject',
     'CalendarTimeUtilTestCase' => 'PhabricatorTestCase',
     'CelerityAPI' => 'Phobject',
     'CelerityDefaultPostprocessor' => 'CelerityPostprocessor',
     'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor',
     'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor',
     'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow',
     'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'CelerityPhabricatorResourceController' => 'CelerityResourceController',
     'CelerityPhabricatorResources' => 'CelerityResourcesOnDisk',
     'CelerityPhysicalResources' => 'CelerityResources',
     'CelerityPhysicalResourcesTestCase' => 'PhabricatorTestCase',
     'CelerityPostprocessor' => 'Phobject',
     'CelerityPostprocessorTestCase' => 'PhabricatorTestCase',
     'CelerityResourceController' => 'PhabricatorController',
     'CelerityResourceGraph' => 'AbstractDirectedGraph',
     'CelerityResourceMap' => 'Phobject',
     'CelerityResourceMapGenerator' => 'Phobject',
     'CelerityResourceTransformer' => 'Phobject',
     'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase',
     'CelerityResources' => 'Phobject',
     'CelerityResourcesOnDisk' => 'CelerityPhysicalResources',
     'CeleritySpriteGenerator' => 'Phobject',
     'CelerityStaticResourceResponse' => 'Phobject',
     'ChatLogConduitAPIMethod' => 'ConduitAPIMethod',
     'ChatLogQueryConduitAPIMethod' => 'ChatLogConduitAPIMethod',
     'ChatLogRecordConduitAPIMethod' => 'ChatLogConduitAPIMethod',
     'ConduitAPIMethod' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
     ),
     'ConduitAPIMethodTestCase' => 'PhabricatorTestCase',
     'ConduitAPIRequest' => 'Phobject',
     'ConduitAPIResponse' => 'Phobject',
     'ConduitApplicationNotInstalledException' => 'ConduitMethodNotFoundException',
     'ConduitBoolParameterType' => 'ConduitListParameterType',
     'ConduitCall' => 'Phobject',
     'ConduitCallTestCase' => 'PhabricatorTestCase',
+    'ConduitColumnsParameterType' => 'ConduitParameterType',
     'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitEpochParameterType' => 'ConduitListParameterType',
     'ConduitException' => 'Exception',
     'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitIntListParameterType' => 'ConduitListParameterType',
     'ConduitIntParameterType' => 'ConduitParameterType',
     'ConduitListParameterType' => 'ConduitParameterType',
     'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
     'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
     'ConduitMethodNotFoundException' => 'ConduitException',
     'ConduitPHIDListParameterType' => 'ConduitListParameterType',
     'ConduitPHIDParameterType' => 'ConduitParameterType',
     'ConduitParameterType' => 'Phobject',
     'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitPointsParameterType' => 'ConduitParameterType',
     'ConduitProjectListParameterType' => 'ConduitListParameterType',
     'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
     'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow',
     'ConduitStringListParameterType' => 'ConduitListParameterType',
     'ConduitStringParameterType' => 'ConduitParameterType',
     'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
     'ConduitUserListParameterType' => 'ConduitListParameterType',
     'ConduitUserParameterType' => 'ConduitParameterType',
     'ConduitWildParameterType' => 'ConduitListParameterType',
     'ConpherenceColumnViewController' => 'ConpherenceController',
     'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod',
     'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'ConpherenceConstants' => 'Phobject',
     'ConpherenceController' => 'PhabricatorController',
     'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
     'ConpherenceDAO' => 'PhabricatorLiskDAO',
     'ConpherenceDurableColumnView' => 'AphrontTagView',
     'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
     'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
     'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery',
     'ConpherenceImageData' => 'ConpherenceConstants',
     'ConpherenceIndex' => 'ConpherenceDAO',
     'ConpherenceLayoutView' => 'AphrontView',
     'ConpherenceListController' => 'ConpherenceController',
     'ConpherenceMenuItemView' => 'AphrontTagView',
     'ConpherenceNewRoomController' => 'ConpherenceController',
     'ConpherenceNotificationPanelController' => 'ConpherenceController',
     'ConpherenceParticipant' => 'ConpherenceDAO',
     'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery',
     'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
     'ConpherenceParticipationStatus' => 'ConpherenceConstants',
     'ConpherencePeopleWidgetView' => 'ConpherenceWidgetView',
     'ConpherencePicCropControl' => 'AphrontFormControl',
     'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
     'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
     'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
     'ConpherenceRoomListController' => 'ConpherenceController',
     'ConpherenceRoomTestCase' => 'ConpherenceTestCase',
     'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'ConpherenceSettings' => 'ConpherenceConstants',
     'ConpherenceTestCase' => 'PhabricatorTestCase',
     'ConpherenceThread' => array(
       'ConpherenceDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorDestructibleInterface',
     ),
     'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
     'ConpherenceThreadListView' => 'AphrontView',
     'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver',
     'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule',
     'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'ConpherenceTransaction' => 'PhabricatorApplicationTransaction',
     'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'ConpherenceTransactionRenderer' => 'Phobject',
     'ConpherenceTransactionView' => 'AphrontView',
     'ConpherenceUpdateActions' => 'ConpherenceConstants',
     'ConpherenceUpdateController' => 'ConpherenceController',
     'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
     'ConpherenceViewController' => 'ConpherenceController',
     'ConpherenceWidgetConfigConstants' => 'ConpherenceConstants',
     'ConpherenceWidgetController' => 'ConpherenceController',
     'ConpherenceWidgetView' => 'AphrontView',
     'DarkConsoleController' => 'PhabricatorController',
     'DarkConsoleCore' => 'Phobject',
     'DarkConsoleDataController' => 'PhabricatorController',
     'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin',
     'DarkConsoleErrorLogPluginAPI' => 'Phobject',
     'DarkConsoleEventPlugin' => 'DarkConsolePlugin',
     'DarkConsoleEventPluginAPI' => 'PhabricatorEventListener',
     'DarkConsolePlugin' => 'Phobject',
     'DarkConsoleRequestPlugin' => 'DarkConsolePlugin',
     'DarkConsoleServicesPlugin' => 'DarkConsolePlugin',
     'DarkConsoleStartupPlugin' => 'DarkConsolePlugin',
     'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin',
     'DarkConsoleXHProfPluginAPI' => 'Phobject',
     'DefaultDatabaseConfigurationProvider' => array(
       'Phobject',
       'DatabaseConfigurationProvider',
     ),
     'DifferentialAction' => 'Phobject',
     'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'DifferentialActionMenuEventListener' => 'PhabricatorEventListener',
     'DifferentialAddCommentView' => 'AphrontView',
     'DifferentialAdjustmentMapTestCase' => 'PhutilTestCase',
     'DifferentialAffectedPath' => 'DifferentialDAO',
     'DifferentialApplyPatchField' => 'DifferentialCustomField',
     'DifferentialAsanaRepresentationField' => 'DifferentialCustomField',
     'DifferentialAuditorsField' => 'DifferentialStoredCustomField',
     'DifferentialAuthorField' => 'DifferentialCustomField',
     'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
     'DifferentialBlockHeraldAction' => 'HeraldAction',
     'DifferentialBranchField' => 'DifferentialCustomField',
     'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
     'DifferentialChangeType' => 'Phobject',
     'DifferentialChangesSinceLastUpdateField' => 'DifferentialCustomField',
     'DifferentialChangeset' => array(
       'DifferentialDAO',
       'PhabricatorPolicyInterface',
     ),
     'DifferentialChangesetDetailView' => 'AphrontView',
     'DifferentialChangesetFileTreeSideNavBuilder' => 'Phobject',
     'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer',
     'DifferentialChangesetListView' => 'AphrontView',
     'DifferentialChangesetOneUpRenderer' => 'DifferentialChangesetHTMLRenderer',
     'DifferentialChangesetOneUpTestRenderer' => 'DifferentialChangesetTestRenderer',
     'DifferentialChangesetParser' => 'Phobject',
     'DifferentialChangesetParserTestCase' => 'PhabricatorTestCase',
     'DifferentialChangesetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DifferentialChangesetRenderer' => 'Phobject',
     'DifferentialChangesetTestRenderer' => 'DifferentialChangesetRenderer',
     'DifferentialChangesetTwoUpRenderer' => 'DifferentialChangesetHTMLRenderer',
     'DifferentialChangesetTwoUpTestRenderer' => 'DifferentialChangesetTestRenderer',
     'DifferentialChangesetViewController' => 'DifferentialController',
     'DifferentialCloseConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCommentPreviewController' => 'DifferentialController',
     'DifferentialCommentSaveController' => 'DifferentialController',
     'DifferentialCommitMessageParser' => 'Phobject',
     'DifferentialCommitMessageParserTestCase' => 'PhabricatorTestCase',
     'DifferentialCommitsField' => 'DifferentialCustomField',
     'DifferentialConduitAPIMethod' => 'ConduitAPIMethod',
     'DifferentialConflictsField' => 'DifferentialCustomField',
     'DifferentialController' => 'PhabricatorController',
     'DifferentialCoreCustomField' => 'DifferentialCustomField',
     'DifferentialCreateCommentConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCreateDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCreateInlineConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCreateMailReceiver' => 'PhabricatorMailReceiver',
     'DifferentialCreateRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCreateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialCustomField' => 'PhabricatorCustomField',
     'DifferentialCustomFieldDependsOnParser' => 'PhabricatorCustomFieldMonogramParser',
     'DifferentialCustomFieldDependsOnParserTestCase' => 'PhabricatorTestCase',
     'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
     'DifferentialCustomFieldRevertsParser' => 'PhabricatorCustomFieldMonogramParser',
     'DifferentialCustomFieldRevertsParserTestCase' => 'PhabricatorTestCase',
     'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
     'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
     'DifferentialDAO' => 'PhabricatorLiskDAO',
     'DifferentialDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'DifferentialDependenciesField' => 'DifferentialCustomField',
     'DifferentialDependsOnField' => 'DifferentialCustomField',
     'DifferentialDiff' => array(
       'DifferentialDAO',
       'PhabricatorPolicyInterface',
       'HarbormasterBuildableInterface',
       'HarbormasterCircleCIBuildableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
     ),
     'DifferentialDiffAffectedFilesHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffAuthorHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffAuthorProjectsHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffContentAddedHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffContentHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffCreateController' => 'DifferentialController',
     'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor',
     'DifferentialDiffExtractionEngine' => 'Phobject',
     'DifferentialDiffHeraldField' => 'HeraldField',
     'DifferentialDiffHeraldFieldGroup' => 'HeraldFieldGroup',
     'DifferentialDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery',
     'DifferentialDiffPHIDType' => 'PhabricatorPHIDType',
     'DifferentialDiffProperty' => 'DifferentialDAO',
     'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField',
     'DifferentialDiffTestCase' => 'PhutilTestCase',
     'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction',
     'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'DifferentialDiffViewController' => 'DifferentialController',
     'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
     'DifferentialDraft' => 'DifferentialDAO',
     'DifferentialEditPolicyField' => 'DifferentialCoreCustomField',
     'DifferentialFieldParseException' => 'Exception',
     'DifferentialFieldValidationException' => 'Exception',
     'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetAllDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetCommitPathsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetRevisionCommentsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialGetWorkingCopy' => 'Phobject',
     'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy',
     'DifferentialGitSVNIDField' => 'DifferentialCustomField',
     'DifferentialHarbormasterField' => 'DifferentialCustomField',
     'DifferentialHiddenComment' => 'DifferentialDAO',
     'DifferentialHostField' => 'DifferentialCustomField',
     'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy',
     'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy',
     'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'DifferentialHunk' => array(
       'DifferentialDAO',
       'PhabricatorPolicyInterface',
     ),
     'DifferentialHunkParser' => 'Phobject',
     'DifferentialHunkParserTestCase' => 'PhabricatorTestCase',
     'DifferentialHunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DifferentialHunkTestCase' => 'PhutilTestCase',
     'DifferentialInlineComment' => array(
       'Phobject',
       'PhabricatorInlineCommentInterface',
     ),
     'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController',
     'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
     'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
     'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
     'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
     'DifferentialLandingStrategy' => 'Phobject',
     'DifferentialLegacyHunk' => 'DifferentialHunk',
     'DifferentialLineAdjustmentMap' => 'Phobject',
     'DifferentialLintField' => 'DifferentialHarbormasterField',
     'DifferentialLintStatus' => 'Phobject',
     'DifferentialLocalCommitsView' => 'AphrontView',
     'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
     'DifferentialModernHunk' => 'DifferentialHunk',
     'DifferentialNextStepField' => 'DifferentialCustomField',
     'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
     'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
     'DifferentialPathField' => 'DifferentialCustomField',
     'DifferentialProjectReviewersField' => 'DifferentialCustomField',
     'DifferentialProjectsField' => 'DifferentialCoreCustomField',
     'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialQueryDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialRawDiffRenderer' => 'Phobject',
     'DifferentialReleephRequestFieldSpecification' => 'Phobject',
     'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
     'DifferentialRepositoryLookup' => 'Phobject',
     'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
     'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
     'DifferentialReviewedByField' => 'DifferentialCoreCustomField',
     'DifferentialReviewer' => 'Phobject',
     'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
     'DifferentialReviewerStatus' => 'Phobject',
     'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
     'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
     'DifferentialReviewersAddReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
     'DifferentialReviewersAddSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
     'DifferentialReviewersField' => 'DifferentialCoreCustomField',
     'DifferentialReviewersHeraldAction' => 'HeraldAction',
     'DifferentialReviewersView' => 'AphrontView',
     'DifferentialRevision' => array(
       'DifferentialDAO',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorExtendedPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhrequentTrackableInterface',
       'HarbormasterBuildableInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorProjectInterface',
       'PhabricatorFulltextInterface',
     ),
     'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionCloseDetailsController' => 'DifferentialController',
     'DifferentialRevisionContentAddedHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionContentHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionContentRemovedHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionControlSystem' => 'Phobject',
     'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType',
     'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType',
     'DifferentialRevisionEditController' => 'DifferentialController',
     'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine',
     'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType',
     'DifferentialRevisionHasReviewerEdgeType' => 'PhabricatorEdgeType',
     'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType',
     'DifferentialRevisionHeraldField' => 'HeraldField',
     'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup',
     'DifferentialRevisionIDField' => 'DifferentialCustomField',
     'DifferentialRevisionLandController' => 'DifferentialController',
     'DifferentialRevisionListController' => 'DifferentialController',
     'DifferentialRevisionListView' => 'AphrontView',
     'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver',
     'DifferentialRevisionOperationController' => 'DifferentialController',
     'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType',
     'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DifferentialRevisionStatus' => 'Phobject',
     'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField',
     'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
     'DifferentialRevisionViewController' => 'DifferentialController',
     'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialStoredCustomField' => 'DifferentialCustomField',
     'DifferentialSubscribersField' => 'DifferentialCoreCustomField',
     'DifferentialSummaryField' => 'DifferentialCoreCustomField',
     'DifferentialTestPlanField' => 'DifferentialCoreCustomField',
     'DifferentialTitleField' => 'DifferentialCoreCustomField',
     'DifferentialTransaction' => 'PhabricatorApplicationTransaction',
     'DifferentialTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView',
     'DifferentialUnitField' => 'DifferentialCustomField',
     'DifferentialUnitStatus' => 'Phobject',
     'DifferentialUnitTestResult' => 'Phobject',
     'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
     'DifferentialViewPolicyField' => 'DifferentialCoreCustomField',
     'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction',
     'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction',
     'DiffusionAuditorsHeraldAction' => 'HeraldAction',
     'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionBlameQuery' => 'DiffusionQuery',
     'DiffusionBlockHeraldAction' => 'HeraldAction',
     'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionBranchTableController' => 'DiffusionController',
     'DiffusionBranchTableView' => 'DiffusionView',
     'DiffusionBrowseController' => 'DiffusionController',
     'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionBrowseResultSet' => 'Phobject',
     'DiffusionBrowseTableView' => 'DiffusionView',
     'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionChangeController' => 'DiffusionController',
     'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup',
     'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitAuthorHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitBranchesController' => 'DiffusionController',
     'DiffusionCommitBranchesHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitCommitterHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitController' => 'DiffusionController',
     'DiffusionCommitDiffContentAddedHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitDiffContentHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitDiffContentRemovedHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitDiffEnormousHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitEditController' => 'DiffusionController',
     'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine',
     'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType',
     'DiffusionCommitHasTaskEdgeType' => 'PhabricatorEdgeType',
     'DiffusionCommitHash' => 'Phobject',
     'DiffusionCommitHeraldField' => 'HeraldField',
     'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup',
     'DiffusionCommitHookEngine' => 'Phobject',
     'DiffusionCommitHookRejectException' => 'Exception',
     'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitPackageHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitPackageOwnerHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitParentsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DiffusionCommitRef' => 'Phobject',
     'DiffusionCommitRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'DiffusionCommitRemarkupRuleTestCase' => 'PhabricatorTestCase',
     'DiffusionCommitRepositoryHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRepositoryProjectsHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRevertedByCommitEdgeType' => 'PhabricatorEdgeType',
     'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType',
     'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField',
     'DiffusionCommitTagsController' => 'DiffusionController',
     'DiffusionConduitAPIMethod' => 'ConduitAPIMethod',
     'DiffusionController' => 'PhabricatorController',
     'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'DiffusionDiffController' => 'DiffusionController',
     'DiffusionDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery',
     'DiffusionDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
     'DiffusionEmptyResultView' => 'DiffusionView',
     'DiffusionExistsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionExternalController' => 'DiffusionController',
     'DiffusionExternalSymbolQuery' => 'Phobject',
     'DiffusionExternalSymbolsSource' => 'Phobject',
     'DiffusionFileContentQuery' => 'DiffusionQuery',
     'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionGitBlameQuery' => 'DiffusionBlameQuery',
     'DiffusionGitBranch' => 'Phobject',
     'DiffusionGitBranchTestCase' => 'PhabricatorTestCase',
     'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
     'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow',
     'DiffusionGitLFSResponse' => 'AphrontResponse',
     'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
     'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
     'DiffusionGitRequest' => 'DiffusionRequest',
     'DiffusionGitResponse' => 'AphrontResponse',
     'DiffusionGitSSHWorkflow' => 'DiffusionSSHWorkflow',
     'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
     'DiffusionHistoryController' => 'DiffusionController',
     'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionHistoryTableView' => 'DiffusionView',
     'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
     'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
     'DiffusionLastModifiedController' => 'DiffusionController',
     'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionLintController' => 'DiffusionController',
     'DiffusionLintCountQuery' => 'PhabricatorQuery',
     'DiffusionLintSaveRunner' => 'Phobject',
     'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelMercurialPathsQueryTests' => 'PhabricatorTestCase',
     'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionLowLevelQuery' => 'Phobject',
     'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery',
     'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery',
     'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery',
     'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery',
     'DiffusionMercurialRequest' => 'DiffusionRequest',
     'DiffusionMercurialResponse' => 'AphrontResponse',
     'DiffusionMercurialSSHWorkflow' => 'DiffusionSSHWorkflow',
     'DiffusionMercurialServeSSHWorkflow' => 'DiffusionMercurialSSHWorkflow',
     'DiffusionMercurialWireClientSSHProtocolChannel' => 'PhutilProtocolChannel',
     'DiffusionMercurialWireProtocol' => 'Phobject',
     'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase',
     'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase',
     'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionMirrorDeleteController' => 'DiffusionController',
     'DiffusionMirrorEditController' => 'DiffusionController',
     'DiffusionPathChange' => 'Phobject',
     'DiffusionPathChangeQuery' => 'Phobject',
     'DiffusionPathCompleteController' => 'DiffusionController',
     'DiffusionPathIDQuery' => 'Phobject',
     'DiffusionPathQuery' => 'Phobject',
     'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
     'DiffusionPathTreeController' => 'DiffusionController',
     'DiffusionPathValidateController' => 'DiffusionController',
     'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource',
     'DiffusionPreCommitContentAffectedFilesHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentAuthorHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentAuthorRawHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentBranchesHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentCommitterRawHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentDiffContentHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentDiffEnormousHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentHeraldField' => 'HeraldField',
     'DiffusionPreCommitContentMergeHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentMessageHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentPusherHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentPusherProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRepositoryHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRevisionHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'DiffusionPreCommitContentHeraldField',
     'DiffusionPreCommitRefChangeHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefHeraldField' => 'HeraldField',
     'DiffusionPreCommitRefHeraldFieldGroup' => 'HeraldFieldGroup',
     'DiffusionPreCommitRefNameHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefPusherHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefPusherProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefRepositoryHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPreCommitRefTypeHeraldField' => 'DiffusionPreCommitRefHeraldField',
     'DiffusionPullEventGarbageCollector' => 'PhabricatorGarbageCollector',
     'DiffusionPushCapability' => 'PhabricatorPolicyCapability',
     'DiffusionPushEventViewController' => 'DiffusionPushLogController',
     'DiffusionPushLogController' => 'DiffusionController',
     'DiffusionPushLogListController' => 'DiffusionPushLogController',
     'DiffusionPushLogListView' => 'AphrontView',
     'DiffusionPythonExternalSymbolsSource' => 'DiffusionExternalSymbolsSource',
     'DiffusionQuery' => 'PhabricatorQuery',
     'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionRawDiffQuery' => 'DiffusionQuery',
     'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionReadmeView' => 'DiffusionView',
     'DiffusionRefDatasource' => 'PhabricatorTypeaheadDatasource',
     'DiffusionRefNotFoundException' => 'Exception',
     'DiffusionRefTableController' => 'DiffusionController',
     'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionRenameHistoryQuery' => 'Phobject',
     'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'DiffusionRepositoryController' => 'DiffusionController',
     'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
     'DiffusionRepositoryDefaultController' => 'DiffusionController',
     'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditController' => 'DiffusionController',
     'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryListController' => 'DiffusionController',
     'DiffusionRepositoryNewController' => 'DiffusionController',
     'DiffusionRepositoryPath' => 'Phobject',
     'DiffusionRepositoryRef' => 'Phobject',
     'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryTag' => 'Phobject',
     'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
     'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
     'DiffusionRequest' => 'Phobject',
     'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionResolveUserQuery' => 'Phobject',
     'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
     'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionServeController' => 'DiffusionController',
     'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
     'DiffusionSetupException' => 'Exception',
     'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
     'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
     'DiffusionSubversionWireProtocol' => 'Phobject',
     'DiffusionSubversionWireProtocolTestCase' => 'PhabricatorTestCase',
     'DiffusionSvnBlameQuery' => 'DiffusionBlameQuery',
     'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery',
     'DiffusionSvnRawDiffQuery' => 'DiffusionRawDiffQuery',
     'DiffusionSvnRequest' => 'DiffusionRequest',
     'DiffusionSymbolController' => 'DiffusionController',
     'DiffusionSymbolDatasource' => 'PhabricatorTypeaheadDatasource',
     'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
     'DiffusionTagListController' => 'DiffusionController',
     'DiffusionTagListView' => 'DiffusionView',
     'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
     'DiffusionURITestCase' => 'PhutilTestCase',
     'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod',
     'DiffusionView' => 'AphrontView',
     'DivinerArticleAtomizer' => 'DivinerAtomizer',
     'DivinerAtom' => 'Phobject',
     'DivinerAtomCache' => 'DivinerDiskCache',
     'DivinerAtomController' => 'DivinerController',
     'DivinerAtomListController' => 'DivinerController',
     'DivinerAtomPHIDType' => 'PhabricatorPHIDType',
     'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DivinerAtomRef' => 'Phobject',
     'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DivinerAtomizeWorkflow' => 'DivinerWorkflow',
     'DivinerAtomizer' => 'Phobject',
     'DivinerBookController' => 'DivinerController',
     'DivinerBookDatasource' => 'PhabricatorTypeaheadDatasource',
     'DivinerBookEditController' => 'DivinerController',
     'DivinerBookItemView' => 'AphrontTagView',
     'DivinerBookPHIDType' => 'PhabricatorPHIDType',
     'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DivinerController' => 'PhabricatorController',
     'DivinerDAO' => 'PhabricatorLiskDAO',
     'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'DivinerDefaultRenderer' => 'DivinerRenderer',
     'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'DivinerDiskCache' => 'Phobject',
     'DivinerFileAtomizer' => 'DivinerAtomizer',
     'DivinerFindController' => 'DivinerController',
     'DivinerGenerateWorkflow' => 'DivinerWorkflow',
     'DivinerLiveAtom' => 'DivinerDAO',
     'DivinerLiveBook' => array(
       'DivinerDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFulltextInterface',
     ),
     'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor',
     'DivinerLiveBookFulltextEngine' => 'PhabricatorFulltextEngine',
     'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction',
     'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'DivinerLivePublisher' => 'DivinerPublisher',
     'DivinerLiveSymbol' => array(
       'DivinerDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorFulltextInterface',
     ),
     'DivinerLiveSymbolFulltextEngine' => 'PhabricatorFulltextEngine',
     'DivinerMainController' => 'DivinerController',
     'DivinerPHPAtomizer' => 'DivinerAtomizer',
     'DivinerParameterTableView' => 'AphrontTagView',
     'DivinerPublishCache' => 'DivinerDiskCache',
     'DivinerPublisher' => 'Phobject',
     'DivinerRenderer' => 'Phobject',
     'DivinerReturnTableView' => 'AphrontTagView',
     'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'DivinerSectionView' => 'AphrontTagView',
     'DivinerStaticPublisher' => 'DivinerPublisher',
     'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
     'DivinerWorkflow' => 'PhabricatorManagementWorkflow',
     'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker',
     'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule',
     'DoorkeeperBridge' => 'Phobject',
     'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
     'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
     'DoorkeeperBridgeGitHubIssue' => 'DoorkeeperBridgeGitHub',
     'DoorkeeperBridgeGitHubUser' => 'DoorkeeperBridgeGitHub',
     'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge',
     'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase',
     'DoorkeeperBridgedObjectCurtainExtension' => 'PHUICurtainExtension',
     'DoorkeeperDAO' => 'PhabricatorLiskDAO',
     'DoorkeeperExternalObject' => array(
       'DoorkeeperDAO',
       'PhabricatorPolicyInterface',
     ),
     'DoorkeeperExternalObjectPHIDType' => 'PhabricatorPHIDType',
     'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DoorkeeperFeedStoryPublisher' => 'Phobject',
     'DoorkeeperFeedWorker' => 'FeedPushWorker',
     'DoorkeeperImportEngine' => 'Phobject',
     'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker',
     'DoorkeeperJIRARemarkupRule' => 'DoorkeeperRemarkupRule',
     'DoorkeeperMissingLinkException' => 'Exception',
     'DoorkeeperObjectRef' => 'Phobject',
     'DoorkeeperRemarkupRule' => 'PhutilRemarkupRule',
     'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'DoorkeeperTagView' => 'AphrontView',
     'DoorkeeperTagsController' => 'PhabricatorController',
     'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
     'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
     'DrydockAuthorization' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockAuthorizationAuthorizeController' => 'DrydockController',
     'DrydockAuthorizationListController' => 'DrydockController',
     'DrydockAuthorizationListView' => 'AphrontView',
     'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType',
     'DrydockAuthorizationQuery' => 'DrydockQuery',
     'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockAuthorizationViewController' => 'DrydockController',
     'DrydockBlueprint' => array(
       'DrydockDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorNgramsInterface',
       'PhabricatorProjectInterface',
     ),
     'DrydockBlueprintController' => 'DrydockController',
     'DrydockBlueprintCoreCustomField' => array(
       'DrydockBlueprintCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'DrydockBlueprintCustomField' => 'PhabricatorCustomField',
     'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource',
     'DrydockBlueprintDisableController' => 'DrydockBlueprintController',
     'DrydockBlueprintEditController' => 'DrydockBlueprintController',
     'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine',
     'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor',
     'DrydockBlueprintImplementation' => 'Phobject',
     'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase',
     'DrydockBlueprintListController' => 'DrydockBlueprintController',
     'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams',
     'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType',
     'DrydockBlueprintQuery' => 'DrydockQuery',
     'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction',
     'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'DrydockBlueprintViewController' => 'DrydockBlueprintController',
     'DrydockCommand' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockCommandError' => 'Phobject',
     'DrydockCommandInterface' => 'DrydockInterface',
     'DrydockCommandQuery' => 'DrydockQuery',
     'DrydockConsoleController' => 'DrydockController',
     'DrydockConstants' => 'Phobject',
     'DrydockController' => 'PhabricatorController',
     'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability',
     'DrydockDAO' => 'PhabricatorLiskDAO',
     'DrydockDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'DrydockFilesystemInterface' => 'DrydockInterface',
     'DrydockInterface' => 'Phobject',
     'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType',
     'DrydockLease' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockLeaseAcquiredLogType' => 'DrydockLogType',
     'DrydockLeaseActivatedLogType' => 'DrydockLogType',
     'DrydockLeaseActivationFailureLogType' => 'DrydockLogType',
     'DrydockLeaseActivationYieldLogType' => 'DrydockLogType',
     'DrydockLeaseController' => 'DrydockController',
     'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource',
     'DrydockLeaseDestroyedLogType' => 'DrydockLogType',
     'DrydockLeaseListController' => 'DrydockLeaseController',
     'DrydockLeaseListView' => 'AphrontView',
     'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType',
     'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType',
     'DrydockLeasePHIDType' => 'PhabricatorPHIDType',
     'DrydockLeaseQuery' => 'DrydockQuery',
     'DrydockLeaseQueuedLogType' => 'DrydockLogType',
     'DrydockLeaseReclaimLogType' => 'DrydockLogType',
     'DrydockLeaseReleaseController' => 'DrydockLeaseController',
     'DrydockLeaseReleasedLogType' => 'DrydockLogType',
     'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockLeaseStatus' => 'DrydockConstants',
     'DrydockLeaseUpdateWorker' => 'DrydockWorker',
     'DrydockLeaseViewController' => 'DrydockLeaseController',
     'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType',
     'DrydockLog' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockLogController' => 'DrydockController',
     'DrydockLogGarbageCollector' => 'PhabricatorGarbageCollector',
     'DrydockLogListController' => 'DrydockLogController',
     'DrydockLogListView' => 'AphrontView',
     'DrydockLogQuery' => 'DrydockQuery',
     'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockLogType' => 'Phobject',
     'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementReclaimWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'DrydockObjectAuthorizationView' => 'AphrontView',
     'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DrydockRepositoryOperation' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockRepositoryOperationController' => 'DrydockController',
     'DrydockRepositoryOperationDismissController' => 'DrydockRepositoryOperationController',
     'DrydockRepositoryOperationListController' => 'DrydockRepositoryOperationController',
     'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType',
     'DrydockRepositoryOperationQuery' => 'DrydockQuery',
     'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockRepositoryOperationStatusController' => 'DrydockRepositoryOperationController',
     'DrydockRepositoryOperationStatusView' => 'AphrontView',
     'DrydockRepositoryOperationType' => 'Phobject',
     'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker',
     'DrydockRepositoryOperationViewController' => 'DrydockRepositoryOperationController',
     'DrydockResource' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
     ),
     'DrydockResourceActivationFailureLogType' => 'DrydockLogType',
     'DrydockResourceActivationYieldLogType' => 'DrydockLogType',
     'DrydockResourceController' => 'DrydockController',
     'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource',
     'DrydockResourceListController' => 'DrydockResourceController',
     'DrydockResourceListView' => 'AphrontView',
     'DrydockResourcePHIDType' => 'PhabricatorPHIDType',
     'DrydockResourceQuery' => 'DrydockQuery',
     'DrydockResourceReclaimLogType' => 'DrydockLogType',
     'DrydockResourceReleaseController' => 'DrydockResourceController',
     'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockResourceStatus' => 'DrydockConstants',
     'DrydockResourceUpdateWorker' => 'DrydockWorker',
     'DrydockResourceViewController' => 'DrydockResourceController',
     'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
     'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
     'DrydockSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'DrydockSlotLock' => 'DrydockDAO',
     'DrydockSlotLockException' => 'Exception',
     'DrydockSlotLockFailureLogType' => 'DrydockLogType',
     'DrydockTestRepositoryOperation' => 'DrydockRepositoryOperationType',
     'DrydockWebrootInterface' => 'DrydockInterface',
     'DrydockWorker' => 'PhabricatorWorker',
     'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
     'FeedConduitAPIMethod' => 'ConduitAPIMethod',
     'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod',
     'FeedPublisherHTTPWorker' => 'FeedPushWorker',
     'FeedPublisherWorker' => 'FeedPushWorker',
     'FeedPushWorker' => 'PhabricatorWorker',
     'FeedQueryConduitAPIMethod' => 'FeedConduitAPIMethod',
     'FeedStoryNotificationGarbageCollector' => 'PhabricatorGarbageCollector',
     'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileConduitAPIMethod' => 'ConduitAPIMethod',
     'FileCreateMailReceiver' => 'PhabricatorMailReceiver',
     'FileDownloadConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileInfoConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileMailReceiver' => 'PhabricatorObjectMailReceiver',
     'FileQueryChunksConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'FileUploadChunkConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileUploadConduitAPIMethod' => 'FileConduitAPIMethod',
     'FileUploadHashConduitAPIMethod' => 'FileConduitAPIMethod',
     'FilesDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'FlagConduitAPIMethod' => 'ConduitAPIMethod',
     'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod',
     'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod',
     'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod',
     'FundBacker' => array(
       'FundDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
     ),
     'FundBackerCart' => 'PhortuneCartImplementation',
     'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor',
     'FundBackerListController' => 'FundController',
     'FundBackerPHIDType' => 'PhabricatorPHIDType',
     'FundBackerProduct' => 'PhortuneProductImplementation',
     'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'FundBackerTransaction' => 'PhabricatorApplicationTransaction',
     'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'FundController' => 'PhabricatorController',
     'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability',
     'FundDAO' => 'PhabricatorLiskDAO',
     'FundDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'FundInitiative' => array(
       'FundDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorProjectInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorFulltextInterface',
     ),
     'FundInitiativeBackController' => 'FundController',
     'FundInitiativeCloseController' => 'FundController',
     'FundInitiativeCommentController' => 'FundController',
     'FundInitiativeEditController' => 'FundController',
     'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor',
     'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine',
     'FundInitiativeListController' => 'FundController',
     'FundInitiativePHIDType' => 'PhabricatorPHIDType',
     'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction',
     'FundInitiativeTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'FundInitiativeViewController' => 'FundController',
     'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterArtifact' => 'Phobject',
     'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase',
     'HarbormasterBuild' => array(
       'HarbormasterDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'HarbormasterBuildAbortedException' => 'Exception',
     'HarbormasterBuildActionController' => 'HarbormasterController',
     'HarbormasterBuildArcanistAutoplan' => 'HarbormasterBuildAutoplan',
     'HarbormasterBuildArtifact' => array(
       'HarbormasterDAO',
       'PhabricatorPolicyInterface',
     ),
     'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildAutoplan' => 'Phobject',
     'HarbormasterBuildCommand' => 'HarbormasterDAO',
     'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
     'HarbormasterBuildEngine' => 'Phobject',
     'HarbormasterBuildFailureException' => 'Exception',
     'HarbormasterBuildGraph' => 'AbstractDirectedGraph',
     'HarbormasterBuildLintMessage' => 'HarbormasterDAO',
     'HarbormasterBuildLog' => array(
       'HarbormasterDAO',
       'PhabricatorPolicyInterface',
     ),
     'HarbormasterBuildLogChunk' => 'HarbormasterDAO',
     'HarbormasterBuildLogChunkIterator' => 'PhutilBufferedIterator',
     'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildMessage' => array(
       'HarbormasterDAO',
       'PhabricatorPolicyInterface',
     ),
     'HarbormasterBuildMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildPlan' => array(
       'HarbormasterDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorNgramsInterface',
       'PhabricatorProjectInterface',
     ),
     'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
     'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine',
     'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
     'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams',
     'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
     'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildRequest' => 'Phobject',
     'HarbormasterBuildStep' => array(
       'HarbormasterDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorCustomFieldInterface',
     ),
     'HarbormasterBuildStepCoreCustomField' => array(
       'HarbormasterBuildStepCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
     'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
     'HarbormasterBuildStepGroup' => 'Phobject',
     'HarbormasterBuildStepImplementation' => 'Phobject',
     'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase',
     'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
     'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HarbormasterBuildTarget' => array(
       'HarbormasterDAO',
       'PhabricatorPolicyInterface',
     ),
     'HarbormasterBuildTargetPHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildTransaction' => 'PhabricatorApplicationTransaction',
     'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HarbormasterBuildUnitMessage' => 'HarbormasterDAO',
     'HarbormasterBuildViewController' => 'HarbormasterController',
     'HarbormasterBuildWorker' => 'HarbormasterWorker',
     'HarbormasterBuildable' => array(
       'HarbormasterDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'HarbormasterBuildableInterface',
     ),
     'HarbormasterBuildableActionController' => 'HarbormasterController',
     'HarbormasterBuildableListController' => 'HarbormasterController',
     'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType',
     'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction',
     'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HarbormasterBuildableViewController' => 'HarbormasterController',
     'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterCircleCIHookController' => 'HarbormasterController',
     'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
     'HarbormasterController' => 'PhabricatorController',
     'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
     'HarbormasterCreatePlansCapability' => 'PhabricatorPolicyCapability',
     'HarbormasterDAO' => 'PhabricatorLiskDAO',
     'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
     'HarbormasterExecFuture' => 'Future',
     'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterFileArtifact' => 'HarbormasterArtifact',
     'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
     'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterLintMessagesController' => 'HarbormasterController',
     'HarbormasterLintPropertyView' => 'AphrontView',
     'HarbormasterManagementArchiveLogsWorkflow' => 'HarbormasterManagementWorkflow',
     'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
     'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
     'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'HarbormasterMessageType' => 'Phobject',
     'HarbormasterObject' => 'HarbormasterDAO',
     'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterPlanController' => 'HarbormasterController',
     'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
     'HarbormasterPlanEditController' => 'HarbormasterPlanController',
     'HarbormasterPlanListController' => 'HarbormasterPlanController',
     'HarbormasterPlanRunController' => 'HarbormasterPlanController',
     'HarbormasterPlanViewController' => 'HarbormasterPlanController',
     'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
     'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
     'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
     'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
     'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'HarbormasterScratchTable' => 'HarbormasterDAO',
     'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
     'HarbormasterSleepBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterStepAddController' => 'HarbormasterPlanController',
     'HarbormasterStepDeleteController' => 'HarbormasterPlanController',
     'HarbormasterStepEditController' => 'HarbormasterPlanController',
     'HarbormasterStepViewController' => 'HarbormasterPlanController',
     'HarbormasterTargetEngine' => 'Phobject',
     'HarbormasterTargetWorker' => 'HarbormasterWorker',
     'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
     'HarbormasterUIEventListener' => 'PhabricatorEventListener',
     'HarbormasterURIArtifact' => 'HarbormasterArtifact',
     'HarbormasterUnitMessageListController' => 'HarbormasterController',
     'HarbormasterUnitMessageViewController' => 'HarbormasterController',
     'HarbormasterUnitPropertyView' => 'AphrontView',
     'HarbormasterUnitStatus' => 'Phobject',
     'HarbormasterUnitSummaryView' => 'AphrontView',
     'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterWorker' => 'PhabricatorWorker',
     'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact',
     'HeraldAction' => 'Phobject',
     'HeraldActionGroup' => 'HeraldGroup',
     'HeraldActionRecord' => 'HeraldDAO',
     'HeraldAdapter' => 'Phobject',
     'HeraldAlwaysField' => 'HeraldField',
     'HeraldAnotherRuleField' => 'HeraldField',
     'HeraldApplicationActionGroup' => 'HeraldActionGroup',
     'HeraldApplyTranscript' => 'Phobject',
     'HeraldBasicFieldGroup' => 'HeraldFieldGroup',
     'HeraldCommitAdapter' => array(
       'HeraldAdapter',
       'HarbormasterBuildableAdapterInterface',
     ),
     'HeraldCondition' => 'HeraldDAO',
     'HeraldConditionTranscript' => 'Phobject',
     'HeraldContentSourceField' => 'HeraldField',
     'HeraldController' => 'PhabricatorController',
     'HeraldDAO' => 'PhabricatorLiskDAO',
     'HeraldDifferentialAdapter' => 'HeraldAdapter',
     'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter',
     'HeraldDifferentialRevisionAdapter' => array(
       'HeraldDifferentialAdapter',
       'HarbormasterBuildableAdapterInterface',
     ),
     'HeraldDisableController' => 'HeraldController',
     'HeraldDoNothingAction' => 'HeraldAction',
     'HeraldEditFieldGroup' => 'HeraldFieldGroup',
     'HeraldEffect' => 'Phobject',
     'HeraldEmptyFieldValue' => 'HeraldFieldValue',
     'HeraldEngine' => 'Phobject',
     'HeraldExactProjectsField' => 'HeraldField',
     'HeraldField' => 'Phobject',
     'HeraldFieldGroup' => 'HeraldGroup',
     'HeraldFieldTestCase' => 'PhutilTestCase',
     'HeraldFieldValue' => 'Phobject',
     'HeraldGroup' => 'Phobject',
     'HeraldInvalidActionException' => 'Exception',
     'HeraldInvalidConditionException' => 'Exception',
     'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability',
     'HeraldManiphestTaskAdapter' => 'HeraldAdapter',
     'HeraldNewController' => 'HeraldController',
     'HeraldNewObjectField' => 'HeraldField',
     'HeraldNotifyActionGroup' => 'HeraldActionGroup',
     'HeraldObjectTranscript' => 'Phobject',
     'HeraldPhameBlogAdapter' => 'HeraldAdapter',
     'HeraldPhamePostAdapter' => 'HeraldAdapter',
     'HeraldPholioMockAdapter' => 'HeraldAdapter',
     'HeraldPonderQuestionAdapter' => 'HeraldAdapter',
     'HeraldPreCommitAdapter' => 'HeraldAdapter',
     'HeraldPreCommitContentAdapter' => 'HeraldPreCommitAdapter',
     'HeraldPreCommitRefAdapter' => 'HeraldPreCommitAdapter',
     'HeraldPreventActionGroup' => 'HeraldActionGroup',
     'HeraldProjectsField' => 'HeraldField',
     'HeraldRecursiveConditionsException' => 'Exception',
     'HeraldRelatedFieldGroup' => 'HeraldFieldGroup',
     'HeraldRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'HeraldRepetitionPolicyConfig' => 'Phobject',
     'HeraldRule' => array(
       'HeraldDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSubscribableInterface',
     ),
     'HeraldRuleController' => 'HeraldController',
     'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
     'HeraldRuleListController' => 'HeraldController',
     'HeraldRulePHIDType' => 'PhabricatorPHIDType',
     'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'HeraldRuleSerializer' => 'Phobject',
     'HeraldRuleTestCase' => 'PhabricatorTestCase',
     'HeraldRuleTransaction' => 'PhabricatorApplicationTransaction',
     'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'HeraldRuleTranscript' => 'Phobject',
     'HeraldRuleTypeConfig' => 'Phobject',
     'HeraldRuleViewController' => 'HeraldController',
     'HeraldSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'HeraldSelectFieldValue' => 'HeraldFieldValue',
     'HeraldSpaceField' => 'HeraldField',
     'HeraldSubscribersField' => 'HeraldField',
     'HeraldSupportActionGroup' => 'HeraldActionGroup',
     'HeraldSupportFieldGroup' => 'HeraldFieldGroup',
     'HeraldTestConsoleController' => 'HeraldController',
     'HeraldTextFieldValue' => 'HeraldFieldValue',
     'HeraldTokenizerFieldValue' => 'HeraldFieldValue',
     'HeraldTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HeraldTranscript' => array(
       'HeraldDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'HeraldTranscriptController' => 'HeraldController',
     'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector',
     'HeraldTranscriptListController' => 'HeraldController',
     'HeraldTranscriptPHIDType' => 'PhabricatorPHIDType',
     'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'HeraldTranscriptTestCase' => 'PhabricatorTestCase',
     'HeraldUtilityActionGroup' => 'HeraldActionGroup',
     'Javelin' => 'Phobject',
     'JavelinReactorUIExample' => 'PhabricatorUIExample',
     'JavelinUIExample' => 'PhabricatorUIExample',
     'JavelinViewExampleServerView' => 'AphrontView',
     'JavelinViewUIExample' => 'PhabricatorUIExample',
     'LegalpadController' => 'PhabricatorController',
     'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability',
     'LegalpadDAO' => 'PhabricatorLiskDAO',
     'LegalpadDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'LegalpadDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'LegalpadDocument' => array(
       'LegalpadDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
     ),
     'LegalpadDocumentBody' => array(
       'LegalpadDAO',
       'PhabricatorMarkupInterface',
     ),
     'LegalpadDocumentCommentController' => 'LegalpadController',
     'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource',
     'LegalpadDocumentDoneController' => 'LegalpadController',
     'LegalpadDocumentEditController' => 'LegalpadController',
     'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor',
     'LegalpadDocumentListController' => 'LegalpadController',
     'LegalpadDocumentManageController' => 'LegalpadController',
     'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'LegalpadDocumentSignController' => 'LegalpadController',
     'LegalpadDocumentSignature' => array(
       'LegalpadDAO',
       'PhabricatorPolicyInterface',
     ),
     'LegalpadDocumentSignatureAddController' => 'LegalpadController',
     'LegalpadDocumentSignatureListController' => 'LegalpadController',
     'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'LegalpadDocumentSignatureVerificationController' => 'LegalpadController',
     'LegalpadDocumentSignatureViewController' => 'LegalpadController',
     'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver',
     'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType',
     'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'LegalpadRequireSignatureHeraldAction' => 'HeraldAction',
     'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType',
     'LegalpadTransaction' => 'PhabricatorApplicationTransaction',
     'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView',
     'LiskChunkTestCase' => 'PhabricatorTestCase',
     'LiskDAO' => 'Phobject',
     'LiskDAOSet' => 'Phobject',
     'LiskDAOTestCase' => 'PhabricatorTestCase',
     'LiskEphemeralObjectException' => 'Exception',
     'LiskFixtureTestCase' => 'PhabricatorTestCase',
     'LiskIsolationTestCase' => 'PhabricatorTestCase',
     'LiskIsolationTestDAO' => 'LiskDAO',
     'LiskIsolationTestDAOException' => 'Exception',
     'LiskMigrationIterator' => 'PhutilBufferedIterator',
     'LiskRawMigrationIterator' => 'PhutilBufferedIterator',
     'MacroConduitAPIMethod' => 'ConduitAPIMethod',
     'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod',
     'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod',
     'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand',
     'ManiphestAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'ManiphestBatchEditController' => 'ManiphestController',
     'ManiphestBulkEditCapability' => 'PhabricatorPolicyCapability',
     'ManiphestClaimEmailCommand' => 'ManiphestEmailCommand',
     'ManiphestCloseEmailCommand' => 'ManiphestEmailCommand',
     'ManiphestConduitAPIMethod' => 'ConduitAPIMethod',
     'ManiphestConfiguredCustomField' => array(
       'ManiphestCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'ManiphestConstants' => 'Phobject',
     'ManiphestController' => 'PhabricatorController',
     'ManiphestCreateMailReceiver' => 'PhabricatorMailReceiver',
     'ManiphestCreateTaskConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestCustomField' => 'PhabricatorCustomField',
     'ManiphestCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
     'ManiphestCustomFieldStatusParser' => 'PhabricatorCustomFieldMonogramParser',
     'ManiphestCustomFieldStatusParserTestCase' => 'PhabricatorTestCase',
     'ManiphestCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
     'ManiphestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
     'ManiphestDAO' => 'PhabricatorLiskDAO',
     'ManiphestDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'ManiphestDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEditAssignCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'ManiphestEditEngine' => 'PhabricatorEditEngine',
     'ManiphestEditPoliciesCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEditPriorityCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEditProjectsCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEditStatusCapability' => 'PhabricatorPolicyCapability',
     'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'ManiphestExcelDefaultFormat' => 'ManiphestExcelFormat',
     'ManiphestExcelFormat' => 'Phobject',
     'ManiphestExcelFormatTestCase' => 'PhabricatorTestCase',
     'ManiphestExportController' => 'ManiphestController',
     'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestNameIndex' => 'ManiphestDAO',
     'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
     'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType',
     'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand',
     'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'ManiphestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'ManiphestReportController' => 'ManiphestController',
     'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType',
     'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand',
     'ManiphestSubpriorityController' => 'ManiphestController',
     'ManiphestTask' => array(
       'ManiphestDAO',
       'PhabricatorSubscribableInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorMentionableInterface',
       'PhrequentTrackableInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorProjectInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorConduitResultInterface',
       'PhabricatorFulltextInterface',
       'DoorkeeperBridgedObjectInterface',
     ),
     'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
     'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
     'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction',
     'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule',
     'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskDetailController' => 'ManiphestController',
     'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType',
     'ManiphestTaskEditController' => 'ManiphestController',
     'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
     'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskHasRevisionEdgeType' => 'PhabricatorEdgeType',
     'ManiphestTaskHeraldField' => 'HeraldField',
     'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup',
     'ManiphestTaskListController' => 'ManiphestController',
     'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
     'ManiphestTaskListView' => 'ManiphestView',
     'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
     'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
     'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
     'ManiphestTaskPoints' => 'Phobject',
     'ManiphestTaskPriority' => 'ManiphestConstants',
     'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
     'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ManiphestTaskResultListView' => 'ManiphestView',
     'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'ManiphestTaskStatus' => 'ManiphestConstants',
     'ManiphestTaskStatusDatasource' => 'PhabricatorTypeaheadDatasource',
     'ManiphestTaskStatusFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'ManiphestTaskStatusHeraldAction' => 'HeraldAction',
     'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase',
     'ManiphestTaskTestCase' => 'PhabricatorTestCase',
     'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
     'ManiphestTransaction' => 'PhabricatorApplicationTransaction',
     'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'ManiphestUpdateConduitAPIMethod' => 'ManiphestConduitAPIMethod',
     'ManiphestView' => 'AphrontView',
     'MetaMTAEmailTransactionCommand' => 'Phobject',
     'MetaMTAEmailTransactionCommandTestCase' => 'PhabricatorTestCase',
     'MetaMTAMailReceivedGarbageCollector' => 'PhabricatorGarbageCollector',
     'MetaMTAMailSentGarbageCollector' => 'PhabricatorGarbageCollector',
     'MetaMTAReceivedMailStatus' => 'Phobject',
     'MultimeterContext' => 'MultimeterDimension',
     'MultimeterControl' => 'Phobject',
     'MultimeterController' => 'PhabricatorController',
     'MultimeterDAO' => 'PhabricatorLiskDAO',
     'MultimeterDimension' => 'MultimeterDAO',
     'MultimeterEvent' => 'MultimeterDAO',
     'MultimeterEventGarbageCollector' => 'PhabricatorGarbageCollector',
     'MultimeterHost' => 'MultimeterDimension',
     'MultimeterLabel' => 'MultimeterDimension',
     'MultimeterSampleController' => 'MultimeterController',
     'MultimeterViewer' => 'MultimeterDimension',
     'NuanceConduitAPIMethod' => 'ConduitAPIMethod',
     'NuanceConsoleController' => 'NuanceController',
     'NuanceContentSource' => 'PhabricatorContentSource',
     'NuanceController' => 'PhabricatorController',
     'NuanceDAO' => 'PhabricatorLiskDAO',
     'NuanceGitHubEventItemType' => 'NuanceItemType',
     'NuanceGitHubImportCursor' => 'NuanceImportCursor',
     'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor',
     'NuanceGitHubRawEvent' => 'Phobject',
     'NuanceGitHubRawEventTestCase' => 'PhabricatorTestCase',
     'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor',
     'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition',
     'NuanceImportCursor' => 'Phobject',
     'NuanceImportCursorData' => array(
       'NuanceDAO',
       'PhabricatorPolicyInterface',
     ),
     'NuanceImportCursorDataQuery' => 'NuanceQuery',
     'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType',
     'NuanceItem' => array(
       'NuanceDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
     ),
     'NuanceItemActionController' => 'NuanceController',
     'NuanceItemCommand' => array(
       'NuanceDAO',
       'PhabricatorPolicyInterface',
     ),
     'NuanceItemCommandQuery' => 'NuanceQuery',
     'NuanceItemController' => 'NuanceController',
     'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor',
     'NuanceItemListController' => 'NuanceItemController',
     'NuanceItemManageController' => 'NuanceController',
     'NuanceItemPHIDType' => 'PhabricatorPHIDType',
     'NuanceItemQuery' => 'NuanceQuery',
     'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'NuanceItemTransaction' => 'NuanceTransaction',
     'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'NuanceItemType' => 'Phobject',
     'NuanceItemUpdateWorker' => 'NuanceWorker',
     'NuanceItemViewController' => 'NuanceController',
     'NuanceManagementImportWorkflow' => 'NuanceManagementWorkflow',
     'NuanceManagementUpdateWorkflow' => 'NuanceManagementWorkflow',
     'NuanceManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition',
     'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'NuanceQueue' => array(
       'NuanceDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
     ),
     'NuanceQueueController' => 'NuanceController',
     'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource',
     'NuanceQueueEditController' => 'NuanceQueueController',
     'NuanceQueueEditEngine' => 'PhabricatorEditEngine',
     'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor',
     'NuanceQueueListController' => 'NuanceQueueController',
     'NuanceQueuePHIDType' => 'PhabricatorPHIDType',
     'NuanceQueueQuery' => 'NuanceQuery',
     'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'NuanceQueueTransaction' => 'NuanceTransaction',
     'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'NuanceQueueViewController' => 'NuanceQueueController',
     'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'NuanceSource' => array(
       'NuanceDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorNgramsInterface',
     ),
     'NuanceSourceActionController' => 'NuanceController',
     'NuanceSourceController' => 'NuanceController',
     'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'NuanceSourceDefinition' => 'Phobject',
     'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase',
     'NuanceSourceEditController' => 'NuanceSourceController',
     'NuanceSourceEditEngine' => 'PhabricatorEditEngine',
     'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor',
     'NuanceSourceListController' => 'NuanceSourceController',
     'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability',
     'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams',
     'NuanceSourcePHIDType' => 'PhabricatorPHIDType',
     'NuanceSourceQuery' => 'NuanceQuery',
     'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'NuanceSourceTransaction' => 'NuanceTransaction',
     'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'NuanceSourceViewController' => 'NuanceSourceController',
     'NuanceTransaction' => 'PhabricatorApplicationTransaction',
     'NuanceWorker' => 'PhabricatorWorker',
     'OwnersConduitAPIMethod' => 'ConduitAPIMethod',
     'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler',
     'OwnersQueryConduitAPIMethod' => 'OwnersConduitAPIMethod',
     'OwnersSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'PHIDConduitAPIMethod' => 'ConduitAPIMethod',
     'PHIDInfoConduitAPIMethod' => 'PHIDConduitAPIMethod',
     'PHIDLookupConduitAPIMethod' => 'PHIDConduitAPIMethod',
     'PHIDQueryConduitAPIMethod' => 'PHIDConduitAPIMethod',
     'PHUI' => 'Phobject',
     'PHUIActionPanelExample' => 'PhabricatorUIExample',
     'PHUIActionPanelView' => 'AphrontTagView',
     'PHUIApplicationMenuView' => 'Phobject',
     'PHUIBadgeBoxView' => 'AphrontTagView',
     'PHUIBadgeExample' => 'PhabricatorUIExample',
     'PHUIBadgeMiniView' => 'AphrontTagView',
     'PHUIBadgeView' => 'AphrontTagView',
     'PHUIBigInfoView' => 'AphrontTagView',
     'PHUIBoxExample' => 'PhabricatorUIExample',
     'PHUIBoxView' => 'AphrontTagView',
     'PHUIButtonBarExample' => 'PhabricatorUIExample',
     'PHUIButtonBarView' => 'AphrontTagView',
     'PHUIButtonExample' => 'PhabricatorUIExample',
     'PHUIButtonView' => 'AphrontTagView',
     'PHUICalendarDayView' => 'AphrontView',
     'PHUICalendarListView' => 'AphrontTagView',
     'PHUICalendarMonthView' => 'AphrontView',
     'PHUICalendarWidgetView' => 'AphrontTagView',
     'PHUIColorPalletteExample' => 'PhabricatorUIExample',
     'PHUICrumbView' => 'AphrontView',
     'PHUICrumbsView' => 'AphrontView',
     'PHUICurtainExtension' => 'Phobject',
     'PHUICurtainPanelView' => 'AphrontTagView',
     'PHUICurtainView' => 'AphrontTagView',
     'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView',
     'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView',
     'PHUIDiffInlineCommentRowScaffold' => 'AphrontView',
     'PHUIDiffInlineCommentTableScaffold' => 'AphrontView',
     'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView',
     'PHUIDiffInlineCommentView' => 'AphrontView',
     'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
     'PHUIDiffRevealIconView' => 'AphrontView',
     'PHUIDiffTableOfContentsItemView' => 'AphrontView',
     'PHUIDiffTableOfContentsListView' => 'AphrontView',
     'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
     'PHUIDocumentExample' => 'PhabricatorUIExample',
     'PHUIDocumentSummaryView' => 'AphrontTagView',
     'PHUIDocumentView' => 'AphrontTagView',
     'PHUIDocumentViewPro' => 'AphrontTagView',
     'PHUIFeedStoryExample' => 'PhabricatorUIExample',
     'PHUIFeedStoryView' => 'AphrontView',
     'PHUIFormDividerControl' => 'AphrontFormControl',
     'PHUIFormFreeformDateControl' => 'AphrontFormControl',
     'PHUIFormIconSetControl' => 'AphrontFormControl',
     'PHUIFormInsetView' => 'AphrontView',
     'PHUIFormLayoutView' => 'AphrontView',
     'PHUIFormMultiSubmitControl' => 'AphrontFormControl',
     'PHUIFormPageView' => 'AphrontView',
     'PHUIHandleListView' => 'AphrontTagView',
     'PHUIHandleTagListView' => 'AphrontTagView',
     'PHUIHandleView' => 'AphrontView',
     'PHUIHeadThingView' => 'AphrontTagView',
     'PHUIHeaderView' => 'AphrontTagView',
     'PHUIHovercardUIExample' => 'PhabricatorUIExample',
     'PHUIHovercardView' => 'AphrontTagView',
     'PHUIIconCircleView' => 'AphrontTagView',
     'PHUIIconExample' => 'PhabricatorUIExample',
     'PHUIIconView' => 'AphrontTagView',
     'PHUIImageMaskExample' => 'PhabricatorUIExample',
     'PHUIImageMaskView' => 'AphrontTagView',
     'PHUIInfoExample' => 'PhabricatorUIExample',
     'PHUIInfoPanelExample' => 'PhabricatorUIExample',
     'PHUIInfoPanelView' => 'AphrontView',
     'PHUIInfoView' => 'AphrontView',
     'PHUIListExample' => 'PhabricatorUIExample',
     'PHUIListItemView' => 'AphrontTagView',
     'PHUIListView' => 'AphrontTagView',
     'PHUIListViewTestCase' => 'PhabricatorTestCase',
     'PHUIMainMenuView' => 'AphrontView',
     'PHUIObjectBoxView' => 'AphrontTagView',
     'PHUIObjectItemListExample' => 'PhabricatorUIExample',
     'PHUIObjectItemListView' => 'AphrontTagView',
     'PHUIObjectItemView' => 'AphrontTagView',
     'PHUIPagedFormView' => 'AphrontView',
     'PHUIPagerView' => 'AphrontView',
     'PHUIPinboardItemView' => 'AphrontView',
     'PHUIPinboardView' => 'AphrontView',
     'PHUIPropertyGroupView' => 'AphrontTagView',
     'PHUIPropertyListExample' => 'PhabricatorUIExample',
     'PHUIPropertyListView' => 'AphrontView',
     'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
     'PHUIRemarkupView' => 'AphrontView',
     'PHUISegmentBarSegmentView' => 'AphrontTagView',
     'PHUISegmentBarView' => 'AphrontTagView',
     'PHUISpacesNamespaceContextView' => 'AphrontView',
     'PHUIStatusItemView' => 'AphrontTagView',
     'PHUIStatusListView' => 'AphrontTagView',
     'PHUITagExample' => 'PhabricatorUIExample',
     'PHUITagView' => 'AphrontTagView',
     'PHUITimelineEventView' => 'AphrontView',
     'PHUITimelineExample' => 'PhabricatorUIExample',
     'PHUITimelineView' => 'AphrontView',
     'PHUITwoColumnView' => 'AphrontTagView',
     'PHUITypeaheadExample' => 'PhabricatorUIExample',
     'PHUIWorkboardView' => 'AphrontTagView',
     'PHUIWorkpanelView' => 'AphrontTagView',
     'PassphraseAbstractKey' => 'Phobject',
     'PassphraseConduitAPIMethod' => 'ConduitAPIMethod',
     'PassphraseController' => 'PhabricatorController',
     'PassphraseCredential' => array(
       'PassphraseDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorFulltextInterface',
     ),
     'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule',
     'PassphraseCredentialConduitController' => 'PassphraseController',
     'PassphraseCredentialControl' => 'AphrontFormControl',
     'PassphraseCredentialCreateController' => 'PassphraseController',
     'PassphraseCredentialDestroyController' => 'PassphraseController',
     'PassphraseCredentialEditController' => 'PassphraseController',
     'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine',
     'PassphraseCredentialListController' => 'PassphraseController',
     'PassphraseCredentialLockController' => 'PassphraseController',
     'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType',
     'PassphraseCredentialPublicController' => 'PassphraseController',
     'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PassphraseCredentialRevealController' => 'PassphraseController',
     'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PassphraseCredentialTransaction' => 'PhabricatorApplicationTransaction',
     'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PassphraseCredentialType' => 'Phobject',
     'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase',
     'PassphraseCredentialViewController' => 'PassphraseController',
     'PassphraseDAO' => 'PhabricatorLiskDAO',
     'PassphraseDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'PassphraseDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PassphraseNoteCredentialType' => 'PassphraseCredentialType',
     'PassphrasePasswordCredentialType' => 'PassphraseCredentialType',
     'PassphrasePasswordKey' => 'PassphraseAbstractKey',
     'PassphraseQueryConduitAPIMethod' => 'PassphraseConduitAPIMethod',
     'PassphraseRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PassphraseSSHGeneratedKeyCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
     'PassphraseSSHKey' => 'PassphraseAbstractKey',
     'PassphraseSSHPrivateKeyCredentialType' => 'PassphraseCredentialType',
     'PassphraseSSHPrivateKeyFileCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
     'PassphraseSSHPrivateKeyTextCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
     'PassphraseSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PassphraseSecret' => 'PassphraseDAO',
     'PassphraseTokenCredentialType' => 'PassphraseCredentialType',
     'PasteConduitAPIMethod' => 'ConduitAPIMethod',
     'PasteCreateConduitAPIMethod' => 'PasteConduitAPIMethod',
     'PasteCreateMailReceiver' => 'PhabricatorMailReceiver',
     'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PasteEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'PasteEmbedView' => 'AphrontView',
     'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod',
     'PasteMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod',
     'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
     'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
     'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
     'Phabricator404Controller' => 'PhabricatorController',
     'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase',
     'PhabricatorAccessLog' => 'Phobject',
     'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorAccountSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorActionListView' => 'AphrontView',
     'PhabricatorActionView' => 'AphrontView',
     'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
     'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorAnchorView' => 'AphrontView',
     'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow',
     'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
     'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
     'PhabricatorAphlictManagementStatusWorkflow' => 'PhabricatorAphlictManagementWorkflow',
     'PhabricatorAphlictManagementStopWorkflow' => 'PhabricatorAphlictManagementWorkflow',
     'PhabricatorAphlictManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorAphlictSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorAphrontBarUIExample' => 'PhabricatorUIExample',
     'PhabricatorAphrontViewTestCase' => 'PhabricatorTestCase',
     'PhabricatorAppSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorApplication' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorApplicationApplicationPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorApplicationConfigOptions' => 'Phobject',
     'PhabricatorApplicationConfigurationPanel' => 'Phobject',
     'PhabricatorApplicationConfigurationPanelTestCase' => 'PhabricatorTestCase',
     'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
     'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
     'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView',
     'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
     'PhabricatorApplicationLaunchView' => 'AphrontTagView',
     'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
     'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
     'PhabricatorApplicationSearchEngine' => 'Phobject',
     'PhabricatorApplicationSearchEngineTestCase' => 'PhabricatorTestCase',
     'PhabricatorApplicationSearchResultView' => 'Phobject',
     'PhabricatorApplicationStatusView' => 'AphrontView',
     'PhabricatorApplicationTestCase' => 'PhabricatorTestCase',
     'PhabricatorApplicationTransaction' => array(
       'PhabricatorLiskDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorApplicationTransactionComment' => array(
       'PhabricatorLiskDAO',
       'PhabricatorMarkupInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorApplicationTransactionCommentEditController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor',
     'PhabricatorApplicationTransactionCommentHistoryController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorApplicationTransactionCommentQuoteController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionCommentRawController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionCommentRemoveController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionCommentView' => 'AphrontView',
     'PhabricatorApplicationTransactionController' => 'PhabricatorController',
     'PhabricatorApplicationTransactionDetailController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor',
     'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory',
     'PhabricatorApplicationTransactionNoEffectException' => 'Exception',
     'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse',
     'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker',
     'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorApplicationTransactionRemarkupPreviewController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler',
     'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse',
     'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionStructureException' => 'Exception',
     'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery',
     'PhabricatorApplicationTransactionTextDiffDetailView' => 'AphrontView',
     'PhabricatorApplicationTransactionTransactionPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorApplicationTransactionValidationError' => 'Phobject',
     'PhabricatorApplicationTransactionValidationException' => 'Exception',
     'PhabricatorApplicationTransactionValidationResponse' => 'AphrontProxyResponse',
     'PhabricatorApplicationTransactionValueController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorApplicationTransactionView' => 'AphrontView',
     'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController',
     'PhabricatorApplicationsApplication' => 'PhabricatorApplication',
     'PhabricatorApplicationsController' => 'PhabricatorController',
     'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
     'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorAuditActionConstants' => 'Phobject',
     'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController',
     'PhabricatorAuditApplication' => 'PhabricatorApplication',
     'PhabricatorAuditCommentEditor' => 'PhabricatorEditor',
     'PhabricatorAuditCommitStatusConstants' => 'Phobject',
     'PhabricatorAuditController' => 'PhabricatorController',
     'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorAuditInlineComment' => array(
       'Phobject',
       'PhabricatorInlineCommentInterface',
     ),
     'PhabricatorAuditListController' => 'PhabricatorAuditController',
     'PhabricatorAuditListView' => 'AphrontView',
     'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow',
     'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorAuditPreviewController' => 'PhabricatorAuditController',
     'PhabricatorAuditReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorAuditStatusConstants' => 'Phobject',
     'PhabricatorAuditTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorAuditTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorAuditTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorAuditTransactionView' => 'PhabricatorApplicationTransactionView',
     'PhabricatorAuthAccountView' => 'AphrontView',
     'PhabricatorAuthApplication' => 'PhabricatorApplication',
     'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod',
     'PhabricatorAuthConfirmLinkController' => 'PhabricatorAuthController',
     'PhabricatorAuthController' => 'PhabricatorController',
     'PhabricatorAuthDAO' => 'PhabricatorLiskDAO',
     'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController',
     'PhabricatorAuthDowngradeSessionController' => 'PhabricatorAuthController',
     'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
     'PhabricatorAuthFactor' => 'Phobject',
     'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
     'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
     'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
     'PhabricatorAuthHighSecurityRequiredException' => 'Exception',
     'PhabricatorAuthHighSecurityToken' => 'Phobject',
     'PhabricatorAuthInvite' => array(
       'PhabricatorUserDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorAuthInviteAccountException' => 'PhabricatorAuthInviteDialogException',
     'PhabricatorAuthInviteAction' => 'Phobject',
     'PhabricatorAuthInviteActionTableView' => 'AphrontView',
     'PhabricatorAuthInviteController' => 'PhabricatorAuthController',
     'PhabricatorAuthInviteDialogException' => 'PhabricatorAuthInviteException',
     'PhabricatorAuthInviteEngine' => 'Phobject',
     'PhabricatorAuthInviteException' => 'Exception',
     'PhabricatorAuthInviteInvalidException' => 'PhabricatorAuthInviteDialogException',
     'PhabricatorAuthInviteLoginException' => 'PhabricatorAuthInviteDialogException',
     'PhabricatorAuthInvitePHIDType' => 'PhabricatorPHIDType',
     'PhabricatorAuthInviteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorAuthInviteRegisteredException' => 'PhabricatorAuthInviteException',
     'PhabricatorAuthInviteSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorAuthInviteTestCase' => 'PhabricatorTestCase',
     'PhabricatorAuthInviteVerifyException' => 'PhabricatorAuthInviteDialogException',
     'PhabricatorAuthInviteWorker' => 'PhabricatorWorker',
     'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
     'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
     'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
     'PhabricatorAuthLoginHandler' => 'Phobject',
+    'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
     'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
     'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementUnlimitWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementVerifyWorkflow' => 'PhabricatorAuthManagementWorkflow',
     'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController',
     'PhabricatorAuthNeedsMultiFactorController' => 'PhabricatorAuthController',
     'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController',
     'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
     'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController',
     'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'PhabricatorAuthProvider' => 'Phobject',
     'PhabricatorAuthProviderConfig' => array(
       'PhabricatorAuthDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthController',
     'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
     'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
     'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
     'PhabricatorAuthSSHKey' => array(
       'PhabricatorAuthDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
     'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController',
     'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
     'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
     'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
     'PhabricatorAuthSSHPublicKey' => 'Phobject',
     'PhabricatorAuthSession' => array(
       'PhabricatorAuthDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorAuthSessionEngine' => 'Phobject',
+    'PhabricatorAuthSessionEngineExtension' => 'Phobject',
+    'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorAuthStartController' => 'PhabricatorAuthController',
     'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'PhabricatorAuthTemporaryToken' => array(
       'PhabricatorAuthDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorAuthTemporaryTokenType' => 'Phobject',
     'PhabricatorAuthTemporaryTokenTypeModule' => 'PhabricatorConfigModule',
     'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController',
     'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction',
     'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController',
     'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
     'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
     'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorBadgesApplication' => 'PhabricatorApplication',
     'PhabricatorBadgesArchiveController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesAward' => array(
       'PhabricatorBadgesDAO',
       'PhabricatorDestructibleInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorBadgesAwardController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesAwardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorBadgesBadge' => array(
       'PhabricatorBadgesDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorDestructibleInterface',
+      'PhabricatorConduitResultInterface',
+      'PhabricatorNgramsInterface',
     ),
+    'PhabricatorBadgesBadgeNameNgrams' => 'PhabricatorSearchNgrams',
     'PhabricatorBadgesCommentController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesController' => 'PhabricatorController',
     'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO',
     'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability',
+    'PhabricatorBadgesEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'PhabricatorBadgesEditController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorBadgesIconSet' => 'PhabricatorIconSet',
     'PhabricatorBadgesListController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorBadgesQuality' => 'Phobject',
     'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorBadgesRecipientsListView' => 'AphrontView',
     'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController',
     'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+    'PhabricatorBadgesSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorBadgesViewController' => 'PhabricatorBadgesController',
     'PhabricatorBarePageUIExample' => 'PhabricatorUIExample',
     'PhabricatorBarePageView' => 'AphrontPageView',
     'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher',
     'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
     'PhabricatorBoardLayoutEngine' => 'Phobject',
     'PhabricatorBoardRenderingEngine' => 'Phobject',
     'PhabricatorBoardResponseEngine' => 'Phobject',
     'PhabricatorBot' => 'PhabricatorDaemon',
     'PhabricatorBotChannel' => 'PhabricatorBotTarget',
     'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter',
     'PhabricatorBotHandler' => 'Phobject',
     'PhabricatorBotLogHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotMessage' => 'Phobject',
     'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler',
     'PhabricatorBotTarget' => 'Phobject',
     'PhabricatorBotUser' => 'PhabricatorBotTarget',
     'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler',
     'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation',
     'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
     'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
     'PhabricatorBusyUIExample' => 'PhabricatorUIExample',
     'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
     'PhabricatorCacheGeneralGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow',
     'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorCacheSpec' => 'Phobject',
     'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorCaches' => 'Phobject',
     'PhabricatorCachesTestCase' => 'PhabricatorTestCase',
     'PhabricatorCalendarApplication' => 'PhabricatorApplication',
     'PhabricatorCalendarController' => 'PhabricatorController',
     'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO',
     'PhabricatorCalendarEvent' => array(
       'PhabricatorCalendarDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorProjectInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorFulltextInterface',
     ),
     'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine',
     'PhabricatorCalendarEventInvitee' => array(
       'PhabricatorCalendarDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand',
     'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
     'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
     'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',
     'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorCampfireProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter',
     'PhabricatorCelerityApplication' => 'PhabricatorApplication',
     'PhabricatorCelerityTestCase' => 'PhabricatorTestCase',
     'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
     'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
     'PhabricatorChatLogApplication' => 'PhabricatorApplication',
     'PhabricatorChatLogChannel' => array(
       'PhabricatorChatLogDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController',
     'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
     'PhabricatorChatLogChannelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorChatLogController' => 'PhabricatorController',
     'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO',
     'PhabricatorChatLogEvent' => array(
       'PhabricatorChatLogDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
+    'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField',
     'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension',
     'PhabricatorCommentEditField' => 'PhabricatorEditField',
     'PhabricatorCommentEditType' => 'PhabricatorEditType',
     'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField',
     'PhabricatorCommitCustomField' => 'PhabricatorCustomField',
     'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField',
     'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField',
     'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField',
     'PhabricatorCommonPasswords' => 'Phobject',
     'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
     'PhabricatorConduitApplication' => 'PhabricatorApplication',
     'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
     'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
     'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
     'PhabricatorConduitContentSource' => 'PhabricatorContentSource',
     'PhabricatorConduitController' => 'PhabricatorController',
     'PhabricatorConduitDAO' => 'PhabricatorLiskDAO',
     'PhabricatorConduitEditField' => 'PhabricatorEditField',
     'PhabricatorConduitListController' => 'PhabricatorConduitController',
     'PhabricatorConduitLogController' => 'PhabricatorConduitController',
     'PhabricatorConduitLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorConduitLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorConduitMethodCallLog' => array(
       'PhabricatorConduitDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorConduitResultInterface' => 'PhabricatorPHIDInterface',
     'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorConduitSearchFieldSpecification' => 'Phobject',
     'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
     'PhabricatorConduitToken' => array(
       'PhabricatorConduitDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
     'PhabricatorConduitTokenEditController' => 'PhabricatorConduitController',
     'PhabricatorConduitTokenHandshakeController' => 'PhabricatorConduitController',
     'PhabricatorConduitTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorConduitTokenTerminateController' => 'PhabricatorConduitController',
     'PhabricatorConduitTokensSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorConfigAllController' => 'PhabricatorConfigController',
     'PhabricatorConfigApplication' => 'PhabricatorApplication',
     'PhabricatorConfigCacheController' => 'PhabricatorConfigController',
     'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
     'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorConfigController' => 'PhabricatorController',
     'PhabricatorConfigCoreSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController',
     'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController',
     'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema',
     'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource',
     'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController',
     'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource',
     'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
     'PhabricatorConfigEdgeModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigEditController' => 'PhabricatorConfigController',
     'PhabricatorConfigEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorConfigEntry' => array(
       'PhabricatorConfigEntryDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO',
     'PhabricatorConfigEntryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource',
     'PhabricatorConfigGroupController' => 'PhabricatorConfigController',
     'PhabricatorConfigHTTPParameterTypesModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigHistoryController' => 'PhabricatorConfigController',
     'PhabricatorConfigIgnoreController' => 'PhabricatorConfigController',
     'PhabricatorConfigIssueListController' => 'PhabricatorConfigController',
     'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController',
     'PhabricatorConfigJSON' => 'Phobject',
     'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType',
     'PhabricatorConfigKeySchema' => 'PhabricatorConfigStorageSchema',
     'PhabricatorConfigListController' => 'PhabricatorConfigController',
     'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource',
     'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow',
     'PhabricatorConfigManagementGetWorkflow' => 'PhabricatorConfigManagementWorkflow',
     'PhabricatorConfigManagementListWorkflow' => 'PhabricatorConfigManagementWorkflow',
     'PhabricatorConfigManagementMigrateWorkflow' => 'PhabricatorConfigManagementWorkflow',
     'PhabricatorConfigManagementSetWorkflow' => 'PhabricatorConfigManagementWorkflow',
     'PhabricatorConfigManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorConfigModule' => 'Phobject',
     'PhabricatorConfigModuleController' => 'PhabricatorConfigController',
     'PhabricatorConfigOption' => array(
       'Phobject',
       'PhabricatorMarkupInterface',
     ),
     'PhabricatorConfigOptionType' => 'Phobject',
     'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
     'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController',
     'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
     'PhabricatorConfigSchemaQuery' => 'Phobject',
     'PhabricatorConfigSchemaSpec' => 'Phobject',
     'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema',
     'PhabricatorConfigSiteModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigSiteSource' => 'PhabricatorConfigProxySource',
     'PhabricatorConfigSource' => 'Phobject',
     'PhabricatorConfigStackSource' => 'PhabricatorConfigSource',
     'PhabricatorConfigStorageSchema' => 'Phobject',
     'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema',
     'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorConfigValidationException' => 'Exception',
     'PhabricatorConfigVersionsModule' => 'PhabricatorConfigModule',
     'PhabricatorConfigWelcomeController' => 'PhabricatorConfigController',
     'PhabricatorConpherenceApplication' => 'PhabricatorApplication',
     'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorConsoleApplication' => 'PhabricatorApplication',
     'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
     'PhabricatorContentSource' => 'Phobject',
     'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
     'PhabricatorContentSourceView' => 'AphrontView',
     'PhabricatorContributedToObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorController' => 'AphrontController',
     'PhabricatorCookies' => 'Phobject',
     'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorCountdown' => array(
       'PhabricatorCountdownDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorProjectInterface',
     ),
     'PhabricatorCountdownApplication' => 'PhabricatorApplication',
-    'PhabricatorCountdownCommentController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownController' => 'PhabricatorController',
     'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
     'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
+    'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorCountdownView' => 'AphrontView',
     'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
     'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
     'PhabricatorCustomField' => 'Phobject',
     'PhabricatorCustomFieldAttachment' => 'Phobject',
     'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType',
     'PhabricatorCustomFieldDataNotAvailableException' => 'Exception',
     'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension',
     'PhabricatorCustomFieldEditField' => 'PhabricatorEditField',
     'PhabricatorCustomFieldEditType' => 'PhabricatorEditType',
     'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'PhabricatorCustomFieldHeraldField' => 'HeraldField',
     'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup',
     'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception',
     'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO',
     'PhabricatorCustomFieldList' => 'Phobject',
     'PhabricatorCustomFieldMonogramParser' => 'Phobject',
     'PhabricatorCustomFieldNotAttachedException' => 'Exception',
     'PhabricatorCustomFieldNotProxyException' => 'Exception',
     'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
     'PhabricatorCustomFieldSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorCustomFieldStorage' => 'PhabricatorLiskDAO',
     'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
     'PhabricatorCustomHeaderConfigType' => 'PhabricatorConfigOptionType',
     'PhabricatorDaemon' => 'PhutilDaemon',
     'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonContentSource' => 'PhabricatorContentSource',
     'PhabricatorDaemonController' => 'PhabricatorController',
     'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDaemonEventListener' => 'PhabricatorEventListener',
     'PhabricatorDaemonLog' => array(
       'PhabricatorDaemonDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
     'PhabricatorDaemonLogEventGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorDaemonLogEventViewController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonLogEventsView' => 'AphrontView',
     'PhabricatorDaemonLogGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonLogListView' => 'AphrontView',
     'PhabricatorDaemonLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonManagementDebugWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementLaunchWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementListWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementLogWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementReloadWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementRestartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementStartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementStatusWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementStopWorkflow' => 'PhabricatorDaemonManagementWorkflow',
     'PhabricatorDaemonManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorDaemonOverseerModule' => 'PhutilDaemonOverseerModule',
     'PhabricatorDaemonReference' => 'Phobject',
     'PhabricatorDaemonTaskGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorDaemonTasksTableView' => 'AphrontView',
     'PhabricatorDaemonsApplication' => 'PhabricatorApplication',
     'PhabricatorDaemonsSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorDailyRoutineTriggerClock' => 'PhabricatorTriggerClock',
     'PhabricatorDashboard' => array(
       'PhabricatorDashboardDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorProjectInterface',
     ),
     'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardApplication' => 'PhabricatorApplication',
     'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardController' => 'PhabricatorController',
     'PhabricatorDashboardCopyController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
-    'PhabricatorDashboardHistoryController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO',
     'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardLayoutConfig' => 'Phobject',
     'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardManageController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPanel' => array(
       'PhabricatorDashboardDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPanelCoreCustomField' => array(
       'PhabricatorDashboardPanelCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField',
     'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorDashboardPanelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorDashboardPanelRenderController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
     'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField',
     'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField',
     'PhabricatorDashboardPanelTabsCustomField' => 'PhabricatorStandardCustomField',
     'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorDashboardPanelType' => 'Phobject',
     'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType',
     'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardRenderingEngine' => 'Phobject',
     'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType',
     'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType',
     'PhabricatorDashboardTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorDashboardTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorDashboardTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorDashboardUninstallController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardViewController' => 'PhabricatorDashboardController',
     'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec',
     'PhabricatorDataNotAttachedException' => 'Exception',
     'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField',
     'PhabricatorDatasourceEditType' => 'PhabricatorPHIDListEditType',
     'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorDebugController' => 'PhabricatorController',
     'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorDestructionEngine' => 'Phobject',
     'PhabricatorDestructionEngineExtension' => 'Phobject',
     'PhabricatorDestructionEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorDiffInlineCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery',
     'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorDifferenceEngine' => 'Phobject',
     'PhabricatorDifferentialApplication' => 'PhabricatorApplication',
     'PhabricatorDifferentialAttachCommitWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
     'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
     'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorDiffusionApplication' => 'PhabricatorApplication',
     'PhabricatorDiffusionConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
     'PhabricatorDisplayPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorDisqusAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorDividerProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorDivinerApplication' => 'PhabricatorApplication',
     'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication',
     'PhabricatorDraft' => 'PhabricatorDraftDAO',
     'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDrydockApplication' => 'PhabricatorApplication',
     'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
     'PhabricatorEdgeConstants' => 'Phobject',
     'PhabricatorEdgeCycleException' => 'Exception',
     'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType',
     'PhabricatorEdgeEditor' => 'Phobject',
     'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
     'PhabricatorEdgeQuery' => 'PhabricatorQuery',
     'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
     'PhabricatorEdgeType' => 'Phobject',
     'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase',
     'PhabricatorEdgesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorEditEngine' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod',
+    'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction',
     'PhabricatorEditEngineCommentAction' => 'Phobject',
     'PhabricatorEditEngineConfiguration' => array(
       'PhabricatorSearchDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorEditEngineConfigurationDefaultCreateController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationDefaultsController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationDisableController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationEditController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorEditEngineConfigurationEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorEditEngineConfigurationIsEditController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationListController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationLockController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorEditEngineConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorEditEngineConfigurationReorderController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationSaveController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorEditEngineConfigurationSortController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineConfigurationTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorEditEngineConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController',
     'PhabricatorEditEngineExtension' => 'Phobject',
     'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
     'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
     'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
     'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
     'PhabricatorEditField' => 'Phobject',
     'PhabricatorEditType' => 'Phobject',
     'PhabricatorEditor' => 'Phobject',
     'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
     'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorEmailContentSource' => 'PhabricatorContentSource',
     'PhabricatorEmailFormatSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
     'PhabricatorEmailPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorEmailVerificationController' => 'PhabricatorAuthController',
     'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorEmptyQueryException' => 'Exception',
     'PhabricatorEnv' => 'Phobject',
     'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
+    'PhabricatorEpochEditField' => 'PhabricatorEditField',
     'PhabricatorEvent' => 'PhutilEvent',
     'PhabricatorEventEngine' => 'Phobject',
     'PhabricatorEventListener' => 'PhutilEventListener',
     'PhabricatorEventType' => 'PhutilEventType',
     'PhabricatorExampleEventListener' => 'PhabricatorEventListener',
     'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource',
     'PhabricatorExtendingPhabricatorConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorExtensionsSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorExternalAccount' => array(
       'PhabricatorUserDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorExternalAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorExternalAccountsSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorExtraConfigSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorFacebookAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
     'PhabricatorFactApplication' => 'PhabricatorApplication',
     'PhabricatorFactChartController' => 'PhabricatorFactController',
     'PhabricatorFactController' => 'PhabricatorController',
     'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
     'PhabricatorFactCursor' => 'PhabricatorFactDAO',
     'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFactDaemon' => 'PhabricatorDaemon',
     'PhabricatorFactEngine' => 'Phobject',
     'PhabricatorFactEngineTestCase' => 'PhabricatorTestCase',
     'PhabricatorFactHomeController' => 'PhabricatorFactController',
     'PhabricatorFactLastUpdatedEngine' => 'PhabricatorFactEngine',
     'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow',
     'PhabricatorFactManagementCursorsWorkflow' => 'PhabricatorFactManagementWorkflow',
     'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow',
     'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow',
     'PhabricatorFactManagementStatusWorkflow' => 'PhabricatorFactManagementWorkflow',
     'PhabricatorFactManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorFactRaw' => 'PhabricatorFactDAO',
     'PhabricatorFactSimpleSpec' => 'PhabricatorFactSpec',
     'PhabricatorFactSpec' => 'Phobject',
     'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator',
     'PhabricatorFaxContentSource' => 'PhabricatorContentSource',
     'PhabricatorFeedApplication' => 'PhabricatorApplication',
     'PhabricatorFeedBuilder' => 'Phobject',
     'PhabricatorFeedConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorFeedController' => 'PhabricatorController',
     'PhabricatorFeedDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFeedDetailController' => 'PhabricatorFeedController',
     'PhabricatorFeedListController' => 'PhabricatorFeedController',
     'PhabricatorFeedManagementRepublishWorkflow' => 'PhabricatorFeedManagementWorkflow',
     'PhabricatorFeedManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorFeedQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorFeedSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorFeedStory' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
       'PhabricatorMarkupInterface',
     ),
     'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
     'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
     'PhabricatorFeedStoryPublisher' => 'Phobject',
     'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
     'PhabricatorFile' => array(
       'PhabricatorFileDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
-    'PhabricatorFileAccessTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'PhabricatorFileBundleLoader' => 'Phobject',
     'PhabricatorFileChunk' => array(
       'PhabricatorFileDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorFileChunkIterator' => array(
       'Phobject',
       'Iterator',
     ),
     'PhabricatorFileChunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorFileCommentController' => 'PhabricatorFileController',
     'PhabricatorFileComposeController' => 'PhabricatorFileController',
     'PhabricatorFileController' => 'PhabricatorController',
     'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFileDataController' => 'PhabricatorFileController',
     'PhabricatorFileDeleteController' => 'PhabricatorFileController',
     'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
     'PhabricatorFileEditController' => 'PhabricatorFileController',
     'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorFileFilePHIDType' => 'PhabricatorPHIDType',
     'PhabricatorFileHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorFileIconSetSelectController' => 'PhabricatorFileController',
     'PhabricatorFileImageMacro' => array(
       'PhabricatorFileDAO',
       'PhabricatorSubscribableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
     'PhabricatorFileInfoController' => 'PhabricatorFileController',
     'PhabricatorFileLinkView' => 'AphrontView',
     'PhabricatorFileListController' => 'PhabricatorFileController',
     'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
     'PhabricatorFileStorageConfigurationException' => 'Exception',
     'PhabricatorFileStorageEngine' => 'Phobject',
     'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase',
     'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorFileTestCase' => 'PhabricatorTestCase',
     'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform',
     'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorFileTransform' => 'Phobject',
     'PhabricatorFileTransformController' => 'PhabricatorFileController',
     'PhabricatorFileTransformListController' => 'PhabricatorFileController',
     'PhabricatorFileTransformTestCase' => 'PhabricatorTestCase',
     'PhabricatorFileUploadController' => 'PhabricatorFileController',
     'PhabricatorFileUploadDialogController' => 'PhabricatorFileController',
     'PhabricatorFileUploadException' => 'Exception',
     'PhabricatorFileUploadSource' => 'Phobject',
     'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorFilesApplication' => 'PhabricatorApplication',
     'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
     'PhabricatorFilesBuiltinFile' => 'Phobject',
     'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile',
     'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile',
     'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction',
     'PhabricatorFlag' => array(
       'PhabricatorFlagDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction',
     'PhabricatorFlagColor' => 'PhabricatorFlagConstants',
     'PhabricatorFlagConstants' => 'Phobject',
     'PhabricatorFlagController' => 'PhabricatorController',
     'PhabricatorFlagDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFlagDeleteController' => 'PhabricatorFlagController',
     'PhabricatorFlagDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorFlagEditController' => 'PhabricatorFlagController',
     'PhabricatorFlagListController' => 'PhabricatorFlagController',
     'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorFlagSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorFlagSelectControl' => 'AphrontFormControl',
     'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface',
     'PhabricatorFlagsApplication' => 'PhabricatorApplication',
     'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener',
     'PhabricatorFulltextEngine' => 'Phobject',
     'PhabricatorFulltextEngineExtension' => 'Phobject',
     'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
     'PhabricatorFulltextStorageEngine' => 'Phobject',
     'PhabricatorFundApplication' => 'PhabricatorApplication',
     'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorGarbageCollector' => 'Phobject',
     'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
     'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
     'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorGestureUIExample' => 'PhabricatorUIExample',
     'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream',
     'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorGlobalLock' => 'PhutilLock',
     'PhabricatorGlobalUploadTargetView' => 'AphrontView',
     'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorHTTPParameterTypeTableView' => 'AphrontView',
     'PhabricatorHandleList' => array(
       'Phobject',
       'Iterator',
       'ArrayAccess',
       'Countable',
     ),
     'PhabricatorHandleObjectSelectorDataView' => 'Phobject',
     'PhabricatorHandlePool' => 'Phobject',
     'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase',
     'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorHandleRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField',
     'PhabricatorHarbormasterApplication' => 'PhabricatorApplication',
     'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorHash' => 'Phobject',
     'PhabricatorHashTestCase' => 'PhabricatorTestCase',
     'PhabricatorHelpApplication' => 'PhabricatorApplication',
     'PhabricatorHelpController' => 'PhabricatorController',
     'PhabricatorHelpDocumentationController' => 'PhabricatorHelpController',
     'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController',
     'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
     'PhabricatorHelpMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
     'PhabricatorHeraldApplication' => 'PhabricatorApplication',
     'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
     'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorHomeApplication' => 'PhabricatorApplication',
     'PhabricatorHomeController' => 'PhabricatorController',
     'PhabricatorHomeMainController' => 'PhabricatorHomeController',
     'PhabricatorHomePreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController',
     'PhabricatorHovercardEngineExtension' => 'Phobject',
     'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow',
     'PhabricatorHunksManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorIDsSearchField' => 'PhabricatorSearchField',
     'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter',
     'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorIconSet' => 'Phobject',
     'PhabricatorIconSetEditField' => 'PhabricatorEditField',
     'PhabricatorIconSetIcon' => 'Phobject',
     'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorImageTransformer' => 'Phobject',
     'PhabricatorImagemagickSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorIndexEngine' => 'Phobject',
     'PhabricatorIndexEngineExtension' => 'Phobject',
     'PhabricatorIndexEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase',
     'PhabricatorInlineCommentController' => 'PhabricatorController',
     'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface',
     'PhabricatorInlineCommentPreviewController' => 'PhabricatorController',
     'PhabricatorInlineSummaryView' => 'AphrontView',
     'PhabricatorInstructionsEditField' => 'PhabricatorEditField',
     'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow',
     'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
     'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
     'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
     'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
     'PhabricatorJavelinLinter' => 'ArcanistLinter',
     'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorJumpNavHandler' => 'Phobject',
     'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
     'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider',
     'PhabricatorLegalpadApplication' => 'PhabricatorApplication',
     'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase',
     'PhabricatorLinkProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorLipsumArtist' => 'Phobject',
     'PhabricatorLipsumContentSource' => 'PhabricatorContentSource',
     'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
     'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist',
     'PhabricatorLiskDAO' => 'LiskDAO',
     'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorLiskSerializer' => 'Phobject',
     'PhabricatorListFilterUIExample' => 'PhabricatorUIExample',
     'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
     'PhabricatorLocaleScopeGuard' => 'Phobject',
     'PhabricatorLocaleScopeGuardTestCase' => 'PhabricatorTestCase',
     'PhabricatorLogTriggerAction' => 'PhabricatorTriggerAction',
     'PhabricatorLogoutController' => 'PhabricatorAuthController',
     'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorMacroApplication' => 'PhabricatorApplication',
     'PhabricatorMacroAudioController' => 'PhabricatorMacroController',
     'PhabricatorMacroCommentController' => 'PhabricatorMacroController',
     'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorMacroController' => 'PhabricatorController',
     'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorMacroDisableController' => 'PhabricatorMacroController',
     'PhabricatorMacroEditController' => 'PhabricatorMacroController',
     'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorMacroListController' => 'PhabricatorMacroController',
     'PhabricatorMacroMacroPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorMacroMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorMacroManageCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorMacroMemeController' => 'PhabricatorMacroController',
     'PhabricatorMacroMemeDialogController' => 'PhabricatorMacroController',
     'PhabricatorMacroQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorMacroReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorMacroSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorMacroViewController' => 'PhabricatorMacroController',
     'PhabricatorMailEmailHeraldField' => 'HeraldField',
     'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup',
     'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField',
     'PhabricatorMailImplementationAdapter' => 'Phobject',
     'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
     'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementListOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementReceiveTestWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementResendWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementSendTestWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementShowInboundWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow',
     'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter',
     'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction',
     'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
     'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
     'PhabricatorMailOutboundStatus' => 'Phobject',
     'PhabricatorMailReceiver' => 'Phobject',
     'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
     'PhabricatorMailReplyHandler' => 'Phobject',
     'PhabricatorMailRoutingRule' => 'Phobject',
     'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorMailTarget' => 'Phobject',
     'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorMainMenuBarExtension' => 'Phobject',
     'PhabricatorMainMenuSearchView' => 'AphrontView',
     'PhabricatorMainMenuView' => 'AphrontView',
     'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow',
     'PhabricatorManiphestApplication' => 'PhabricatorApplication',
     'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
     'PhabricatorMarkupEngine' => 'Phobject',
     'PhabricatorMarkupOneOff' => array(
       'Phobject',
       'PhabricatorMarkupInterface',
     ),
     'PhabricatorMarkupPreviewController' => 'PhabricatorController',
     'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream',
     'PhabricatorMetaMTAActor' => 'Phobject',
     'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery',
     'PhabricatorMetaMTAApplication' => 'PhabricatorApplication',
     'PhabricatorMetaMTAApplicationEmail' => array(
       'PhabricatorMetaMTADAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSpacesInterface',
     ),
     'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorMetaMTAApplicationEmailHeraldField' => 'HeraldField',
     'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel',
     'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorMetaMTAAttachment' => 'Phobject',
     'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorMetaMTAController' => 'PhabricatorController',
     'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
     'PhabricatorMetaMTAEmailBodyParser' => 'Phobject',
     'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTAEmailHeraldAction' => 'HeraldAction',
     'PhabricatorMetaMTAEmailOthersHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction',
     'PhabricatorMetaMTAEmailSelfHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction',
     'PhabricatorMetaMTAErrorMailAction' => 'PhabricatorSystemAction',
     'PhabricatorMetaMTAMail' => array(
       'PhabricatorMetaMTADAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorMetaMTAMailBody' => 'Phobject',
     'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorMetaMTAMailListController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorMetaMTAMailSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorMetaMTAMailSection' => 'Phobject',
     'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTAMailViewController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
     'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
     'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
     'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception',
     'PhabricatorMetaMTAReceivedMailTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTASchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAWorker' => 'PhabricatorWorker',
     'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
     'PhabricatorMotivatorProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorMultiColumnUIExample' => 'PhabricatorUIExample',
     'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorMultimeterApplication' => 'PhabricatorApplication',
     'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController',
     'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorMySQLFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
     'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorNamedQuery' => array(
       'PhabricatorSearchDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
     'PhabricatorNgramsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
     'PhabricatorNotificationBuilder' => 'Phobject',
     'PhabricatorNotificationClearController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationClient' => 'Phobject',
     'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorNotificationController' => 'PhabricatorController',
     'PhabricatorNotificationDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorNotificationIndividualController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationListController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorNotificationStatusController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationStatusView' => 'AphrontTagView',
     'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
     'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory',
     'PhabricatorNotificationUIExample' => 'PhabricatorUIExample',
     'PhabricatorNotificationsApplication' => 'PhabricatorApplication',
     'PhabricatorNuanceApplication' => 'PhabricatorApplication',
     'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider',
     'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
     'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider',
     'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider',
     'PhabricatorOAuthClientAuthorization' => array(
       'PhabricatorOAuthServerDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorOAuthClientAuthorizationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorOAuthClientController' => 'PhabricatorOAuthServerController',
-    'PhabricatorOAuthClientDeleteController' => 'PhabricatorOAuthClientController',
+    'PhabricatorOAuthClientDisableController' => 'PhabricatorOAuthClientController',
     'PhabricatorOAuthClientEditController' => 'PhabricatorOAuthClientController',
     'PhabricatorOAuthClientListController' => 'PhabricatorOAuthClientController',
     'PhabricatorOAuthClientSecretController' => 'PhabricatorOAuthClientController',
+    'PhabricatorOAuthClientTestController' => 'PhabricatorOAuthClientController',
     'PhabricatorOAuthClientViewController' => 'PhabricatorOAuthClientController',
     'PhabricatorOAuthResponse' => 'AphrontResponse',
     'PhabricatorOAuthServer' => 'Phobject',
     'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO',
     'PhabricatorOAuthServerApplication' => 'PhabricatorApplication',
     'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController',
     'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO',
     'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorOAuthServerClient' => array(
       'PhabricatorOAuthServerDAO',
       'PhabricatorPolicyInterface',
+      'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorOAuthServerClientPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorOAuthServerClientQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorOAuthServerClientSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorOAuthServerController' => 'PhabricatorController',
     'PhabricatorOAuthServerCreateClientsCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
+    'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine',
+    'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorOAuthServerScope' => 'Phobject',
     'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
-    'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController',
     'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
+    'PhabricatorOAuthServerTransaction' => 'PhabricatorApplicationTransaction',
+    'PhabricatorOAuthServerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorObjectHandle' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasAsanaTaskEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasContributorEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasFileEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasJiraIssueEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasSubscriberEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasUnsubscriberEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectHasWatcherEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectListQuery' => 'Phobject',
     'PhabricatorObjectListQueryTestCase' => 'PhabricatorTestCase',
     'PhabricatorObjectMailReceiver' => 'PhabricatorMailReceiver',
     'PhabricatorObjectMailReceiverTestCase' => 'PhabricatorTestCase',
     'PhabricatorObjectMentionedByObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectMentionsObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorObjectSelectorDialog' => 'Phobject',
     'PhabricatorObjectUsesCredentialsEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
     'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource',
     'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock',
     'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec',
     'PhabricatorOwnerPathQuery' => 'Phobject',
     'PhabricatorOwnersApplication' => 'PhabricatorApplication',
     'PhabricatorOwnersArchiveController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorOwnersConfiguredCustomField' => array(
       'PhabricatorOwnersCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'PhabricatorOwnersController' => 'PhabricatorController',
     'PhabricatorOwnersCustomField' => 'PhabricatorCustomField',
     'PhabricatorOwnersCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
     'PhabricatorOwnersCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
     'PhabricatorOwnersCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
     'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
     'PhabricatorOwnersDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorOwnersDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersEditController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersListController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO',
     'PhabricatorOwnersPackage' => array(
       'PhabricatorOwnersDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorConduitResultInterface',
       'PhabricatorFulltextInterface',
       'PhabricatorNgramsInterface',
     ),
     'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine',
     'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams',
     'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
     'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
     'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO',
     'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPHID' => 'Phobject',
     'PhabricatorPHIDConstants' => 'Phobject',
     'PhabricatorPHIDListEditField' => 'PhabricatorEditField',
     'PhabricatorPHIDListEditType' => 'PhabricatorEditType',
     'PhabricatorPHIDResolver' => 'Phobject',
     'PhabricatorPHIDType' => 'Phobject',
     'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase',
     'PhabricatorPHIDsSearchField' => 'PhabricatorSearchField',
     'PhabricatorPHPASTApplication' => 'PhabricatorApplication',
     'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPagedFormUIExample' => 'PhabricatorUIExample',
     'PhabricatorPagerUIExample' => 'PhabricatorUIExample',
     'PhabricatorPassphraseApplication' => 'PhabricatorApplication',
     'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider',
     'PhabricatorPasswordHasher' => 'Phobject',
     'PhabricatorPasswordHasherTestCase' => 'PhabricatorTestCase',
     'PhabricatorPasswordHasherUnavailableException' => 'Exception',
     'PhabricatorPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorPaste' => array(
       'PhabricatorPasteDAO',
       'PhabricatorSubscribableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorConduitResultInterface',
     ),
     'PhabricatorPasteApplication' => 'PhabricatorApplication',
     'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
     'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorPasteController' => 'PhabricatorController',
     'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
     'PhabricatorPasteEditController' => 'PhabricatorPasteController',
     'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar',
     'PhabricatorPasteListController' => 'PhabricatorPasteController',
     'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorPasteRawController' => 'PhabricatorPasteController',
     'PhabricatorPasteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorPasteSnippet' => 'Phobject',
     'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorPasteTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorPasteViewController' => 'PhabricatorPasteController',
     'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorPeopleApplication' => 'PhabricatorApplication',
     'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleProfileController',
     'PhabricatorPeopleController' => 'PhabricatorController',
     'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleDetailsProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPeopleIconSet' => 'PhabricatorIconSet',
     'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController',
     'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController',
     'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
     'PhabricatorPeopleManageProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorPeopleNewController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorPeopleOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleProfileController',
     'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController',
     'PhabricatorPeopleProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
     'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController',
     'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController',
     'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorPeopleUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorPeopleUserPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPeopleWelcomeController' => 'PhabricatorPeopleController',
     'PhabricatorPersonaAuthProvider' => 'PhabricatorAuthProvider',
     'PhabricatorPhabricatorAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorPhameApplication' => 'PhabricatorApplication',
     'PhabricatorPhameBlogPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPhamePostPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPhluxApplication' => 'PhabricatorApplication',
     'PhabricatorPholioApplication' => 'PhabricatorApplication',
     'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorPhortuneApplication' => 'PhabricatorApplication',
     'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource',
     'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow',
     'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorPhragmentApplication' => 'PhabricatorApplication',
     'PhabricatorPhrequentApplication' => 'PhabricatorApplication',
     'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPhrictionApplication' => 'PhabricatorApplication',
     'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPhurlApplication' => 'PhabricatorApplication',
     'PhabricatorPhurlConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPhurlController' => 'PhabricatorController',
     'PhabricatorPhurlDAO' => 'PhabricatorLiskDAO',
     'PhabricatorPhurlLinkRemarkupRule' => 'PhutilRemarkupRule',
     'PhabricatorPhurlRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorPhurlSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorPhurlShortURLController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlShortURLDefaultController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlURL' => array(
       'PhabricatorPhurlDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorProjectInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorSpacesInterface',
     ),
     'PhabricatorPhurlURLAccessController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlURLCommentController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlURLCreateCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorPhurlURLEditController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlURLEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorPhurlURLListController' => 'PhabricatorPhurlController',
     'PhabricatorPhurlURLMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorPhurlURLPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPhurlURLQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorPhurlURLReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorPhurlURLSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorPhurlURLTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorPhurlURLTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorPhurlURLTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController',
     'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation',
     'PhabricatorPlatformSite' => 'PhabricatorSite',
     'PhabricatorPointsEditField' => 'PhabricatorEditField',
     'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
     'PhabricatorPolicy' => array(
       'PhabricatorPolicyDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorPolicyApplication' => 'PhabricatorApplication',
     'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery',
     'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
     'PhabricatorPolicyCanEditCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorPolicyCanJoinCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorPolicyCanViewCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorPolicyCapability' => 'Phobject',
     'PhabricatorPolicyCapabilityTestCase' => 'PhabricatorTestCase',
     'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorPolicyConstants' => 'Phobject',
     'PhabricatorPolicyController' => 'PhabricatorController',
     'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO',
     'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
     'PhabricatorPolicyEditController' => 'PhabricatorPolicyController',
     'PhabricatorPolicyEditEngineExtension' => 'PhabricatorEditEngineExtension',
     'PhabricatorPolicyEditField' => 'PhabricatorEditField',
     'PhabricatorPolicyException' => 'Exception',
     'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController',
     'PhabricatorPolicyFilter' => 'Phobject',
     'PhabricatorPolicyInterface' => 'PhabricatorPHIDInterface',
     'PhabricatorPolicyManagementShowWorkflow' => 'PhabricatorPolicyManagementWorkflow',
     'PhabricatorPolicyManagementUnlockWorkflow' => 'PhabricatorPolicyManagementWorkflow',
     'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
     'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorPolicyRule' => 'Phobject',
     'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
     'PhabricatorPolicyTestObject' => array(
       'Phobject',
       'PhabricatorPolicyInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
     'PhabricatorPonderApplication' => 'PhabricatorApplication',
     'PhabricatorProfilePanel' => 'Phobject',
     'PhabricatorProfilePanelConfiguration' => array(
       'PhabricatorSearchDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorExtendedPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
     ),
     'PhabricatorProfilePanelConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorProfilePanelConfigurationTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorProfilePanelConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorProfilePanelEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorProfilePanelEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorProfilePanelEngine' => 'Phobject',
     'PhabricatorProfilePanelIconSet' => 'PhabricatorIconSet',
     'PhabricatorProfilePanelPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorProject' => array(
       'PhabricatorProjectDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorExtendedPolicyInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorFulltextInterface',
       'PhabricatorConduitResultInterface',
       'PhabricatorColumnProxyInterface',
     ),
     'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
     'PhabricatorProjectApplication' => 'PhabricatorApplication',
     'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
     'PhabricatorProjectBoardBackgroundController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
     'PhabricatorProjectBoardDisableController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectBoardManageController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectCardView' => 'AphrontTagView',
     'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
     'PhabricatorProjectColumn' => array(
       'PhabricatorProjectDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorExtendedPolicyInterface',
     ),
     'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectColumnHideController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectColumnPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorProjectColumnPosition' => array(
       'PhabricatorProjectDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorProjectColumnPositionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorProjectColumnTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorProjectColumnTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorProjectColumnTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorProjectConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorProjectConfiguredCustomField' => array(
       'PhabricatorProjectStandardCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'PhabricatorProjectController' => 'PhabricatorController',
     'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
     'PhabricatorProjectCoverController' => 'PhabricatorProjectController',
     'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
     'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
     'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
     'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
     'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
     'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorProjectDefaultController' => 'PhabricatorProjectBoardController',
     'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
     'PhabricatorProjectDetailsProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectEditController' => 'PhabricatorProjectController',
     'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine',
     'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
     'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine',
     'PhabricatorProjectHeraldAction' => 'HeraldAction',
     'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter',
     'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup',
     'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
     'PhabricatorProjectIconSet' => 'PhabricatorIconSet',
     'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType',
     'PhabricatorProjectListController' => 'PhabricatorProjectController',
     'PhabricatorProjectListView' => 'AphrontView',
     'PhabricatorProjectLockController' => 'PhabricatorProjectController',
     'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorProjectManageController' => 'PhabricatorProjectController',
     'PhabricatorProjectManageProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectMemberListView' => 'PhabricatorProjectUserListView',
     'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectMembersAddController' => 'PhabricatorProjectController',
     'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
     'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController',
     'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
     'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
     'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
     'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
     'PhabricatorProjectPointsProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
     'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
     'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction',
     'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorProjectSilenceController' => 'PhabricatorProjectController',
     'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorProjectSlug' => 'PhabricatorProjectDAO',
     'PhabricatorProjectStandardCustomField' => array(
       'PhabricatorProjectCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'PhabricatorProjectStatus' => 'Phobject',
     'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
     'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
     'PhabricatorProjectSubprojectsProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
     'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
     'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
     'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorProjectUserListView' => 'AphrontView',
     'PhabricatorProjectViewController' => 'PhabricatorProjectController',
     'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
     'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView',
     'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject',
     'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
     'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension',
     'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
     'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
     'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'PhabricatorProjectsMembersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
     'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorProtocolAdapter' => 'Phobject',
     'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorQuery' => 'Phobject',
     'PhabricatorQueryConstraint' => 'Phobject',
     'PhabricatorQueryOrderItem' => 'Phobject',
     'PhabricatorQueryOrderTestCase' => 'PhabricatorTestCase',
     'PhabricatorQueryOrderVector' => array(
       'Phobject',
       'Iterator',
     ),
     'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
     'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorRedirectController' => 'PhabricatorController',
     'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
     'PhabricatorRegistrationProfile' => 'Phobject',
     'PhabricatorReleephApplication' => 'PhabricatorApplication',
     'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
     'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
     'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule',
     'PhabricatorRemarkupCustomInlineRule' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupEditField' => 'PhabricatorEditField',
     'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
     'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample',
     'PhabricatorRepositoriesSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorRepository' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorProjectInterface',
       'PhabricatorSpacesInterface',
     ),
     'PhabricatorRepositoryAuditRequest' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryCommit' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorProjectInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorMentionableInterface',
       'HarbormasterBuildableInterface',
       'HarbormasterCircleCIBuildableInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFulltextInterface',
     ),
     'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitOwnersWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker',
     'PhabricatorRepositoryCommitRef' => 'Phobject',
     'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
     'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine',
     'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorRepositoryEngine' => 'Phobject',
     'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
     'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
     'PhabricatorRepositoryGitLFSRef' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryGraphCache' => 'Phobject',
     'PhabricatorRepositoryGraphStream' => 'Phobject',
     'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementPullWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
     'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
     'PhabricatorRepositoryMirror' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine',
     'PhabricatorRepositoryMirrorPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryMirrorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryParsedChange' => 'Phobject',
     'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine',
     'PhabricatorRepositoryPullEvent' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryPullEventPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryPullEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon',
     'PhabricatorRepositoryPushEvent' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryPushEventPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryPushEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryPushLog' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryPushLogPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorRepositoryPushMailWorker' => 'PhabricatorWorker',
     'PhabricatorRepositoryPushReplyHandler' => 'PhabricatorMailReplyHandler',
     'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryRefCursor' => array(
       'PhabricatorRepositoryDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorRepositoryRefCursorPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositoryRefCursorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine',
     'PhabricatorRepositoryRepositoryPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorRepositorySchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorRepositoryStatusMessage' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
     'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
     'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase',
     'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorRepositoryType' => 'Phobject',
     'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryURINormalizer' => 'Phobject',
     'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
     'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
     'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryVersion' => 'Phobject',
     'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
     'PhabricatorResourceSite' => 'PhabricatorSite',
     'PhabricatorRobotsController' => 'PhabricatorController',
     'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorSMS' => 'PhabricatorSMSDAO',
     'PhabricatorSMSConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorSMSDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSMSDemultiplexWorker' => 'PhabricatorSMSWorker',
     'PhabricatorSMSImplementationAdapter' => 'Phobject',
     'PhabricatorSMSImplementationTestBlackholeAdapter' => 'PhabricatorSMSImplementationAdapter',
     'PhabricatorSMSImplementationTwilioAdapter' => 'PhabricatorSMSImplementationAdapter',
     'PhabricatorSMSManagementListOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow',
     'PhabricatorSMSManagementSendTestWorkflow' => 'PhabricatorSMSManagementWorkflow',
     'PhabricatorSMSManagementShowOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow',
     'PhabricatorSMSManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorSMSSendWorker' => 'PhabricatorSMSWorker',
     'PhabricatorSMSWorker' => 'PhabricatorWorker',
     'PhabricatorSQLPatchList' => 'Phobject',
     'PhabricatorSSHKeyGenerator' => 'Phobject',
     'PhabricatorSSHKeysSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorSSHLog' => 'Phobject',
     'PhabricatorSSHPassthruCommand' => 'Phobject',
     'PhabricatorSSHWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorSavedQuery' => array(
       'PhabricatorSearchDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction',
     'PhabricatorScopedEnv' => 'Phobject',
     'PhabricatorSearchAbstractDocument' => 'Phobject',
     'PhabricatorSearchApplication' => 'PhabricatorApplication',
     'PhabricatorSearchApplicationSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
     'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchBaseController' => 'PhabricatorController',
     'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField',
     'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField',
     'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorSearchDateControlField' => 'PhabricatorSearchField',
     'PhabricatorSearchDateField' => 'PhabricatorSearchField',
     'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
     'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',
     'PhabricatorSearchDocumentFieldType' => 'Phobject',
     'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO',
     'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod',
     'PhabricatorSearchEngineAttachment' => 'Phobject',
     'PhabricatorSearchEngineExtension' => 'Phobject',
     'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule',
     'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase',
     'PhabricatorSearchField' => 'Phobject',
     'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchIndexVersion' => 'PhabricatorSearchDAO',
     'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow',
     'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow',
     'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorSearchNgrams' => 'PhabricatorSearchDAO',
     'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
     'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorSearchRelationship' => 'Phobject',
     'PhabricatorSearchResultView' => 'AphrontView',
     'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
     'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
     'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorSearchTextField' => 'PhabricatorSearchField',
     'PhabricatorSearchThreeStateField' => 'PhabricatorSearchField',
     'PhabricatorSearchTokenizerField' => 'PhabricatorSearchField',
     'PhabricatorSearchWorker' => 'PhabricatorWorker',
     'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorSelectEditField' => 'PhabricatorEditField',
     'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction',
     'PhabricatorSettingsAdjustController' => 'PhabricatorController',
     'PhabricatorSettingsApplication' => 'PhabricatorApplication',
     'PhabricatorSettingsMainController' => 'PhabricatorController',
     'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
     'PhabricatorSettingsPanel' => 'Phobject',
     'PhabricatorSetupCheck' => 'Phobject',
     'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase',
     'PhabricatorSetupIssue' => 'Phobject',
     'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
     'PhabricatorSetupIssueView' => 'AphrontView',
     'PhabricatorShortSite' => 'PhabricatorSite',
     'PhabricatorSimpleEditType' => 'PhabricatorEditType',
     'PhabricatorSite' => 'AphrontSite',
     'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
     'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteController' => 'PhabricatorController',
     'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvotePoll' => array(
       'PhabricatorSlowvoteDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSpacesInterface',
     ),
     'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlug' => 'Phobject',
     'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
     'PhabricatorSortTableUIExample' => 'PhabricatorUIExample',
     'PhabricatorSourceCodeView' => 'AphrontView',
     'PhabricatorSpaceEditField' => 'PhabricatorEditField',
     'PhabricatorSpacesApplication' => 'PhabricatorApplication',
     'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
     'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
     'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
     'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
     'PhabricatorSpacesController' => 'PhabricatorController',
     'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',
     'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface',
     'PhabricatorSpacesListController' => 'PhabricatorSpacesController',
     'PhabricatorSpacesNamespace' => array(
       'PhabricatorSpacesDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType',
     'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController',
     'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorSpacesSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorSpacesTestCase' => 'PhabricatorTestCase',
     'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',
     'PhabricatorStandardCustomField' => 'PhabricatorCustomField',
     'PhabricatorStandardCustomFieldBlueprints' => 'PhabricatorStandardCustomFieldTokenizer',
     'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer',
     'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldLink' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldPHIDs' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField',
     'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs',
     'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer',
     'PhabricatorStandardPageView' => array(
       'PhabricatorBarePageView',
       'AphrontResponseProducerInterface',
     ),
     'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorStaticEditField' => 'PhabricatorEditField',
     'PhabricatorStatusController' => 'PhabricatorController',
     'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
     'PhabricatorStorageFixtureScopeGuard' => 'Phobject',
     'PhabricatorStorageManagementAPI' => 'Phobject',
     'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementProbeWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementQuickstartWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementRenamespaceWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementShellWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementStatusWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementUpgradeWorkflow' => 'PhabricatorStorageManagementWorkflow',
     'PhabricatorStorageManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorStoragePatch' => 'Phobject',
     'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter',
     'PhabricatorStringListEditField' => 'PhabricatorEditField',
     'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorSubscribersEditField' => 'PhabricatorTokenizerEditField',
     'PhabricatorSubscribersQuery' => 'PhabricatorQuery',
     'PhabricatorSubscriptionTriggerClock' => 'PhabricatorTriggerClock',
     'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
     'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
     'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication',
     'PhabricatorSubscriptionsCurtainExtension' => 'PHUICurtainExtension',
     'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
     'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension',
     'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
     'PhabricatorSubscriptionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction',
     'PhabricatorSubscriptionsListController' => 'PhabricatorController',
     'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
     'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
     'PhabricatorSubscriptionsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
     'PhabricatorSubscriptionsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
     'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController',
     'PhabricatorSubscriptionsUIEventListener' => 'PhabricatorEventListener',
     'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'PhabricatorSupportApplication' => 'PhabricatorApplication',
     'PhabricatorSyntaxHighlighter' => 'Phobject',
     'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorSystemAction' => 'Phobject',
     'PhabricatorSystemActionEngine' => 'Phobject',
     'PhabricatorSystemActionGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorSystemActionLog' => 'PhabricatorSystemDAO',
     'PhabricatorSystemActionRateLimitException' => 'Exception',
     'PhabricatorSystemApplication' => 'PhabricatorApplication',
     'PhabricatorSystemDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector',
     'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO',
     'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow',
     'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow',
     'PhabricatorSystemRemoveWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorSystemSelectEncodingController' => 'PhabricatorController',
     'PhabricatorSystemSelectHighlightController' => 'PhabricatorController',
     'PhabricatorTOTPAuthFactor' => 'PhabricatorAuthFactor',
     'PhabricatorTOTPAuthFactorTestCase' => 'PhabricatorTestCase',
     'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
     'PhabricatorTestApplication' => 'PhabricatorApplication',
     'PhabricatorTestCase' => 'PhutilTestCase',
     'PhabricatorTestController' => 'PhabricatorController',
     'PhabricatorTestDataGenerator' => 'Phobject',
     'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorTestWorker' => 'PhabricatorWorker',
     'PhabricatorTextAreaEditField' => 'PhabricatorEditField',
     'PhabricatorTextEditField' => 'PhabricatorEditField',
     'PhabricatorTime' => 'Phobject',
     'PhabricatorTimeGuard' => 'Phobject',
     'PhabricatorTimeTestCase' => 'PhabricatorTestCase',
     'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck',
     'PhabricatorToken' => array(
       'PhabricatorTokenDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorTokenController' => 'PhabricatorController',
     'PhabricatorTokenCount' => 'PhabricatorTokenDAO',
     'PhabricatorTokenCountQuery' => 'PhabricatorOffsetPagedQuery',
     'PhabricatorTokenDAO' => 'PhabricatorLiskDAO',
     'PhabricatorTokenDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorTokenGiveController' => 'PhabricatorTokenController',
     'PhabricatorTokenGiven' => array(
       'PhabricatorTokenDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorTokenGivenController' => 'PhabricatorTokenController',
     'PhabricatorTokenGivenEditor' => 'PhabricatorEditor',
     'PhabricatorTokenGivenFeedStory' => 'PhabricatorFeedStory',
     'PhabricatorTokenGivenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorTokenLeaderController' => 'PhabricatorTokenController',
     'PhabricatorTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener',
     'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField',
     'PhabricatorTokensApplication' => 'PhabricatorApplication',
     'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension',
     'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',
     'PhabricatorTransactions' => 'Phobject',
     'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
     'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
     'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
     'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorTriggerAction' => 'Phobject',
     'PhabricatorTriggerClock' => 'Phobject',
     'PhabricatorTriggerClockTestCase' => 'PhabricatorTestCase',
     'PhabricatorTriggerDaemon' => 'PhabricatorDaemon',
     'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
     'PhabricatorTwitchAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorTwitterAuthProvider' => 'PhabricatorOAuth1AuthProvider',
     'PhabricatorTypeaheadApplication' => 'PhabricatorApplication',
     'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorTypeaheadDatasource' => 'Phobject',
     'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
     'PhabricatorTypeaheadFunctionHelpController' => 'PhabricatorTypeaheadDatasourceController',
     'PhabricatorTypeaheadInvalidTokenException' => 'Exception',
     'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
     'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorTypeaheadResult' => 'Phobject',
     'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
     'PhabricatorTypeaheadTokenView' => 'AphrontTagView',
     'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorUIExample' => 'Phobject',
     'PhabricatorUIExampleRenderController' => 'PhabricatorController',
     'PhabricatorUIExamplesApplication' => 'PhabricatorApplication',
     'PhabricatorUSEnglishTranslation' => 'PhutilTranslation',
     'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
     'PhabricatorUnitsTestCase' => 'PhabricatorTestCase',
     'PhabricatorUnknownContentSource' => 'PhabricatorContentSource',
     'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorUser' => array(
       'PhabricatorUserDAO',
       'PhutilPerson',
       'PhabricatorPolicyInterface',
       'PhabricatorCustomFieldInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSSHPublicKeyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFulltextInterface',
     ),
     'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
     'PhabricatorUserCardView' => 'AphrontTagView',
     'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorUserConfiguredCustomField' => array(
       'PhabricatorUserCustomField',
       'PhabricatorStandardCustomFieldInterface',
     ),
     'PhabricatorUserConfiguredCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
     'PhabricatorUserCustomField' => 'PhabricatorCustomField',
     'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
     'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
     'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
     'PhabricatorUserEditor' => 'PhabricatorEditor',
     'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase',
     'PhabricatorUserEmail' => 'PhabricatorUserDAO',
     'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase',
     'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine',
     'PhabricatorUserIconField' => 'PhabricatorUserCustomField',
     'PhabricatorUserLog' => array(
       'PhabricatorUserDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorUserLogView' => 'AphrontView',
     'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver',
     'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
     'PhabricatorUserProfile' => 'PhabricatorUserDAO',
     'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField',
     'PhabricatorUserRolesField' => 'PhabricatorUserCustomField',
     'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorUserSinceField' => 'PhabricatorUserCustomField',
     'PhabricatorUserStatusField' => 'PhabricatorUserCustomField',
     'PhabricatorUserTestCase' => 'PhabricatorTestCase',
     'PhabricatorUserTitleField' => 'PhabricatorUserCustomField',
     'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorUsersEditField' => 'PhabricatorTokenizerEditField',
     'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule',
     'PhabricatorUsersSearchField' => 'PhabricatorSearchTokenizerField',
     'PhabricatorVCSResponse' => 'AphrontResponse',
     'PhabricatorVersionedDraft' => 'PhabricatorDraftDAO',
     'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation',
     'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource',
     'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType',
     'PhabricatorWebContentSource' => 'PhabricatorContentSource',
     'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorWorker' => 'Phobject',
     'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
     'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
     'PhabricatorWorkerArchiveTaskQuery' => 'PhabricatorQuery',
     'PhabricatorWorkerBulkJob' => array(
       'PhabricatorWorkerDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PhabricatorWorkerBulkJobCreateWorker' => 'PhabricatorWorkerBulkJobWorker',
     'PhabricatorWorkerBulkJobEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorWorkerBulkJobPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorWorkerBulkJobQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorWorkerBulkJobSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhabricatorWorkerBulkJobTaskWorker' => 'PhabricatorWorkerBulkJobWorker',
     'PhabricatorWorkerBulkJobTestCase' => 'PhabricatorTestCase',
     'PhabricatorWorkerBulkJobTransaction' => 'PhabricatorApplicationTransaction',
     'PhabricatorWorkerBulkJobTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorWorkerBulkJobType' => 'Phobject',
     'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker',
     'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
     'PhabricatorWorkerDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
     'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
     'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementExecuteWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
     'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorWorkerPermanentFailureException' => 'Exception',
     'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
     'PhabricatorWorkerTestCase' => 'PhabricatorTestCase',
     'PhabricatorWorkerTrigger' => array(
       'PhabricatorWorkerDAO',
       'PhabricatorDestructibleInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhabricatorWorkerTriggerEvent' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTriggerManagementFireWorkflow' => 'PhabricatorWorkerTriggerManagementWorkflow',
     'PhabricatorWorkerTriggerManagementWorkflow' => 'PhabricatorManagementWorkflow',
     'PhabricatorWorkerTriggerPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorWorkerTriggerQuery' => 'PhabricatorPolicyAwareQuery',
     'PhabricatorWorkerYieldException' => 'Exception',
     'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',
     'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase',
     'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase',
     'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO',
     'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO',
     'PhabricatorXHPASTViewController' => 'PhabricatorController',
     'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHProfApplication' => 'PhabricatorApplication',
     'PhabricatorXHProfController' => 'PhabricatorController',
     'PhabricatorXHProfDAO' => 'PhabricatorLiskDAO',
     'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController',
     'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
     'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
     'PhabricatorXHProfProfileView' => 'AphrontView',
     'PhabricatorXHProfSample' => 'PhabricatorXHProfDAO',
     'PhabricatorXHProfSampleListController' => 'PhabricatorXHProfController',
     'PhabricatorYoutubeRemarkupRule' => 'PhutilRemarkupRule',
     'PhameBlog' => array(
       'PhameDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorConduitResultInterface',
     ),
     'PhameBlogArchiveController' => 'PhameBlogController',
     'PhameBlogController' => 'PhameController',
     'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability',
     'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'PhameBlogEditController' => 'PhameBlogController',
     'PhameBlogEditEngine' => 'PhabricatorEditEngine',
     'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhameBlogFeedController' => 'PhameBlogController',
     'PhameBlogListController' => 'PhameBlogController',
     'PhameBlogListView' => 'AphrontTagView',
     'PhameBlogManageController' => 'PhameBlogController',
     'PhameBlogProfilePictureController' => 'PhameBlogController',
     'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhameBlogSite' => 'PhameSite',
     'PhameBlogTransaction' => 'PhabricatorApplicationTransaction',
     'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhameBlogViewController' => 'PhameLiveController',
     'PhameConstants' => 'Phobject',
     'PhameController' => 'PhabricatorController',
     'PhameDAO' => 'PhabricatorLiskDAO',
     'PhameDescriptionView' => 'AphrontTagView',
     'PhameDraftListView' => 'AphrontTagView',
     'PhameHomeController' => 'PhamePostController',
     'PhameLiveController' => 'PhameController',
     'PhameNextPostView' => 'AphrontTagView',
     'PhamePost' => array(
       'PhameDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorProjectInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorConduitResultInterface',
     ),
     'PhamePostCommentController' => 'PhamePostController',
     'PhamePostController' => 'PhameController',
     'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'PhamePostEditController' => 'PhamePostController',
     'PhamePostEditEngine' => 'PhabricatorEditEngine',
     'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhamePostHistoryController' => 'PhamePostController',
     'PhamePostListController' => 'PhamePostController',
     'PhamePostListView' => 'AphrontTagView',
     'PhamePostMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PhamePostMoveController' => 'PhamePostController',
     'PhamePostPublishController' => 'PhamePostController',
     'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhamePostTransaction' => 'PhabricatorApplicationTransaction',
     'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhamePostViewController' => 'PhameLiveController',
     'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhameSite' => 'PhabricatorSite',
     'PhluxController' => 'PhabricatorController',
     'PhluxDAO' => 'PhabricatorLiskDAO',
     'PhluxEditController' => 'PhluxController',
     'PhluxListController' => 'PhluxController',
     'PhluxTransaction' => 'PhabricatorApplicationTransaction',
     'PhluxTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhluxVariable' => array(
       'PhluxDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhluxVariableEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhluxVariablePHIDType' => 'PhabricatorPHIDType',
     'PhluxVariableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhluxViewController' => 'PhluxController',
     'PholioActionMenuEventListener' => 'PhabricatorEventListener',
     'PholioController' => 'PhabricatorController',
     'PholioDAO' => 'PhabricatorLiskDAO',
     'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PholioImage' => array(
       'PholioDAO',
       'PhabricatorMarkupInterface',
       'PhabricatorPolicyInterface',
     ),
     'PholioImagePHIDType' => 'PhabricatorPHIDType',
     'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PholioImageUploadController' => 'PholioController',
     'PholioInlineController' => 'PholioController',
     'PholioInlineListController' => 'PholioController',
     'PholioMock' => array(
       'PholioDAO',
       'PhabricatorMarkupInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorMentionableInterface',
       'PhabricatorFulltextInterface',
     ),
     'PholioMockArchiveController' => 'PholioController',
     'PholioMockAuthorHeraldField' => 'PholioMockHeraldField',
     'PholioMockCommentController' => 'PholioController',
     'PholioMockDescriptionHeraldField' => 'PholioMockHeraldField',
     'PholioMockEditController' => 'PholioController',
     'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor',
     'PholioMockEmbedView' => 'AphrontView',
     'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine',
     'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType',
     'PholioMockHeraldField' => 'HeraldField',
     'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup',
     'PholioMockImagesView' => 'AphrontView',
     'PholioMockListController' => 'PholioController',
     'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PholioMockNameHeraldField' => 'PholioMockHeraldField',
     'PholioMockPHIDType' => 'PhabricatorPHIDType',
     'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PholioMockThumbGridView' => 'AphrontView',
     'PholioMockViewController' => 'PholioController',
     'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PholioTransaction' => 'PhabricatorApplicationTransaction',
     'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PholioTransactionView' => 'PhabricatorApplicationTransactionView',
     'PholioUploadedImageView' => 'AphrontView',
     'PhortuneAccount' => array(
       'PhortuneDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneAccountEditController' => 'PhortuneController',
     'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType',
     'PhortuneAccountListController' => 'PhortuneController',
     'PhortuneAccountPHIDType' => 'PhabricatorPHIDType',
     'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
     'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhortuneAccountViewController' => 'PhortuneController',
     'PhortuneAdHocCart' => 'PhortuneCartImplementation',
     'PhortuneAdHocProduct' => 'PhortuneProductImplementation',
     'PhortuneCart' => array(
       'PhortuneDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneCartAcceptController' => 'PhortuneCartController',
     'PhortuneCartCancelController' => 'PhortuneCartController',
     'PhortuneCartCheckoutController' => 'PhortuneCartController',
     'PhortuneCartController' => 'PhortuneController',
     'PhortuneCartEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhortuneCartImplementation' => 'Phobject',
     'PhortuneCartListController' => 'PhortuneController',
     'PhortuneCartPHIDType' => 'PhabricatorPHIDType',
     'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneCartReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhortuneCartSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhortuneCartTransaction' => 'PhabricatorApplicationTransaction',
     'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhortuneCartUpdateController' => 'PhortuneCartController',
     'PhortuneCartViewController' => 'PhortuneCartController',
     'PhortuneCharge' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneChargeListController' => 'PhortuneController',
     'PhortuneChargePHIDType' => 'PhabricatorPHIDType',
     'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhortuneChargeTableView' => 'AphrontView',
     'PhortuneConstants' => 'Phobject',
     'PhortuneController' => 'PhabricatorController',
     'PhortuneCreditCardForm' => 'Phobject',
     'PhortuneCurrency' => 'Phobject',
     'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer',
     'PhortuneCurrencyTestCase' => 'PhabricatorTestCase',
     'PhortuneDAO' => 'PhabricatorLiskDAO',
     'PhortuneErrCode' => 'PhortuneConstants',
     'PhortuneLandingController' => 'PhortuneController',
     'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType',
     'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType',
     'PhortuneMerchant' => array(
       'PhortuneDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability',
     'PhortuneMerchantController' => 'PhortuneController',
     'PhortuneMerchantEditController' => 'PhortuneMerchantController',
     'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType',
     'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController',
     'PhortuneMerchantListController' => 'PhortuneMerchantController',
     'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType',
     'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhortuneMerchantTransaction' => 'PhabricatorApplicationTransaction',
     'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhortuneMerchantViewController' => 'PhortuneMerchantController',
     'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
     'PhortuneOrderTableView' => 'AphrontView',
     'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider',
     'PhortunePaymentMethod' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortunePaymentMethodCreateController' => 'PhortuneController',
     'PhortunePaymentMethodDisableController' => 'PhortuneController',
     'PhortunePaymentMethodEditController' => 'PhortuneController',
     'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType',
     'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortunePaymentProvider' => 'Phobject',
     'PhortunePaymentProviderConfig' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortunePaymentProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhortunePaymentProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortunePaymentProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
     'PhortunePaymentProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhortunePaymentProviderPHIDType' => 'PhabricatorPHIDType',
     'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase',
     'PhortuneProduct' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneProductImplementation' => 'Phobject',
     'PhortuneProductListController' => 'PhabricatorController',
     'PhortuneProductPHIDType' => 'PhabricatorPHIDType',
     'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneProductViewController' => 'PhortuneController',
     'PhortuneProviderActionController' => 'PhortuneController',
     'PhortuneProviderDisableController' => 'PhortuneMerchantController',
     'PhortuneProviderEditController' => 'PhortuneMerchantController',
     'PhortunePurchase' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortunePurchasePHIDType' => 'PhabricatorPHIDType',
     'PhortunePurchaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider',
     'PhortuneSubscription' => array(
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhortuneSubscriptionCart' => 'PhortuneCartImplementation',
     'PhortuneSubscriptionEditController' => 'PhortuneController',
     'PhortuneSubscriptionImplementation' => 'Phobject',
     'PhortuneSubscriptionListController' => 'PhortuneController',
     'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType',
     'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation',
     'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhortuneSubscriptionTableView' => 'AphrontView',
     'PhortuneSubscriptionViewController' => 'PhortuneController',
     'PhortuneSubscriptionWorker' => 'PhabricatorWorker',
     'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
     'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider',
     'PhragmentBrowseController' => 'PhragmentController',
     'PhragmentCanCreateCapability' => 'PhabricatorPolicyCapability',
     'PhragmentConduitAPIMethod' => 'ConduitAPIMethod',
     'PhragmentController' => 'PhabricatorController',
     'PhragmentCreateController' => 'PhragmentController',
     'PhragmentDAO' => 'PhabricatorLiskDAO',
     'PhragmentFragment' => array(
       'PhragmentDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhragmentFragmentPHIDType' => 'PhabricatorPHIDType',
     'PhragmentFragmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhragmentFragmentVersion' => array(
       'PhragmentDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhragmentFragmentVersionPHIDType' => 'PhabricatorPHIDType',
     'PhragmentFragmentVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhragmentGetPatchConduitAPIMethod' => 'PhragmentConduitAPIMethod',
     'PhragmentHistoryController' => 'PhragmentController',
     'PhragmentPatchController' => 'PhragmentController',
     'PhragmentPatchUtil' => 'Phobject',
     'PhragmentPolicyController' => 'PhragmentController',
     'PhragmentQueryFragmentsConduitAPIMethod' => 'PhragmentConduitAPIMethod',
     'PhragmentRevertController' => 'PhragmentController',
     'PhragmentSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhragmentSnapshot' => array(
       'PhragmentDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhragmentSnapshotChild' => array(
       'PhragmentDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhragmentSnapshotCreateController' => 'PhragmentController',
     'PhragmentSnapshotDeleteController' => 'PhragmentController',
     'PhragmentSnapshotPHIDType' => 'PhabricatorPHIDType',
     'PhragmentSnapshotPromoteController' => 'PhragmentController',
     'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhragmentSnapshotViewController' => 'PhragmentController',
     'PhragmentUpdateController' => 'PhragmentController',
     'PhragmentVersionController' => 'PhragmentController',
     'PhragmentZIPController' => 'PhragmentController',
     'PhrequentConduitAPIMethod' => 'ConduitAPIMethod',
     'PhrequentController' => 'PhabricatorController',
     'PhrequentCurtainExtension' => 'PHUICurtainExtension',
     'PhrequentDAO' => 'PhabricatorLiskDAO',
     'PhrequentListController' => 'PhrequentController',
     'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod',
     'PhrequentPushConduitAPIMethod' => 'PhrequentConduitAPIMethod',
     'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhrequentTimeBlock' => 'Phobject',
     'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase',
     'PhrequentTimeSlices' => 'Phobject',
     'PhrequentTrackController' => 'PhrequentController',
     'PhrequentTrackingConduitAPIMethod' => 'PhrequentConduitAPIMethod',
     'PhrequentTrackingEditor' => 'PhabricatorEditor',
     'PhrequentUIEventListener' => 'PhabricatorEventListener',
     'PhrequentUserTime' => array(
       'PhrequentDAO',
       'PhabricatorPolicyInterface',
     ),
     'PhrequentUserTimeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhrictionChangeType' => 'PhrictionConstants',
     'PhrictionConduitAPIMethod' => 'ConduitAPIMethod',
     'PhrictionConstants' => 'Phobject',
     'PhrictionContent' => array(
       'PhrictionDAO',
       'PhabricatorMarkupInterface',
     ),
     'PhrictionController' => 'PhabricatorController',
     'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod',
     'PhrictionDAO' => 'PhabricatorLiskDAO',
     'PhrictionDeleteController' => 'PhrictionController',
     'PhrictionDiffController' => 'PhrictionController',
     'PhrictionDocument' => array(
       'PhrictionDAO',
       'PhabricatorPolicyInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorFulltextInterface',
     ),
     'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField',
     'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField',
     'PhrictionDocumentController' => 'PhrictionController',
     'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine',
     'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter',
     'PhrictionDocumentHeraldField' => 'HeraldField',
     'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup',
     'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType',
     'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField',
     'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhrictionDocumentStatus' => 'PhrictionConstants',
     'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField',
     'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod',
     'PhrictionEditController' => 'PhrictionController',
     'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod',
     'PhrictionHistoryController' => 'PhrictionController',
     'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod',
     'PhrictionListController' => 'PhrictionController',
     'PhrictionMoveController' => 'PhrictionController',
     'PhrictionNewController' => 'PhrictionController',
     'PhrictionRemarkupRule' => 'PhutilRemarkupRule',
     'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PhrictionTransaction' => 'PhabricatorApplicationTransaction',
     'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PolicyLockOptionType' => 'PhabricatorConfigJSONOptionType',
     'PonderAddAnswerView' => 'AphrontView',
     'PonderAnswer' => array(
       'PonderDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorDestructibleInterface',
     ),
     'PonderAnswerCommentController' => 'PonderController',
     'PonderAnswerEditController' => 'PonderController',
     'PonderAnswerEditor' => 'PonderEditor',
     'PonderAnswerHistoryController' => 'PonderController',
     'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PonderAnswerPHIDType' => 'PhabricatorPHIDType',
     'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PonderAnswerSaveController' => 'PonderController',
     'PonderAnswerStatus' => 'PonderConstants',
     'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction',
     'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PonderAnswerView' => 'AphrontTagView',
     'PonderConstants' => 'Phobject',
     'PonderController' => 'PhabricatorController',
     'PonderDAO' => 'PhabricatorLiskDAO',
     'PonderDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'PonderEditor' => 'PhabricatorApplicationTransactionEditor',
     'PonderFooterView' => 'AphrontTagView',
     'PonderModerateCapability' => 'PhabricatorPolicyCapability',
     'PonderQuestion' => array(
       'PonderDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorMarkupInterface',
       'PhabricatorSubscribableInterface',
       'PhabricatorFlaggableInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorTokenReceiverInterface',
       'PhabricatorProjectInterface',
       'PhabricatorDestructibleInterface',
       'PhabricatorSpacesInterface',
       'PhabricatorFulltextInterface',
     ),
     'PonderQuestionCommentController' => 'PonderController',
     'PonderQuestionEditController' => 'PonderController',
     'PonderQuestionEditor' => 'PonderEditor',
     'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine',
     'PonderQuestionHistoryController' => 'PonderController',
     'PonderQuestionListController' => 'PonderController',
     'PonderQuestionMailReceiver' => 'PhabricatorObjectMailReceiver',
     'PonderQuestionPHIDType' => 'PhabricatorPHIDType',
     'PonderQuestionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PonderQuestionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'PonderQuestionStatus' => 'PonderConstants',
     'PonderQuestionStatusController' => 'PonderController',
     'PonderQuestionTransaction' => 'PhabricatorApplicationTransaction',
     'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PonderQuestionViewController' => 'PonderController',
     'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec',
     'ProjectAddProjectsEmailCommand' => 'MetaMTAEmailTransactionCommand',
     'ProjectBoardTaskCard' => 'Phobject',
     'ProjectCanLockProjectsCapability' => 'PhabricatorPolicyCapability',
     'ProjectConduitAPIMethod' => 'ConduitAPIMethod',
     'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod',
     'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
     'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability',
     'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability',
     'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability',
     'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
     'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
     'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
     'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
     'QueryFormattingTestCase' => 'PhabricatorTestCase',
     'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephBranch' => array(
       'ReleephDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'ReleephBranchAccessController' => 'ReleephBranchController',
     'ReleephBranchCommitFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephBranchController' => 'ReleephController',
     'ReleephBranchCreateController' => 'ReleephProductController',
     'ReleephBranchEditController' => 'ReleephBranchController',
     'ReleephBranchEditor' => 'PhabricatorEditor',
     'ReleephBranchHistoryController' => 'ReleephBranchController',
     'ReleephBranchNamePreviewController' => 'ReleephController',
     'ReleephBranchPHIDType' => 'PhabricatorPHIDType',
     'ReleephBranchPreviewView' => 'AphrontFormControl',
     'ReleephBranchQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ReleephBranchSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'ReleephBranchTemplate' => 'Phobject',
     'ReleephBranchTransaction' => 'PhabricatorApplicationTransaction',
     'ReleephBranchTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'ReleephBranchViewController' => 'ReleephBranchController',
     'ReleephCommitFinder' => 'Phobject',
     'ReleephCommitFinderException' => 'Exception',
     'ReleephCommitMessageFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephConduitAPIMethod' => 'ConduitAPIMethod',
     'ReleephController' => 'PhabricatorController',
     'ReleephDAO' => 'PhabricatorLiskDAO',
     'ReleephDefaultFieldSelector' => 'ReleephFieldSelector',
     'ReleephDependsOnFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephDiffChurnFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephDiffMessageFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephDiffSizeFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephFieldParseException' => 'Exception',
     'ReleephFieldSelector' => 'Phobject',
     'ReleephFieldSpecification' => array(
       'PhabricatorCustomField',
       'PhabricatorMarkupInterface',
     ),
     'ReleephGetBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephIntentFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephLevelFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephOriginalCommitFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephProductActionController' => 'ReleephProductController',
     'ReleephProductController' => 'ReleephController',
     'ReleephProductCreateController' => 'ReleephProductController',
     'ReleephProductEditController' => 'ReleephProductController',
     'ReleephProductEditor' => 'PhabricatorApplicationTransactionEditor',
     'ReleephProductHistoryController' => 'ReleephProductController',
     'ReleephProductListController' => 'ReleephController',
     'ReleephProductPHIDType' => 'PhabricatorPHIDType',
     'ReleephProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ReleephProductSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'ReleephProductTransaction' => 'PhabricatorApplicationTransaction',
     'ReleephProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'ReleephProductViewController' => 'ReleephProductController',
     'ReleephProject' => array(
       'ReleephDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
     ),
     'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephReasonFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephRequest' => array(
       'ReleephDAO',
       'PhabricatorApplicationTransactionInterface',
       'PhabricatorPolicyInterface',
       'PhabricatorCustomFieldInterface',
     ),
     'ReleephRequestActionController' => 'ReleephRequestController',
     'ReleephRequestCommentController' => 'ReleephRequestController',
     'ReleephRequestConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephRequestController' => 'ReleephController',
     'ReleephRequestDifferentialCreateController' => 'ReleephController',
     'ReleephRequestEditController' => 'ReleephBranchController',
     'ReleephRequestMailReceiver' => 'PhabricatorObjectMailReceiver',
     'ReleephRequestPHIDType' => 'PhabricatorPHIDType',
     'ReleephRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'ReleephRequestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
     'ReleephRequestSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'ReleephRequestStatus' => 'Phobject',
     'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction',
     'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment',
     'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor',
     'ReleephRequestTypeaheadControl' => 'AphrontFormControl',
     'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController',
     'ReleephRequestView' => 'AphrontView',
     'ReleephRequestViewController' => 'ReleephBranchController',
     'ReleephRequestorFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephRevisionFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephSeverityFieldSpecification' => 'ReleephLevelFieldSpecification',
     'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
     'ReleephWorkCanPushConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkGetBranchConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkGetCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkNextRequestConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkRecordConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod',
     'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod',
     'RepositoryConduitAPIMethod' => 'ConduitAPIMethod',
     'RepositoryCreateConduitAPIMethod' => 'RepositoryConduitAPIMethod',
     'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod',
     'ShellLogView' => 'AphrontView',
     'SlowvoteConduitAPIMethod' => 'ConduitAPIMethod',
     'SlowvoteEmbedView' => 'AphrontView',
     'SlowvoteInfoConduitAPIMethod' => 'SlowvoteConduitAPIMethod',
     'SlowvoteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
     'SubscriptionListDialogBuilder' => 'Phobject',
     'SubscriptionListStringBuilder' => 'Phobject',
     'TokenConduitAPIMethod' => 'ConduitAPIMethod',
     'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod',
     'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod',
     'TokenQueryConduitAPIMethod' => 'TokenConduitAPIMethod',
     'UserConduitAPIMethod' => 'ConduitAPIMethod',
     'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod',
     'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod',
     'UserFindConduitAPIMethod' => 'UserConduitAPIMethod',
     'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod',
     'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod',
   ),
 ));
diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php
index f8b01eb94..3a95b0ebd 100644
--- a/src/aphront/AphrontRequest.php
+++ b/src/aphront/AphrontRequest.php
@@ -1,800 +1,801 @@
 <?php
 
 /**
  * @task data     Accessing Request Data
  * @task cookie   Managing Cookies
  * @task cluster  Working With a Phabricator Cluster
  */
 final class AphrontRequest extends Phobject {
 
   // NOTE: These magic request-type parameters are automatically included in
   // certain requests (e.g., by phabricator_form(), JX.Request,
   // JX.Workflow, and ConduitClient) and help us figure out what sort of
   // response the client expects.
 
   const TYPE_AJAX = '__ajax__';
   const TYPE_FORM = '__form__';
   const TYPE_CONDUIT = '__conduit__';
   const TYPE_WORKFLOW = '__wflow__';
   const TYPE_CONTINUE = '__continue__';
   const TYPE_PREVIEW = '__preview__';
   const TYPE_HISEC = '__hisec__';
   const TYPE_QUICKSAND = '__quicksand__';
 
   private $host;
   private $path;
   private $requestData;
   private $user;
   private $applicationConfiguration;
   private $site;
   private $controller;
   private $uriData = array();
   private $cookiePrefix;
 
   public function __construct($host, $path) {
     $this->host = $host;
     $this->path = $path;
   }
 
   public function setURIMap(array $uri_data) {
     $this->uriData = $uri_data;
     return $this;
   }
 
   public function getURIMap() {
     return $this->uriData;
   }
 
   public function getURIData($key, $default = null) {
     return idx($this->uriData, $key, $default);
   }
 
   public function setApplicationConfiguration(
     $application_configuration) {
     $this->applicationConfiguration = $application_configuration;
     return $this;
   }
 
   public function getApplicationConfiguration() {
     return $this->applicationConfiguration;
   }
 
   public function setPath($path) {
     $this->path = $path;
     return $this;
   }
 
   public function getPath() {
     return $this->path;
   }
 
   public function getHost() {
     // The "Host" header may include a port number, or may be a malicious
     // header in the form "realdomain.com:ignored@evil.com". Invoke the full
     // parser to extract the real domain correctly. See here for coverage of
     // a similar issue in Django:
     //
     //  https://www.djangoproject.com/weblog/2012/oct/17/security/
     $uri = new PhutilURI('http://'.$this->host);
     return $uri->getDomain();
   }
 
   public function setSite(AphrontSite $site) {
     $this->site = $site;
     return $this;
   }
 
   public function getSite() {
     return $this->site;
   }
 
   public function setController(AphrontController $controller) {
     $this->controller = $controller;
     return $this;
   }
 
   public function getController() {
     return $this->controller;
   }
 
 
 /* -(  Accessing Request Data  )--------------------------------------------- */
 
 
   /**
    * @task data
    */
   public function setRequestData(array $request_data) {
     $this->requestData = $request_data;
     return $this;
   }
 
 
   /**
    * @task data
    */
   public function getRequestData() {
     return $this->requestData;
   }
 
 
   /**
    * @task data
    */
   public function getInt($name, $default = null) {
     if (isset($this->requestData[$name])) {
       // Converting from array to int is "undefined". Don't rely on whatever
       // PHP decides to do.
       if (is_array($this->requestData[$name])) {
         return $default;
       }
       return (int)$this->requestData[$name];
     } else {
       return $default;
     }
   }
 
 
   /**
    * @task data
    */
   public function getBool($name, $default = null) {
     if (isset($this->requestData[$name])) {
       if ($this->requestData[$name] === 'true') {
         return true;
       } else if ($this->requestData[$name] === 'false') {
         return false;
       } else {
         return (bool)$this->requestData[$name];
       }
     } else {
       return $default;
     }
   }
 
 
   /**
    * @task data
    */
   public function getStr($name, $default = null) {
     if (isset($this->requestData[$name])) {
       $str = (string)$this->requestData[$name];
       // Normalize newline craziness.
       $str = str_replace(
         array("\r\n", "\r"),
         array("\n", "\n"),
         $str);
       return $str;
     } else {
       return $default;
     }
   }
 
 
   /**
    * @task data
    */
   public function getArr($name, $default = array()) {
     if (isset($this->requestData[$name]) &&
         is_array($this->requestData[$name])) {
       return $this->requestData[$name];
     } else {
       return $default;
     }
   }
 
 
   /**
    * @task data
    */
   public function getStrList($name, $default = array()) {
     if (!isset($this->requestData[$name])) {
       return $default;
     }
     $list = $this->getStr($name);
     $list = preg_split('/[\s,]+/', $list, $limit = -1, PREG_SPLIT_NO_EMPTY);
     return $list;
   }
 
 
   /**
    * @task data
    */
   public function getExists($name) {
     return array_key_exists($name, $this->requestData);
   }
 
   public function getFileExists($name) {
     return isset($_FILES[$name]) &&
            (idx($_FILES[$name], 'error') !== UPLOAD_ERR_NO_FILE);
   }
 
   public function isHTTPGet() {
     return ($_SERVER['REQUEST_METHOD'] == 'GET');
   }
 
   public function isHTTPPost() {
     return ($_SERVER['REQUEST_METHOD'] == 'POST');
   }
 
   public function isAjax() {
     return $this->getExists(self::TYPE_AJAX) && !$this->isQuicksand();
   }
 
   public function isWorkflow() {
     return $this->getExists(self::TYPE_WORKFLOW) && !$this->isQuicksand();
   }
 
   public function isQuicksand() {
     return $this->getExists(self::TYPE_QUICKSAND);
   }
 
   public function isConduit() {
     return $this->getExists(self::TYPE_CONDUIT);
   }
 
   public static function getCSRFTokenName() {
     return '__csrf__';
   }
 
   public static function getCSRFHeaderName() {
     return 'X-Phabricator-Csrf';
   }
 
   public static function getViaHeaderName() {
     return 'X-Phabricator-Via';
   }
 
   public function validateCSRF() {
     $token_name = self::getCSRFTokenName();
     $token = $this->getStr($token_name);
 
     // No token in the request, check the HTTP header which is added for Ajax
     // requests.
     if (empty($token)) {
       $token = self::getHTTPHeader(self::getCSRFHeaderName());
     }
 
     $valid = $this->getUser()->validateCSRFToken($token);
     if (!$valid) {
 
       // Add some diagnostic details so we can figure out if some CSRF issues
       // are JS problems or people accessing Ajax URIs directly with their
       // browsers.
       $more_info = array();
 
       if ($this->isAjax()) {
         $more_info[] = pht('This was an Ajax request.');
       } else {
         $more_info[] = pht('This was a Web request.');
       }
 
       if ($token) {
         $more_info[] = pht('This request had an invalid CSRF token.');
       } else {
         $more_info[] = pht('This request had no CSRF token.');
       }
 
       // Give a more detailed explanation of how to avoid the exception
       // in developer mode.
       if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
         // TODO: Clean this up, see T1921.
         $more_info[] = pht(
           "To avoid this error, use %s to construct forms. If you are already ".
           "using %s, make sure the form 'action' uses a relative URI (i.e., ".
           "begins with a '%s'). Forms using absolute URIs do not include CSRF ".
           "tokens, to prevent leaking tokens to external sites.\n\n".
           "If this page performs writes which do not require CSRF protection ".
           "(usually, filling caches or logging), you can use %s to ".
           "temporarily bypass CSRF protection while writing. You should use ".
           "this only for writes which can not be protected with normal CSRF ".
           "mechanisms.\n\n".
           "Some UI elements (like %s) also have methods which will allow you ".
           "to render links as forms (like %s).",
           'phabricator_form()',
           'phabricator_form()',
           '/',
           'AphrontWriteGuard::beginScopedUnguardedWrites()',
           'PhabricatorActionListView',
           'setRenderAsForm(true)');
       }
 
       // This should only be able to happen if you load a form, pull your
       // internet for 6 hours, and then reconnect and immediately submit,
       // but give the user some indication of what happened since the workflow
       // is incredibly confusing otherwise.
       throw new AphrontCSRFException(
         pht(
           'You are trying to save some data to Phabricator, but the request '.
           'your browser made included an incorrect token. Reload the page '.
           'and try again. You may need to clear your cookies.')."\n\n".
           implode("\n", $more_info));
     }
 
     return true;
   }
 
   public function isFormPost() {
     $post = $this->getExists(self::TYPE_FORM) &&
             !$this->getExists(self::TYPE_HISEC) &&
             $this->isHTTPPost();
 
     if (!$post) {
       return false;
     }
 
     return $this->validateCSRF();
   }
 
   public function isFormOrHisecPost() {
     $post = $this->getExists(self::TYPE_FORM) &&
             $this->isHTTPPost();
 
     if (!$post) {
       return false;
     }
 
     return $this->validateCSRF();
   }
 
 
   public function setCookiePrefix($prefix) {
     $this->cookiePrefix = $prefix;
     return $this;
   }
 
   private function getPrefixedCookieName($name) {
     if (strlen($this->cookiePrefix)) {
       return $this->cookiePrefix.'_'.$name;
     } else {
       return $name;
     }
   }
 
   public function getCookie($name, $default = null) {
     $name = $this->getPrefixedCookieName($name);
     $value = idx($_COOKIE, $name, $default);
 
     // Internally, PHP deletes cookies by setting them to the value 'deleted'
     // with an expiration date in the past.
 
     // At least in Safari, the browser may send this cookie anyway in some
     // circumstances. After logging out, the 302'd GET to /login/ consistently
     // includes deleted cookies on my local install. If a cookie value is
     // literally 'deleted', pretend it does not exist.
 
     if ($value === 'deleted') {
       return null;
     }
 
     return $value;
   }
 
   public function clearCookie($name) {
     $this->setCookieWithExpiration($name, '', time() - (60 * 60 * 24 * 30));
     unset($_COOKIE[$name]);
   }
 
   /**
    * Get the domain which cookies should be set on for this request, or null
    * if the request does not correspond to a valid cookie domain.
    *
    * @return PhutilURI|null   Domain URI, or null if no valid domain exists.
    *
    * @task cookie
    */
   private function getCookieDomainURI() {
     if (PhabricatorEnv::getEnvConfig('security.require-https') &&
         !$this->isHTTPS()) {
       return null;
     }
 
     $host = $this->getHost();
 
     // If there's no base domain configured, just use whatever the request
     // domain is. This makes setup easier, and we'll tell administrators to
     // configure a base domain during the setup process.
     $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
     if (!strlen($base_uri)) {
       return new PhutilURI('http://'.$host.'/');
     }
 
     $alternates = PhabricatorEnv::getEnvConfig('phabricator.allowed-uris');
     $allowed_uris = array_merge(
       array($base_uri),
       $alternates);
 
     foreach ($allowed_uris as $allowed_uri) {
       $uri = new PhutilURI($allowed_uri);
       if ($uri->getDomain() == $host) {
         return $uri;
       }
     }
 
     return null;
   }
 
   /**
    * Determine if security policy rules will allow cookies to be set when
    * responding to the request.
    *
    * @return bool True if setCookie() will succeed. If this method returns
    *              false, setCookie() will throw.
    *
    * @task cookie
    */
   public function canSetCookies() {
     return (bool)$this->getCookieDomainURI();
   }
 
 
   /**
    * Set a cookie which does not expire for a long time.
    *
    * To set a temporary cookie, see @{method:setTemporaryCookie}.
    *
    * @param string  Cookie name.
    * @param string  Cookie value.
    * @return this
    * @task cookie
    */
   public function setCookie($name, $value) {
     $far_future = time() + (60 * 60 * 24 * 365 * 5);
     return $this->setCookieWithExpiration($name, $value, $far_future);
   }
 
 
   /**
    * Set a cookie which expires soon.
    *
    * To set a durable cookie, see @{method:setCookie}.
    *
    * @param string  Cookie name.
    * @param string  Cookie value.
    * @return this
    * @task cookie
    */
   public function setTemporaryCookie($name, $value) {
     return $this->setCookieWithExpiration($name, $value, 0);
   }
 
 
   /**
    * Set a cookie with a given expiration policy.
    *
    * @param string  Cookie name.
    * @param string  Cookie value.
    * @param int     Epoch timestamp for cookie expiration.
    * @return this
    * @task cookie
    */
   private function setCookieWithExpiration(
     $name,
     $value,
     $expire) {
 
     $is_secure = false;
 
     $base_domain_uri = $this->getCookieDomainURI();
     if (!$base_domain_uri) {
       $configured_as = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
       $accessed_as = $this->getHost();
 
       throw new Exception(
         pht(
           'This Phabricator install is configured as "%s", but you are '.
           'using the domain name "%s" to access a page which is trying to '.
           'set a cookie. Acccess Phabricator on the configured primary '.
           'domain or a configured alternate domain. Phabricator will not '.
           'set cookies on other domains for security reasons.',
           $configured_as,
           $accessed_as));
     }
 
     $base_domain = $base_domain_uri->getDomain();
     $is_secure = ($base_domain_uri->getProtocol() == 'https');
 
     $name = $this->getPrefixedCookieName($name);
 
     if (php_sapi_name() == 'cli') {
       // Do nothing, to avoid triggering "Cannot modify header information"
       // warnings.
 
       // TODO: This is effectively a test for whether we're running in a unit
       // test or not. Move this actual call to HTTPSink?
     } else {
       setcookie(
         $name,
         $value,
         $expire,
         $path = '/',
         $base_domain,
         $is_secure,
         $http_only = true);
     }
 
     $_COOKIE[$name] = $value;
 
     return $this;
   }
 
   public function setUser($user) {
     $this->user = $user;
     return $this;
   }
 
   public function getUser() {
     return $this->user;
   }
 
   public function getViewer() {
     return $this->user;
   }
 
   public function getRequestURI() {
     $get = $_GET;
     unset($get['__path__']);
     $path = phutil_escape_uri($this->getPath());
     return id(new PhutilURI($path))->setQueryParams($get);
   }
 
   public function isDialogFormPost() {
     return $this->isFormPost() && $this->getStr('__dialog__');
   }
 
   public function getRemoteAddress() {
     $address = $_SERVER['REMOTE_ADDR'];
     if (!strlen($address)) {
       return null;
     }
     return substr($address, 0, 64);
   }
 
   public function isHTTPS() {
     if (empty($_SERVER['HTTPS'])) {
       return false;
     }
     if (!strcasecmp($_SERVER['HTTPS'], 'off')) {
       return false;
     }
     return true;
   }
 
   public function isContinueRequest() {
     return $this->isFormPost() && $this->getStr('__continue__');
   }
 
   public function isPreviewRequest() {
     return $this->isFormPost() && $this->getStr('__preview__');
   }
 
   /**
    * Get application request parameters in a flattened form suitable for
    * inclusion in an HTTP request, excluding parameters with special meanings.
    * This is primarily useful if you want to ask the user for more input and
    * then resubmit their request.
    *
    * @return  dict<string, string>  Original request parameters.
    */
   public function getPassthroughRequestParameters($include_quicksand = false) {
     return self::flattenData(
       $this->getPassthroughRequestData($include_quicksand));
   }
 
   /**
    * Get request data other than "magic" parameters.
    *
    * @return dict<string, wild> Request data, with magic filtered out.
    */
   public function getPassthroughRequestData($include_quicksand = false) {
     $data = $this->getRequestData();
 
     // Remove magic parameters like __dialog__ and __ajax__.
     foreach ($data as $key => $value) {
       if ($include_quicksand && $key == self::TYPE_QUICKSAND) {
         continue;
       }
       if (!strncmp($key, '__', 2)) {
         unset($data[$key]);
       }
     }
 
     return $data;
   }
 
 
   /**
    * Flatten an array of key-value pairs (possibly including arrays as values)
    * into a list of key-value pairs suitable for submitting via HTTP request
    * (with arrays flattened).
    *
    * @param   dict<string, wild>    Data to flatten.
    * @return  dict<string, string>  Flat data suitable for inclusion in an HTTP
    *                                request.
    */
   public static function flattenData(array $data) {
     $result = array();
     foreach ($data as $key => $value) {
       if (is_array($value)) {
         foreach (self::flattenData($value) as $fkey => $fvalue) {
           $fkey = '['.preg_replace('/(?=\[)|$/', ']', $fkey, $limit = 1);
           $result[$key.$fkey] = $fvalue;
         }
       } else {
         $result[$key] = (string)$value;
       }
     }
 
     ksort($result);
 
     return $result;
   }
 
 
   /**
    * Read the value of an HTTP header from `$_SERVER`, or a similar datasource.
    *
    * This function accepts a canonical header name, like `"Accept-Encoding"`,
    * and looks up the appropriate value in `$_SERVER` (in this case,
    * `"HTTP_ACCEPT_ENCODING"`).
    *
    * @param   string        Canonical header name, like `"Accept-Encoding"`.
    * @param   wild          Default value to return if header is not present.
    * @param   array?        Read this instead of `$_SERVER`.
    * @return  string|wild   Header value if present, or `$default` if not.
    */
   public static function getHTTPHeader($name, $default = null, $data = null) {
     // PHP mangles HTTP headers by uppercasing them and replacing hyphens with
     // underscores, then prepending 'HTTP_'.
     $php_index = strtoupper($name);
     $php_index = str_replace('-', '_', $php_index);
 
     $try_names = array();
 
     $try_names[] = 'HTTP_'.$php_index;
     if ($php_index == 'CONTENT_TYPE' || $php_index == 'CONTENT_LENGTH') {
       // These headers may be available under alternate names. See
       // http://www.php.net/manual/en/reserved.variables.server.php#110763
       $try_names[] = $php_index;
     }
 
     if ($data === null) {
       $data = $_SERVER;
     }
 
     foreach ($try_names as $try_name) {
       if (array_key_exists($try_name, $data)) {
         return $data[$try_name];
       }
     }
 
     return $default;
   }
 
 
 /* -(  Working With a Phabricator Cluster  )--------------------------------- */
 
 
   /**
    * Is this a proxied request originating from within the Phabricator cluster?
    *
    * IMPORTANT: This means the request is dangerous!
    *
    * These requests are **more dangerous** than normal requests (they can not
    * be safely proxied, because proxying them may cause a loop). Cluster
    * requests are not guaranteed to come from a trusted source, and should
    * never be treated as safer than normal requests. They are strictly less
    * safe.
    */
   public function isProxiedClusterRequest() {
     return (bool)self::getHTTPHeader('X-Phabricator-Cluster');
   }
 
 
   /**
    * Build a new @{class:HTTPSFuture} which proxies this request to another
    * node in the cluster.
    *
    * IMPORTANT: This is very dangerous!
    *
    * The future forwards authentication information present in the request.
    * Proxied requests must only be sent to trusted hosts. (We attempt to
    * enforce this.)
    *
    * This is not a general-purpose proxying method; it is a specialized
    * method with niche applications and severe security implications.
    *
    * @param string URI identifying the host we are proxying the request to.
    * @return HTTPSFuture New proxy future.
    *
    * @phutil-external-symbol class PhabricatorStartup
    */
   public function newClusterProxyFuture($uri) {
     $uri = new PhutilURI($uri);
 
     $domain = $uri->getDomain();
     $ip = gethostbyname($domain);
     if (!$ip) {
       throw new Exception(
         pht(
           'Unable to resolve domain "%s"!',
           $domain));
     }
 
     if (!PhabricatorEnv::isClusterAddress($ip)) {
       throw new Exception(
         pht(
           'Refusing to proxy a request to IP address ("%s") which is not '.
           'in the cluster address block (this address was derived by '.
           'resolving the domain "%s").',
           $ip,
           $domain));
     }
 
     $uri->setPath($this->getPath());
     $uri->setQueryParams(self::flattenData($_GET));
 
     $input = PhabricatorStartup::getRawInput();
 
     $future = id(new HTTPSFuture($uri))
       ->addHeader('Host', self::getHost())
       ->addHeader('X-Phabricator-Cluster', true)
       ->setMethod($_SERVER['REQUEST_METHOD'])
       ->write($input);
 
     if (isset($_SERVER['PHP_AUTH_USER'])) {
       $future->setHTTPBasicAuthCredentials(
         $_SERVER['PHP_AUTH_USER'],
         new PhutilOpaqueEnvelope(idx($_SERVER, 'PHP_AUTH_PW', '')));
     }
 
     $headers = array();
     $seen = array();
 
     // NOTE: apache_request_headers() might provide a nicer way to do this,
     // but isn't available under FCGI until PHP 5.4.0.
     foreach ($_SERVER as $key => $value) {
       if (preg_match('/^HTTP_/', $key)) {
         // Unmangle the header as best we can.
+        $key = substr($key, strlen('HTTP_'));
         $key = str_replace('_', ' ', $key);
         $key = strtolower($key);
         $key = ucwords($key);
         $key = str_replace(' ', '-', $key);
 
         $headers[] = array($key, $value);
         $seen[$key] = true;
       }
     }
 
     // In some situations, this may not be mapped into the HTTP_X constants.
     // CONTENT_LENGTH is similarly affected, but we trust cURL to take care
     // of that if it matters, since we're handing off a request body.
     if (empty($seen['Content-Type'])) {
       if (isset($_SERVER['CONTENT_TYPE'])) {
         $headers[] = array('Content-Type', $_SERVER['CONTENT_TYPE']);
       }
     }
 
     foreach ($headers as $header) {
       list($key, $value) = $header;
       switch ($key) {
         case 'Host':
         case 'Authorization':
           // Don't forward these headers, we've already handled them elsewhere.
           unset($headers[$key]);
           break;
         default:
           break;
       }
     }
 
     foreach ($headers as $header) {
       list($key, $value) = $header;
       $future->addHeader($key, $value);
     }
 
     return $future;
   }
 
 
 }
diff --git a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php
new file mode 100644
index 000000000..f1932bd87
--- /dev/null
+++ b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php
@@ -0,0 +1,37 @@
+<?php
+
+final class AphrontEpochHTTPParameterType
+  extends AphrontHTTPParameterType {
+
+  protected function getParameterExists(AphrontRequest $request, $key) {
+    return $request->getExists($key) ||
+           $request->getExists($key.'_d');
+  }
+
+  protected function getParameterValue(AphrontRequest $request, $key) {
+    return AphrontFormDateControlValue::newFromRequest($request, $key);
+  }
+
+  protected function getParameterTypeName() {
+    return 'epoch';
+  }
+
+  protected function getParameterFormatDescriptions() {
+    return array(
+      pht('An epoch timestamp, as an integer.'),
+      pht('An absolute date, as a string.'),
+      pht('A relative date, as a string.'),
+      pht('Separate date and time inputs, as strings.'),
+    );
+  }
+
+  protected function getParameterExamples() {
+    return array(
+      'v=1460050737',
+      'v=2022-01-01',
+      'v=yesterday',
+      'v_d=2022-01-01&v_t=12:34',
+    );
+  }
+
+}
diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php
index bf58421c6..4688e3bc8 100644
--- a/src/aphront/response/AphrontFileResponse.php
+++ b/src/aphront/response/AphrontFileResponse.php
@@ -1,142 +1,121 @@
 <?php
 
 final class AphrontFileResponse extends AphrontResponse {
 
   private $content;
   private $contentIterator;
   private $contentLength;
 
   private $mimeType;
   private $download;
   private $rangeMin;
   private $rangeMax;
   private $allowOrigins = array();
-  private $fileToken;
 
   public function addAllowOrigin($origin) {
     $this->allowOrigins[] = $origin;
     return $this;
   }
 
   public function setDownload($download) {
     if (!strlen($download)) {
       $download = 'untitled';
     }
     $this->download = $download;
     return $this;
   }
 
   public function getDownload() {
     return $this->download;
   }
 
   public function setMimeType($mime_type) {
     $this->mimeType = $mime_type;
     return $this;
   }
 
   public function getMimeType() {
     return $this->mimeType;
   }
 
   public function setContent($content) {
     $this->setContentLength(strlen($content));
     $this->content = $content;
     return $this;
   }
 
   public function setContentIterator($iterator) {
     $this->contentIterator = $iterator;
     return $this;
   }
 
   public function buildResponseString() {
     return $this->content;
   }
 
   public function getContentIterator() {
     if ($this->contentIterator) {
       return $this->contentIterator;
     }
     return parent::getContentIterator();
   }
 
   public function setContentLength($length) {
     $this->contentLength = $length;
     return $this;
   }
 
   public function getContentLength() {
     return $this->contentLength;
   }
 
   public function setRange($min, $max) {
     $this->rangeMin = $min;
     $this->rangeMax = $max;
     return $this;
   }
 
-  public function setTemporaryFileToken(PhabricatorAuthTemporaryToken $token) {
-    $this->fileToken = $token;
-    return $this;
-  }
-
-  public function getTemporaryFileToken() {
-    return $this->fileToken;
-  }
-
   public function getHeaders() {
     $headers = array(
       array('Content-Type', $this->getMimeType()),
       // This tells clients that we can support requests with a "Range" header,
       // which allows downloads to be resumed, in some browsers, some of the
       // time, if the stars align.
       array('Accept-Ranges', 'bytes'),
     );
 
     if ($this->rangeMin || $this->rangeMax) {
       $len = $this->getContentLength();
       $min = $this->rangeMin;
       $max = $this->rangeMax;
       $headers[] = array('Content-Range', "bytes {$min}-{$max}/{$len}");
       $content_len = ($max - $min) + 1;
     } else {
       $content_len = $this->getContentLength();
     }
 
     $headers[] = array('Content-Length', $this->getContentLength());
 
     if (strlen($this->getDownload())) {
       $headers[] = array('X-Download-Options', 'noopen');
 
       $filename = $this->getDownload();
       $filename = addcslashes($filename, '"\\');
       $headers[] = array(
         'Content-Disposition',
         'attachment; filename="'.$filename.'"',
       );
     }
 
     if ($this->allowOrigins) {
       $headers[] = array(
         'Access-Control-Allow-Origin',
         implode(',', $this->allowOrigins),
       );
     }
 
     $headers = array_merge(parent::getHeaders(), $headers);
     return $headers;
   }
 
-  public function didCompleteWrite($aborted) {
-    if (!$aborted) {
-      $token = $this->getTemporaryFileToken();
-      if ($token) {
-        $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
-          $token->delete();
-        unset($unguarded);
-      }
-    }
-  }
-
 }
diff --git a/src/aphront/response/AphrontProxyResponse.php b/src/aphront/response/AphrontProxyResponse.php
index 772c4adaa..eda9e9b71 100644
--- a/src/aphront/response/AphrontProxyResponse.php
+++ b/src/aphront/response/AphrontProxyResponse.php
@@ -1,84 +1,89 @@
 <?php
 
 /**
  * Base class for responses which augment other types of responses. For example,
  * a response might be substantially an Ajax response, but add structure to the
  * response content. It can do this by extending @{class:AphrontProxyResponse},
  * instantiating an @{class:AphrontAjaxResponse} in @{method:buildProxy}, and
  * then constructing a real @{class:AphrontAjaxResponse} in
  * @{method:reduceProxyResponse}.
  */
 abstract class AphrontProxyResponse
   extends AphrontResponse
   implements AphrontResponseProducerInterface {
 
   private $proxy;
 
   protected function getProxy() {
     if (!$this->proxy) {
       $this->proxy = $this->buildProxy();
     }
     return $this->proxy;
   }
 
   public function setRequest($request) {
     $this->getProxy()->setRequest($request);
     return $this;
   }
 
   public function getRequest() {
     return $this->getProxy()->getRequest();
   }
 
   public function getHeaders() {
     return $this->getProxy()->getHeaders();
   }
 
   public function setCacheDurationInSeconds($duration) {
     $this->getProxy()->setCacheDurationInSeconds($duration);
     return $this;
   }
 
+  public function setCanCDN($can_cdn) {
+    $this->getProxy()->setCanCDN($can_cdn);
+    return $this;
+  }
+
   public function setLastModified($epoch_timestamp) {
     $this->getProxy()->setLastModified($epoch_timestamp);
     return $this;
   }
 
   public function setHTTPResponseCode($code) {
     $this->getProxy()->setHTTPResponseCode($code);
     return $this;
   }
 
   public function getHTTPResponseCode() {
     return $this->getProxy()->getHTTPResponseCode();
   }
 
   public function setFrameable($frameable) {
     $this->getProxy()->setFrameable($frameable);
     return $this;
   }
 
   public function getCacheHeaders() {
     return $this->getProxy()->getCacheHeaders();
   }
 
   abstract protected function buildProxy();
   abstract public function reduceProxyResponse();
 
   final public function buildResponseString() {
     throw new Exception(
       pht(
         '%s must implement %s.',
         __CLASS__,
         'reduceProxyResponse()'));
   }
 
 
 /* -(  AphrontResponseProducerInterface  )----------------------------------- */
 
 
   public function produceAphrontResponse() {
     return $this->reduceProxyResponse();
   }
 
 }
diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php
index 72dacf977..dbd60d473 100644
--- a/src/aphront/response/AphrontResponse.php
+++ b/src/aphront/response/AphrontResponse.php
@@ -1,233 +1,249 @@
 <?php
 
 abstract class AphrontResponse extends Phobject {
 
   private $request;
   private $cacheable = false;
+  private $canCDN;
   private $responseCode = 200;
   private $lastModified = null;
 
   protected $frameable;
 
   public function setRequest($request) {
     $this->request = $request;
     return $this;
   }
 
   public function getRequest() {
     return $this->request;
   }
 
 
 /* -(  Content  )------------------------------------------------------------ */
 
 
   public function getContentIterator() {
     return array($this->buildResponseString());
   }
 
   public function buildResponseString() {
     throw new PhutilMethodNotImplementedException();
   }
 
 
 /* -(  Metadata  )----------------------------------------------------------- */
 
 
   public function getHeaders() {
     $headers = array();
     if (!$this->frameable) {
       $headers[] = array('X-Frame-Options', 'Deny');
     }
 
     if ($this->getRequest() && $this->getRequest()->isHTTPS()) {
       $hsts_key = 'security.strict-transport-security';
       $use_hsts = PhabricatorEnv::getEnvConfig($hsts_key);
       if ($use_hsts) {
         $duration = phutil_units('365 days in seconds');
       } else {
         // If HSTS has been disabled, tell browsers to turn it off. This may
         // not be effective because we can only disable it over a valid HTTPS
         // connection, but it best represents the configured intent.
         $duration = 0;
       }
 
       $headers[] = array(
         'Strict-Transport-Security',
         "max-age={$duration}; includeSubdomains; preload",
       );
     }
 
     return $headers;
   }
 
   public function setCacheDurationInSeconds($duration) {
     $this->cacheable = $duration;
     return $this;
   }
 
+  public function setCanCDN($can_cdn) {
+    $this->canCDN = $can_cdn;
+    return $this;
+  }
+
   public function setLastModified($epoch_timestamp) {
     $this->lastModified = $epoch_timestamp;
     return $this;
   }
 
   public function setHTTPResponseCode($code) {
     $this->responseCode = $code;
     return $this;
   }
 
   public function getHTTPResponseCode() {
     return $this->responseCode;
   }
 
   public function getHTTPResponseMessage() {
     switch ($this->getHTTPResponseCode()) {
       case 100: return 'Continue';
       case 101: return 'Switching Protocols';
       case 200: return 'OK';
       case 201: return 'Created';
       case 202: return 'Accepted';
       case 203: return 'Non-Authoritative Information';
       case 204: return 'No Content';
       case 205: return 'Reset Content';
       case 206: return 'Partial Content';
       case 300: return 'Multiple Choices';
       case 301: return 'Moved Permanently';
       case 302: return 'Found';
       case 303: return 'See Other';
       case 304: return 'Not Modified';
       case 305: return 'Use Proxy';
       case 306: return 'Switch Proxy';
       case 307: return 'Temporary Redirect';
       case 400: return 'Bad Request';
       case 401: return 'Unauthorized';
       case 402: return 'Payment Required';
       case 403: return 'Forbidden';
       case 404: return 'Not Found';
       case 405: return 'Method Not Allowed';
       case 406: return 'Not Acceptable';
       case 407: return 'Proxy Authentication Required';
       case 408: return 'Request Timeout';
       case 409: return 'Conflict';
       case 410: return 'Gone';
       case 411: return 'Length Required';
       case 412: return 'Precondition Failed';
       case 413: return 'Request Entity Too Large';
       case 414: return 'Request-URI Too Long';
       case 415: return 'Unsupported Media Type';
       case 416: return 'Requested Range Not Satisfiable';
       case 417: return 'Expectation Failed';
       case 418: return "I'm a teapot";
       case 426: return 'Upgrade Required';
       case 500: return 'Internal Server Error';
       case 501: return 'Not Implemented';
       case 502: return 'Bad Gateway';
       case 503: return 'Service Unavailable';
       case 504: return 'Gateway Timeout';
       case 505: return 'HTTP Version Not Supported';
       default:  return '';
     }
   }
 
   public function setFrameable($frameable) {
     $this->frameable = $frameable;
     return $this;
   }
 
   public static function processValueForJSONEncoding(&$value, $key) {
     if ($value instanceof PhutilSafeHTMLProducerInterface) {
       // This renders the producer down to PhutilSafeHTML, which will then
       // be simplified into a string below.
       $value = hsprintf('%s', $value);
     }
 
     if ($value instanceof PhutilSafeHTML) {
       // TODO: Javelin supports implicity conversion of '__html' objects to
       // JX.HTML, but only for Ajax responses, not behaviors. Just leave things
       // as they are for now (where behaviors treat responses as HTML or plain
       // text at their discretion).
       $value = $value->getHTMLContent();
     }
   }
 
   public static function encodeJSONForHTTPResponse(array $object) {
 
     array_walk_recursive(
       $object,
       array(__CLASS__, 'processValueForJSONEncoding'));
 
     $response = json_encode($object);
 
     // Prevent content sniffing attacks by encoding "<" and ">", so browsers
     // won't try to execute the document as HTML even if they ignore
     // Content-Type and X-Content-Type-Options. See T865.
     $response = str_replace(
       array('<', '>'),
       array('\u003c', '\u003e'),
       $response);
 
     return $response;
   }
 
   protected function addJSONShield($json_response) {
     // Add a shield to prevent "JSON Hijacking" attacks where an attacker
     // requests a JSON response using a normal <script /> tag and then uses
     // Object.prototype.__defineSetter__() or similar to read response data.
     // This header causes the browser to loop infinitely instead of handing over
     // sensitive data.
 
     $shield = 'for (;;);';
 
     $response = $shield.$json_response;
 
     return $response;
   }
 
   public function getCacheHeaders() {
     $headers = array();
     if ($this->cacheable) {
+      $cache_control = array();
+      $cache_control[] = sprintf('max-age=%d', $this->cacheable);
+
+      if ($this->canCDN) {
+        $cache_control[] = 'public';
+      } else {
+        $cache_control[] = 'private';
+      }
+
+      $headers[] = array(
+        'Cache-Control',
+        implode(', ', $cache_control),
+      );
+
       $headers[] = array(
         'Expires',
         $this->formatEpochTimestampForHTTPHeader(time() + $this->cacheable),
       );
     } else {
       $headers[] = array(
         'Cache-Control',
-        'private, no-cache, no-store, must-revalidate',
-      );
-      $headers[] = array(
-        'Pragma',
-        'no-cache',
+        'no-store',
       );
       $headers[] = array(
         'Expires',
         'Sat, 01 Jan 2000 00:00:00 GMT',
       );
     }
 
     if ($this->lastModified) {
       $headers[] = array(
         'Last-Modified',
         $this->formatEpochTimestampForHTTPHeader($this->lastModified),
       );
     }
 
     // IE has a feature where it may override an explicit Content-Type
     // declaration by inferring a content type. This can be a security risk
     // and we always explicitly transmit the correct Content-Type header, so
     // prevent IE from using inferred content types. This only offers protection
     // on recent versions of IE; IE6/7 and Opera currently ignore this header.
     $headers[] = array('X-Content-Type-Options', 'nosniff');
 
     return $headers;
   }
 
   private function formatEpochTimestampForHTTPHeader($epoch_timestamp) {
     return gmdate('D, d M Y H:i:s', $epoch_timestamp).' GMT';
   }
 
   public function didCompleteWrite($aborted) {
     return;
   }
 
 }
diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php
index ead3e1b4c..d1ed2178d 100644
--- a/src/applications/almanac/controller/AlmanacBindingViewController.php
+++ b/src/applications/almanac/controller/AlmanacBindingViewController.php
@@ -1,159 +1,159 @@
 <?php
 
 final class AlmanacBindingViewController
   extends AlmanacServiceController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $id = $request->getURIData('id');
 
     $binding = id(new AlmanacBindingQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needProperties(true)
       ->executeOne();
     if (!$binding) {
       return new Aphront404Response();
     }
 
     $service = $binding->getService();
     $service_uri = $service->getURI();
 
     $title = pht('Binding %s', $binding->getID());
 
     $properties = $this->buildPropertyList($binding);
     $details = $this->buildPropertySection($binding);
     $curtain = $this->buildCurtain($binding);
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($title)
       ->setPolicyObject($binding)
       ->setHeaderIcon('fa-object-group');
 
     if ($binding->getIsDisabled()) {
       $header->setStatus('fa-ban', 'red', pht('Disabled'));
     }
 
     $issue = null;
     if ($binding->getService()->isClusterService()) {
       $issue = $this->addClusterMessage(
         pht('The service for this binding is a cluster service.'),
         pht(
           'The service for this binding is a cluster service. You do not '.
           'have permission to manage cluster services, so this binding can '.
           'not be edited.'));
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($service->getName(), $service_uri);
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $binding,
       new AlmanacBindingTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
           $issue,
           $this->buildAlmanacPropertiesTable($binding),
           $timeline,
         ))
-      ->addPropertySection(pht('DETAILS'), $details);
+      ->addPropertySection(pht('Details'), $details);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $view,
       ));
   }
 
   private function buildPropertySection(AlmanacBinding $binding) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(
       pht('Service'),
       $viewer->renderHandle($binding->getServicePHID()));
 
     $properties->addProperty(
       pht('Device'),
       $viewer->renderHandle($binding->getDevicePHID()));
 
     $properties->addProperty(
       pht('Network'),
       $viewer->renderHandle($binding->getInterface()->getNetworkPHID()));
 
     $properties->addProperty(
       pht('Interface'),
       $binding->getInterface()->renderDisplayAddress());
 
     return $properties;
   }
 
   private function buildPropertyList(AlmanacBinding $binding) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($binding);
     $properties->invokeWillRenderEvent();
 
     return $properties;
   }
 
   private function buildCurtain(AlmanacBinding $binding) {
     $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $binding,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $id = $binding->getID();
     $edit_uri = $this->getApplicationURI("binding/edit/{$id}/");
     $disable_uri = $this->getApplicationURI("binding/disable/{$id}/");
 
     $curtain = $this->newCurtainView($binding);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Binding'))
         ->setHref($edit_uri)
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit));
 
     if ($binding->getIsDisabled()) {
       $disable_icon = 'fa-check';
       $disable_text = pht('Enable Binding');
     } else {
       $disable_icon = 'fa-ban';
       $disable_text = pht('Disable Binding');
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon($disable_icon)
         ->setName($disable_text)
         ->setHref($disable_uri)
         ->setWorkflow(true)
         ->setDisabled(!$can_edit));
 
     return $curtain;
   }
 
 }
diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php
index c76fdf11b..c23e99591 100644
--- a/src/applications/almanac/controller/AlmanacController.php
+++ b/src/applications/almanac/controller/AlmanacController.php
@@ -1,214 +1,214 @@
 <?php
 
 abstract class AlmanacController
   extends PhabricatorController {
 
   protected function buildAlmanacPropertiesTable(
     AlmanacPropertyInterface $object) {
 
     $viewer = $this->getViewer();
     $properties = $object->getAlmanacProperties();
 
     $this->requireResource('almanac-css');
     Javelin::initBehavior('phabricator-tooltips', array());
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $object,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $properties = $object->getAlmanacProperties();
 
     $icon_builtin = id(new PHUIIconView())
       ->setIcon('fa-circle')
       ->addSigil('has-tooltip')
       ->setMetadata(
         array(
           'tip' => pht('Builtin Property'),
           'align' => 'E',
         ));
 
     $icon_custom = id(new PHUIIconView())
       ->setIcon('fa-circle-o grey')
       ->addSigil('has-tooltip')
       ->setMetadata(
         array(
           'tip' => pht('Custom Property'),
           'align' => 'E',
         ));
 
     $builtins = $object->getAlmanacPropertyFieldSpecifications();
     $defaults = mpull($builtins, null, 'getValueForTransaction');
 
     // Sort fields so builtin fields appear first, then fields are ordered
     // alphabetically.
     $properties = msort($properties, 'getFieldName');
 
     $head = array();
     $tail = array();
     foreach ($properties as $property) {
       $key = $property->getFieldName();
       if (isset($builtins[$key])) {
         $head[$key] = $property;
       } else {
         $tail[$key] = $property;
       }
     }
 
     $properties = $head + $tail;
 
     $delete_base = $this->getApplicationURI('property/delete/');
     $edit_base = $this->getApplicationURI('property/update/');
 
     $rows = array();
     foreach ($properties as $key => $property) {
       $value = $property->getFieldValue();
 
       $is_builtin = isset($builtins[$key]);
 
       $delete_uri = id(new PhutilURI($delete_base))
         ->setQueryParams(
           array(
             'key' => $key,
             'objectPHID' => $object->getPHID(),
           ));
 
       $edit_uri = id(new PhutilURI($edit_base))
         ->setQueryParams(
           array(
             'key' => $key,
             'objectPHID' => $object->getPHID(),
           ));
 
       $delete = javelin_tag(
         'a',
         array(
           'class' => ($can_edit
             ? 'button grey small'
             : 'button grey small disabled'),
           'sigil' => 'workflow',
           'href' => $delete_uri,
         ),
         $is_builtin ? pht('Reset') : pht('Delete'));
 
       $default = idx($defaults, $key);
       $is_default = ($default !== null && $default === $value);
 
       $display_value = PhabricatorConfigJSON::prettyPrintJSON($value);
       if ($is_default) {
         $display_value = phutil_tag(
           'span',
           array(
             'class' => 'almanac-default-property-value',
           ),
           $display_value);
       }
 
       $display_key = $key;
       if ($can_edit) {
         $display_key = javelin_tag(
           'a',
           array(
             'href' => $edit_uri,
             'sigil' => 'workflow',
           ),
           $display_key);
       }
 
       $rows[] = array(
         ($is_builtin ? $icon_builtin : $icon_custom),
         $display_key,
         $display_value,
         $delete,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setNoDataString(pht('No properties.'))
       ->setHeaders(
         array(
           null,
           pht('Name'),
           pht('Value'),
           null,
         ))
       ->setColumnClasses(
         array(
           null,
           null,
           'wide',
           'action',
         ));
 
     $phid = $object->getPHID();
     $add_uri = id(new PhutilURI($edit_base))
       ->setQueryParam('objectPHID', $object->getPHID());
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $object,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $add_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setHref($add_uri)
       ->setWorkflow(true)
       ->setDisabled(!$can_edit)
       ->setText(pht('Add Property'))
       ->setIcon('fa-plus');
 
     $header = id(new PHUIHeaderView())
-      ->setHeader(pht('PROPERTIES'))
+      ->setHeader(pht('Properties'))
       ->addActionLink($add_button);
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($table);
   }
 
   protected function addClusterMessage(
     $positive,
     $negative) {
 
     $can_manage = $this->hasApplicationCapability(
       AlmanacManageClusterServicesCapability::CAPABILITY);
 
     $doc_link = phutil_tag(
       'a',
       array(
         'href' => PhabricatorEnv::getDoclink(
           'User Guide: Phabricator Clusters'),
         'target' => '_blank',
       ),
       pht('Learn More'));
 
     if ($can_manage) {
       $severity = PHUIInfoView::SEVERITY_NOTICE;
       $message = $positive;
     } else {
       $severity = PHUIInfoView::SEVERITY_WARNING;
       $message = $negative;
     }
 
     $icon = id(new PHUIIconView())
       ->setIcon('fa-sitemap');
 
     return id(new PHUIInfoView())
       ->setSeverity($severity)
       ->setErrors(
         array(
           array($icon, ' ', $message, ' ', $doc_link),
         ));
 
   }
 
   protected function getPropertyDeleteURI($object) {
     return null;
   }
 
   protected function getPropertyUpdateURI($object) {
     return null;
   }
 
 }
diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php
index 1036dc9e7..f4057ca32 100644
--- a/src/applications/almanac/controller/AlmanacServiceViewController.php
+++ b/src/applications/almanac/controller/AlmanacServiceViewController.php
@@ -1,151 +1,151 @@
 <?php
 
 final class AlmanacServiceViewController
   extends AlmanacServiceController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $name = $request->getURIData('name');
 
     $service = id(new AlmanacServiceQuery())
       ->setViewer($viewer)
       ->withNames(array($name))
       ->needProperties(true)
       ->executeOne();
     if (!$service) {
       return new Aphront404Response();
     }
 
     $title = pht('Service %s', $service->getName());
 
     $curtain = $this->buildCurtain($service);
     $details = $this->buildPropertySection($service);
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($service->getName())
       ->setPolicyObject($service)
       ->setHeaderIcon('fa-plug');
 
     $issue = null;
     if ($service->isClusterService()) {
       $issue = $this->addClusterMessage(
         pht('This is a cluster service.'),
         pht(
           'This service is a cluster service. You do not have permission to '.
           'edit cluster services, so you can not edit this service.'));
     }
 
     $bindings = $this->buildBindingList($service);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($service->getName());
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $service,
       new AlmanacServiceTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
           $issue,
           $details,
           $bindings,
           $this->buildAlmanacPropertiesTable($service),
           $timeline,
         ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildPropertySection(
     AlmanacService $service) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(
       pht('Service Type'),
       $service->getServiceImplementation()->getServiceTypeShortName());
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
   }
 
   private function buildCurtain(AlmanacService $service) {
     $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $service,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $id = $service->getID();
     $edit_uri = $this->getApplicationURI("service/edit/{$id}/");
 
     $curtain = $this->newCurtainView($service);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Service'))
         ->setHref($edit_uri)
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit));
 
     return $curtain;
   }
 
   private function buildBindingList(AlmanacService $service) {
     $viewer = $this->getViewer();
     $id = $service->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $service,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $bindings = id(new AlmanacBindingQuery())
       ->setViewer($viewer)
       ->withServicePHIDs(array($service->getPHID()))
       ->execute();
 
     $table = id(new AlmanacBindingTableView())
       ->setNoDataString(
         pht('This service has not been bound to any device interfaces yet.'))
       ->setUser($viewer)
       ->setBindings($bindings)
       ->setHideServiceColumn(true);
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('SERVICE BINDINGS'))
       ->addActionLink(
         id(new PHUIButtonView())
           ->setTag('a')
           ->setHref($this->getApplicationURI("binding/edit/?serviceID={$id}"))
           ->setWorkflow(!$can_edit)
           ->setDisabled(!$can_edit)
           ->setText(pht('Add Binding'))
           ->setIcon('fa-plus'));
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($table);
   }
 
 }
diff --git a/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php b/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php
new file mode 100644
index 000000000..2dd485d8f
--- /dev/null
+++ b/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php
@@ -0,0 +1,51 @@
+<?php
+
+final class PhabricatorAuthLogoutConduitAPIMethod
+  extends PhabricatorAuthConduitAPIMethod {
+
+  public function getAPIMethodName() {
+    return 'auth.logout';
+  }
+
+  public function getMethodSummary() {
+    return pht('Terminate all login sessions.');
+  }
+
+  public function getMethodDescription() {
+    return pht(
+      'Terminate all web login sessions. If called via OAuth, also terminate '.
+      'the current OAuth token.'.
+      "\n\n".
+      'WARNING: This method does what it claims on the label. If you call '.
+      'this method via the test console in the web UI, it will log you out!');
+  }
+
+  protected function defineParamTypes() {
+    return array();
+  }
+
+  protected function defineReturnType() {
+    return 'void';
+  }
+
+  public function getRequiredScope() {
+    return self::SCOPE_ALWAYS;
+  }
+
+  protected function execute(ConduitAPIRequest $request) {
+    $viewer = $request->getUser();
+
+    // Destroy all web sessions.
+    $engine = id(new PhabricatorAuthSessionEngine());
+    $engine->terminateLoginSessions($viewer);
+
+    // If we were called via OAuth, destroy the OAuth token.
+    $oauth_token = $request->getOAuthToken();
+    if ($oauth_token) {
+      $oauth_token->delete();
+    }
+
+    return null;
+  }
+
+}
diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php
index d591b6313..633339a35 100644
--- a/src/applications/auth/controller/PhabricatorAuthStartController.php
+++ b/src/applications/auth/controller/PhabricatorAuthStartController.php
@@ -1,268 +1,285 @@
 <?php
 
 final class PhabricatorAuthStartController
   extends PhabricatorAuthController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     if ($viewer->isLoggedIn()) {
       // Kick the user home if they are already logged in.
       return id(new AphrontRedirectResponse())->setURI('/');
     }
 
     if ($request->isAjax()) {
       return $this->processAjaxRequest();
     }
 
     if ($request->isConduit()) {
       return $this->processConduitRequest();
     }
 
     // If the user gets this far, they aren't logged in, so if they have a
     // user session token we can conclude that it's invalid: if it was valid,
     // they'd have been logged in above and never made it here. Try to clear
     // it and warn the user they may need to nuke their cookies.
 
     $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
+    $did_clear = $request->getStr('cleared');
 
     if (strlen($session_token)) {
       $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(
         $session_token);
       switch ($kind) {
         case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
           // If this is an anonymous session. It's expected that they won't
           // be logged in, so we can just continue.
           break;
         default:
-          // The session cookie is invalid, so clear it.
+          // The session cookie is invalid, so try to clear it.
           $request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
           $request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
 
-          return $this->renderError(
-            pht(
-              'Your login session is invalid. Try reloading the page and '.
-              'logging in again. If that does not work, clear your browser '.
-              'cookies.'));
+          // We've previously tried to clear the cookie but we ended up back
+          // here, so it didn't work. Hard fatal instead of trying again.
+          if ($did_clear) {
+            return $this->renderError(
+              pht(
+                'Your login session is invalid, and clearing the session '.
+                'cookie was unsuccessful. Try clearing your browser cookies.'));
+          }
+
+          $redirect_uri = $request->getRequestURI();
+          $redirect_uri->setQueryParam('cleared', 1);
+          return id(new AphrontRedirectResponse())->setURI($redirect_uri);
       }
     }
 
+    // If we just cleared the session cookie and it worked, clean up after
+    // ourselves by redirecting to get rid of the "cleared" parameter. The
+    // the workflow will continue normally.
+    if ($did_clear) {
+      $redirect_uri = $request->getRequestURI();
+      $redirect_uri->setQueryParam('cleared', null);
+      return id(new AphrontRedirectResponse())->setURI($redirect_uri);
+    }
+
     $providers = PhabricatorAuthProvider::getAllEnabledProviders();
     foreach ($providers as $key => $provider) {
       if (!$provider->shouldAllowLogin()) {
         unset($providers[$key]);
       }
     }
 
     if (!$providers) {
       if ($this->isFirstTimeSetup()) {
         // If this is a fresh install, let the user register their admin
         // account.
         return id(new AphrontRedirectResponse())
           ->setURI($this->getApplicationURI('/register/'));
       }
 
       return $this->renderError(
         pht(
           'This Phabricator install is not configured with any enabled '.
           'authentication providers which can be used to log in. If you '.
           'have accidentally locked yourself out by disabling all providers, '.
           'you can use `%s` to recover access to an administrative account.',
           'phabricator/bin/auth recover <username>'));
     }
 
     $next_uri = $request->getStr('next');
     if (!strlen($next_uri)) {
       if ($this->getDelegatingController()) {
         // Only set a next URI from the request path if this controller was
         // delegated to, which happens when a user tries to view a page which
         // requires them to login.
 
         // If this controller handled the request directly, we're on the main
         // login page, and never want to redirect the user back here after they
         // login.
         $next_uri = (string)$this->getRequest()->getRequestURI();
       }
     }
 
     if (!$request->isFormPost()) {
       if (strlen($next_uri)) {
         PhabricatorCookies::setNextURICookie($request, $next_uri);
       }
       PhabricatorCookies::setClientIDCookie($request);
     }
 
     if (!$request->getURIData('loggedout') && count($providers) == 1) {
       $auto_login_provider = head($providers);
       $auto_login_config = $auto_login_provider->getProviderConfig();
       if ($auto_login_provider instanceof PhabricatorPhabricatorAuthProvider &&
           $auto_login_config->getShouldAutoLogin()) {
         $auto_login_adapter = $provider->getAdapter();
         $auto_login_adapter->setState($provider->getAuthCSRFCode($request));
         return id(new AphrontRedirectResponse())
           ->setIsExternal(true)
           ->setURI($provider->getAdapter()->getAuthenticateURI());
       }
     }
 
     $invite = $this->loadInvite();
 
     $not_buttons = array();
     $are_buttons = array();
     $providers = msort($providers, 'getLoginOrder');
     foreach ($providers as $provider) {
       if ($invite) {
         $form = $provider->buildInviteForm($this);
       } else {
         $form = $provider->buildLoginForm($this);
       }
       if ($provider->isLoginFormAButton()) {
         $are_buttons[] = $form;
       } else {
         $not_buttons[] = $form;
       }
     }
 
     $out = array();
     $out[] = $not_buttons;
     if ($are_buttons) {
       require_celerity_resource('auth-css');
 
       foreach ($are_buttons as $key => $button) {
         $are_buttons[$key] = phutil_tag(
           'div',
           array(
             'class' => 'phabricator-login-button mmb',
           ),
           $button);
       }
 
       // If we only have one button, add a second pretend button so that we
       // always have two columns. This makes it easier to get the alignments
       // looking reasonable.
       if (count($are_buttons) == 1) {
         $are_buttons[] = null;
       }
 
       $button_columns = id(new AphrontMultiColumnView())
         ->setFluidLayout(true);
       $are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
       foreach ($are_buttons as $column) {
         $button_columns->addColumn($column);
       }
 
       $out[] = phutil_tag(
         'div',
         array(
           'class' => 'phabricator-login-buttons',
         ),
         $button_columns);
     }
 
     $handlers = PhabricatorAuthLoginHandler::getAllHandlers();
 
     $delegating_controller = $this->getDelegatingController();
 
     $header = array();
     foreach ($handlers as $handler) {
       $handler = clone $handler;
 
       $handler->setRequest($request);
 
       if ($delegating_controller) {
         $handler->setDelegatingController($delegating_controller);
       }
 
       $header[] = $handler->getAuthLoginHeaderContent();
     }
 
     $invite_message = null;
     if ($invite) {
       $invite_message = $this->renderInviteHeader($invite);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Login'));
     $crumbs->setBorder(true);
 
     $title = pht('Login to Phabricator');
     $view = array(
       $header,
       $invite_message,
       $out,
     );
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
 
   private function processAjaxRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     // We end up here if the user clicks a workflow link that they need to
     // login to use. We give them a dialog saying "You need to login...".
 
     if ($request->isDialogFormPost()) {
       return id(new AphrontRedirectResponse())->setURI(
         $request->getRequestURI());
     }
 
     // Often, users end up here by clicking a disabled action link in the UI
     // (for example, they might click "Edit Blocking Tasks" on a Maniphest
     // task page). After they log in we want to send them back to that main
     // object page if we can, since it's confusing to end up on a standalone
     // page with only a dialog (particularly if that dialog is another error,
     // like a policy exception).
 
     $via_header = AphrontRequest::getViaHeaderName();
     $via_uri = AphrontRequest::getHTTPHeader($via_header);
     if (strlen($via_uri)) {
       PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);
     }
 
     return $this->newDialog()
       ->setTitle(pht('Login Required'))
       ->appendParagraph(pht('You must login to take this action.'))
       ->addSubmitButton(pht('Login'))
       ->addCancelButton('/');
   }
 
 
   private function processConduitRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     // A common source of errors in Conduit client configuration is getting
     // the request path wrong. The client will end up here, so make some
     // effort to give them a comprehensible error message.
 
     $request_path = $this->getRequest()->getPath();
     $conduit_path = '/api/<method>';
     $example_path = '/api/conduit.ping';
 
     $message = pht(
       'ERROR: You are making a Conduit API request to "%s", but the correct '.
       'HTTP request path to use in order to access a COnduit method is "%s" '.
       '(for example, "%s"). Check your configuration.',
       $request_path,
       $conduit_path,
       $example_path);
 
     return id(new AphrontPlainTextResponse())->setContent($message);
   }
 
   protected function renderError($message) {
     return $this->renderErrorPage(
       pht('Authentication Failure'),
       array($message));
   }
 
 }
diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php
index 2600a0831..f4b61f88a 100644
--- a/src/applications/auth/controller/PhabricatorLogoutController.php
+++ b/src/applications/auth/controller/PhabricatorLogoutController.php
@@ -1,69 +1,64 @@
 <?php
 
 final class PhabricatorLogoutController
   extends PhabricatorAuthController {
 
   public function shouldRequireLogin() {
     return true;
   }
 
   public function shouldRequireEmailVerification() {
     // Allow unverified users to logout.
     return false;
   }
 
   public function shouldRequireEnabledUser() {
     // Allow disabled users to logout.
     return false;
   }
 
   public function shouldAllowPartialSessions() {
     return true;
   }
 
   public function shouldAllowLegallyNonCompliantUsers() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     if ($request->isFormPost()) {
-
-      $log = PhabricatorUserLog::initializeNewLog(
-        $viewer,
-        $viewer->getPHID(),
-        PhabricatorUserLog::ACTION_LOGOUT);
-      $log->save();
-
       // Destroy the user's session in the database so logout works even if
       // their cookies have some issues. We'll detect cookie issues when they
       // try to login again and tell them to clear any junk.
       $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
       if (strlen($phsid)) {
         $session = id(new PhabricatorAuthSessionQuery())
           ->setViewer($viewer)
           ->withSessionKeys(array($phsid))
           ->executeOne();
+
         if ($session) {
-          $session->delete();
+          $engine = new PhabricatorAuthSessionEngine();
+          $engine->logoutSession($viewer, $session);
         }
       }
       $request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
 
       return id(new AphrontRedirectResponse())
         ->setURI('/auth/loggedout/');
     }
 
     if ($viewer->getPHID()) {
       return $this->newDialog()
         ->setTitle(pht('Log out of Phabricator?'))
         ->appendChild(pht('Are you sure you want to log out?'))
         ->addSubmitButton(pht('Logout'))
         ->addCancelButton('/');
     }
 
     return id(new AphrontRedirectResponse())->setURI('/');
   }
 
 }
diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
index 98b6a63b5..1547d6bea 100644
--- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
+++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php
@@ -1,717 +1,735 @@
 <?php
 
 /**
  *
  * @task use      Using Sessions
  * @task new      Creating Sessions
  * @task hisec    High Security
  * @task partial  Partial Sessions
  * @task onetime  One Time Login URIs
  */
 final class PhabricatorAuthSessionEngine extends Phobject {
 
   /**
    * Session issued to normal users after they login through a standard channel.
    * Associates the client with a standard user identity.
    */
   const KIND_USER      = 'U';
 
 
   /**
    * Session issued to users who login with some sort of credentials but do not
    * have full accounts. These are sometimes called "grey users".
    *
    * TODO: We do not currently issue these sessions, see T4310.
    */
   const KIND_EXTERNAL  = 'X';
 
 
   /**
    * Session issued to logged-out users which has no real identity information.
    * Its purpose is to protect logged-out users from CSRF.
    */
   const KIND_ANONYMOUS = 'A';
 
 
   /**
    * Session kind isn't known.
    */
   const KIND_UNKNOWN   = '?';
 
 
   const ONETIME_RECOVER = 'recover';
   const ONETIME_RESET = 'reset';
   const ONETIME_WELCOME = 'welcome';
   const ONETIME_USERNAME = 'rename';
 
 
   /**
    * Get the session kind (e.g., anonymous, user, external account) from a
    * session token. Returns a `KIND_` constant.
    *
    * @param   string  Session token.
    * @return  const   Session kind constant.
    */
   public static function getSessionKindFromToken($session_token) {
     if (strpos($session_token, '/') === false) {
       // Old-style session, these are all user sessions.
       return self::KIND_USER;
     }
 
     list($kind, $key) = explode('/', $session_token, 2);
 
     switch ($kind) {
       case self::KIND_ANONYMOUS:
       case self::KIND_USER:
       case self::KIND_EXTERNAL:
         return $kind;
       default:
         return self::KIND_UNKNOWN;
     }
   }
 
 
   /**
    * Load the user identity associated with a session of a given type,
    * identified by token.
    *
    * When the user presents a session token to an API, this method verifies
    * it is of the correct type and loads the corresponding identity if the
    * session exists and is valid.
    *
    * NOTE: `$session_type` is the type of session that is required by the
    * loading context. This prevents use of a Conduit sesssion as a Web
    * session, for example.
    *
    * @param const The type of session to load.
    * @param string The session token.
    * @return PhabricatorUser|null
    * @task use
    */
   public function loadUserForSession($session_type, $session_token) {
     $session_kind = self::getSessionKindFromToken($session_token);
     switch ($session_kind) {
       case self::KIND_ANONYMOUS:
         // Don't bother trying to load a user for an anonymous session, since
         // neither the session nor the user exist.
         return null;
       case self::KIND_UNKNOWN:
         // If we don't know what kind of session this is, don't go looking for
         // it.
         return null;
       case self::KIND_USER:
         break;
       case self::KIND_EXTERNAL:
         // TODO: Implement these (T4310).
         return null;
     }
 
     $session_table = new PhabricatorAuthSession();
     $user_table = new PhabricatorUser();
     $conn_r = $session_table->establishConnection('r');
     $session_key = PhabricatorHash::digest($session_token);
 
     // NOTE: We're being clever here because this happens on every page load,
     // and by joining we can save a query. This might be getting too clever
     // for its own good, though...
 
     $info = queryfx_one(
       $conn_r,
       'SELECT
           s.id AS s_id,
           s.sessionExpires AS s_sessionExpires,
           s.sessionStart AS s_sessionStart,
           s.highSecurityUntil AS s_highSecurityUntil,
           s.isPartial AS s_isPartial,
           s.signedLegalpadDocuments as s_signedLegalpadDocuments,
           u.*
         FROM %T u JOIN %T s ON u.phid = s.userPHID
         AND s.type = %s AND s.sessionKey = %s',
       $user_table->getTableName(),
       $session_table->getTableName(),
       $session_type,
       $session_key);
 
     if (!$info) {
       return null;
     }
 
     $session_dict = array(
       'userPHID' => $info['phid'],
       'sessionKey' => $session_key,
       'type' => $session_type,
     );
     foreach ($info as $key => $value) {
       if (strncmp($key, 's_', 2) === 0) {
         unset($info[$key]);
         $session_dict[substr($key, 2)] = $value;
       }
     }
 
     $user = $user_table->loadFromArray($info);
     switch ($session_type) {
       case PhabricatorAuthSession::TYPE_WEB:
         // Explicitly prevent bots and mailing lists from establishing web
         // sessions. It's normally impossible to attach authentication to these
         // accounts, and likewise impossible to generate sessions, but it's
         // technically possible that a session could exist in the database. If
         // one does somehow, refuse to load it.
         if (!$user->canEstablishWebSessions()) {
           return null;
         }
         break;
     }
 
     $session = id(new PhabricatorAuthSession())->loadFromArray($session_dict);
 
     $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
 
     // If more than 20% of the time on this session has been used, refresh the
     // TTL back up to the full duration. The idea here is that sessions are
     // good forever if used regularly, but get GC'd when they fall out of use.
 
     // NOTE: If we begin rotating session keys when extending sessions, the
     // CSRF code needs to be updated so CSRF tokens survive session rotation.
 
     if (time() + (0.80 * $ttl) > $session->getSessionExpires()) {
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $conn_w = $session_table->establishConnection('w');
         queryfx(
           $conn_w,
           'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d',
           $session->getTableName(),
           $ttl,
           $session->getID());
       unset($unguarded);
     }
 
     $user->attachSession($session);
     return $user;
   }
 
 
   /**
    * Issue a new session key for a given identity. Phabricator supports
    * different types of sessions (like "web" and "conduit") and each session
    * type may have multiple concurrent sessions (this allows a user to be
    * logged in on multiple browsers at the same time, for instance).
    *
    * Note that this method is transport-agnostic and does not set cookies or
    * issue other types of tokens, it ONLY generates a new session key.
    *
    * You can configure the maximum number of concurrent sessions for various
    * session types in the Phabricator configuration.
    *
    * @param   const     Session type constant (see
    *                    @{class:PhabricatorAuthSession}).
    * @param   phid|null Identity to establish a session for, usually a user
    *                    PHID. With `null`, generates an anonymous session.
    * @param   bool      True to issue a partial session.
    * @return  string    Newly generated session key.
    */
   public function establishSession($session_type, $identity_phid, $partial) {
     // Consume entropy to generate a new session key, forestalling the eventual
     // heat death of the universe.
     $session_key = Filesystem::readRandomCharacters(40);
 
     if ($identity_phid === null) {
       return self::KIND_ANONYMOUS.'/'.$session_key;
     }
 
     $session_table = new PhabricatorAuthSession();
     $conn_w = $session_table->establishConnection('w');
 
     // This has a side effect of validating the session type.
     $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type);
 
     $digest_key = PhabricatorHash::digest($session_key);
 
     // Logging-in users don't have CSRF stuff yet, so we have to unguard this
     // write.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       id(new PhabricatorAuthSession())
         ->setUserPHID($identity_phid)
         ->setType($session_type)
         ->setSessionKey($digest_key)
         ->setSessionStart(time())
         ->setSessionExpires(time() + $session_ttl)
         ->setIsPartial($partial ? 1 : 0)
         ->setSignedLegalpadDocuments(0)
         ->save();
 
       $log = PhabricatorUserLog::initializeNewLog(
         null,
         $identity_phid,
         ($partial
           ? PhabricatorUserLog::ACTION_LOGIN_PARTIAL
           : PhabricatorUserLog::ACTION_LOGIN));
 
       $log->setDetails(
         array(
           'session_type' => $session_type,
         ));
       $log->setSession($digest_key);
       $log->save();
     unset($unguarded);
 
     return $session_key;
   }
 
 
   /**
    * Terminate all of a user's login sessions.
    *
    * This is used when users change passwords, linked accounts, or add
    * multifactor authentication.
    *
    * @param PhabricatorUser User whose sessions should be terminated.
    * @param string|null Optionally, one session to keep. Normally, the current
    *   login session.
    *
    * @return void
    */
   public function terminateLoginSessions(
     PhabricatorUser $user,
     $except_session = null) {
 
     $sessions = id(new PhabricatorAuthSessionQuery())
       ->setViewer($user)
       ->withIdentityPHIDs(array($user->getPHID()))
       ->execute();
 
     if ($except_session !== null) {
       $except_session = PhabricatorHash::digest($except_session);
     }
 
     foreach ($sessions as $key => $session) {
       if ($except_session !== null) {
         $is_except = phutil_hashes_are_identical(
           $session->getSessionKey(),
           $except_session);
         if ($is_except) {
           continue;
         }
       }
 
       $session->delete();
     }
   }
 
+  public function logoutSession(
+    PhabricatorUser $user,
+    PhabricatorAuthSession $session) {
+
+    $log = PhabricatorUserLog::initializeNewLog(
+      $user,
+      $user->getPHID(),
+      PhabricatorUserLog::ACTION_LOGOUT);
+    $log->save();
+
+    $extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
+    foreach ($extensions as $extension) {
+      $extension->didLogout($user, array($session));
+    }
+
+    $session->delete();
+  }
+
 
 /* -(  High Security  )------------------------------------------------------ */
 
 
   /**
    * Require high security, or prompt the user to enter high security.
    *
    * If the user's session is in high security, this method will return a
    * token. Otherwise, it will throw an exception which will eventually
    * be converted into a multi-factor authentication workflow.
    *
    * @param PhabricatorUser User whose session needs to be in high security.
    * @param AphrontReqeust  Current request.
    * @param string          URI to return the user to if they cancel.
    * @param bool            True to jump partial sessions directly into high
    *                        security instead of just upgrading them to full
    *                        sessions.
    * @return PhabricatorAuthHighSecurityToken Security token.
    * @task hisec
    */
   public function requireHighSecuritySession(
     PhabricatorUser $viewer,
     AphrontRequest $request,
     $cancel_uri,
     $jump_into_hisec = false) {
 
     if (!$viewer->hasSession()) {
       throw new Exception(
         pht('Requiring a high-security session from a user with no session!'));
     }
 
     $session = $viewer->getSession();
 
     // Check if the session is already in high security mode.
     $token = $this->issueHighSecurityToken($session);
     if ($token) {
       return $token;
     }
 
     // Load the multi-factor auth sources attached to this account.
     $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere(
       'userPHID = %s',
       $viewer->getPHID());
 
     // If the account has no associated multi-factor auth, just issue a token
     // without putting the session into high security mode. This is generally
     // easier for users. A minor but desirable side effect is that when a user
     // adds an auth factor, existing sessions won't get a free pass into hisec,
     // since they never actually got marked as hisec.
     if (!$factors) {
       return $this->issueHighSecurityToken($session, true);
     }
 
     // Check for a rate limit without awarding points, so the user doesn't
     // get partway through the workflow only to get blocked.
     PhabricatorSystemActionEngine::willTakeAction(
       array($viewer->getPHID()),
       new PhabricatorAuthTryFactorAction(),
       0);
 
     $validation_results = array();
     if ($request->isHTTPPost()) {
       $request->validateCSRF();
       if ($request->getExists(AphrontRequest::TYPE_HISEC)) {
 
         // Limit factor verification rates to prevent brute force attacks.
         PhabricatorSystemActionEngine::willTakeAction(
           array($viewer->getPHID()),
           new PhabricatorAuthTryFactorAction(),
           1);
 
         $ok = true;
         foreach ($factors as $factor) {
           $id = $factor->getID();
           $impl = $factor->requireImplementation();
 
           $validation_results[$id] = $impl->processValidateFactorForm(
             $factor,
             $viewer,
             $request);
 
           if (!$impl->isFactorValid($factor, $validation_results[$id])) {
             $ok = false;
           }
         }
 
         if ($ok) {
           // Give the user a credit back for a successful factor verification.
           PhabricatorSystemActionEngine::willTakeAction(
             array($viewer->getPHID()),
             new PhabricatorAuthTryFactorAction(),
             -1);
 
           if ($session->getIsPartial() && !$jump_into_hisec) {
             // If we have a partial session and are not jumping directly into
             // hisec, just issue a token without putting it in high security
             // mode.
             return $this->issueHighSecurityToken($session, true);
           }
 
           $until = time() + phutil_units('15 minutes in seconds');
           $session->setHighSecurityUntil($until);
 
           queryfx(
             $session->establishConnection('w'),
             'UPDATE %T SET highSecurityUntil = %d WHERE id = %d',
             $session->getTableName(),
             $until,
             $session->getID());
 
           $log = PhabricatorUserLog::initializeNewLog(
             $viewer,
             $viewer->getPHID(),
             PhabricatorUserLog::ACTION_ENTER_HISEC);
           $log->save();
         } else {
           $log = PhabricatorUserLog::initializeNewLog(
             $viewer,
             $viewer->getPHID(),
             PhabricatorUserLog::ACTION_FAIL_HISEC);
           $log->save();
         }
       }
     }
 
     $token = $this->issueHighSecurityToken($session);
     if ($token) {
       return $token;
     }
 
     throw id(new PhabricatorAuthHighSecurityRequiredException())
       ->setCancelURI($cancel_uri)
       ->setFactors($factors)
       ->setFactorValidationResults($validation_results);
   }
 
 
   /**
    * Issue a high security token for a session, if authorized.
    *
    * @param PhabricatorAuthSession Session to issue a token for.
    * @param bool Force token issue.
    * @return PhabricatorAuthHighSecurityToken|null Token, if authorized.
    * @task hisec
    */
   private function issueHighSecurityToken(
     PhabricatorAuthSession $session,
     $force = false) {
 
     $until = $session->getHighSecurityUntil();
     if ($until > time() || $force) {
       return new PhabricatorAuthHighSecurityToken();
     }
 
     return null;
   }
 
 
   /**
    * Render a form for providing relevant multi-factor credentials.
    *
    * @param PhabricatorUser Viewing user.
    * @param AphrontRequest Current request.
    * @return AphrontFormView Renderable form.
    * @task hisec
    */
   public function renderHighSecurityForm(
     array $factors,
     array $validation_results,
     PhabricatorUser $viewer,
     AphrontRequest $request) {
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendRemarkupInstructions('');
 
     foreach ($factors as $factor) {
       $factor->requireImplementation()->renderValidateFactorForm(
         $factor,
         $form,
         $viewer,
         idx($validation_results, $factor->getID()));
     }
 
     $form->appendRemarkupInstructions('');
 
     return $form;
   }
 
 
   /**
    * Strip the high security flag from a session.
    *
    * Kicks a session out of high security and logs the exit.
    *
    * @param PhabricatorUser Acting user.
    * @param PhabricatorAuthSession Session to return to normal security.
    * @return void
    * @task hisec
    */
   public function exitHighSecurity(
     PhabricatorUser $viewer,
     PhabricatorAuthSession $session) {
 
     if (!$session->getHighSecurityUntil()) {
       return;
     }
 
     queryfx(
       $session->establishConnection('w'),
       'UPDATE %T SET highSecurityUntil = NULL WHERE id = %d',
       $session->getTableName(),
       $session->getID());
 
     $log = PhabricatorUserLog::initializeNewLog(
       $viewer,
       $viewer->getPHID(),
       PhabricatorUserLog::ACTION_EXIT_HISEC);
     $log->save();
   }
 
 
 /* -(  Partial Sessions  )--------------------------------------------------- */
 
 
   /**
    * Upgrade a partial session to a full session.
    *
    * @param PhabricatorAuthSession Session to upgrade.
    * @return void
    * @task partial
    */
   public function upgradePartialSession(PhabricatorUser $viewer) {
 
     if (!$viewer->hasSession()) {
       throw new Exception(
         pht('Upgrading partial session of user with no session!'));
     }
 
     $session = $viewer->getSession();
 
     if (!$session->getIsPartial()) {
       throw new Exception(pht('Session is not partial!'));
     }
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $session->setIsPartial(0);
 
       queryfx(
         $session->establishConnection('w'),
         'UPDATE %T SET isPartial = %d WHERE id = %d',
         $session->getTableName(),
         0,
         $session->getID());
 
       $log = PhabricatorUserLog::initializeNewLog(
         $viewer,
         $viewer->getPHID(),
         PhabricatorUserLog::ACTION_LOGIN_FULL);
       $log->save();
     unset($unguarded);
   }
 
 
 /* -(  Legalpad Documents )-------------------------------------------------- */
 
 
   /**
    * Upgrade a session to have all legalpad documents signed.
    *
    * @param PhabricatorUser User whose session should upgrade.
    * @param array LegalpadDocument objects
    * @return void
    * @task partial
    */
   public function signLegalpadDocuments(PhabricatorUser $viewer, array $docs) {
 
     if (!$viewer->hasSession()) {
       throw new Exception(
         pht('Signing session legalpad documents of user with no session!'));
     }
 
     $session = $viewer->getSession();
 
     if ($session->getSignedLegalpadDocuments()) {
       throw new Exception(pht(
         'Session has already signed required legalpad documents!'));
     }
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $session->setSignedLegalpadDocuments(1);
 
       queryfx(
         $session->establishConnection('w'),
         'UPDATE %T SET signedLegalpadDocuments = %d WHERE id = %d',
         $session->getTableName(),
         1,
         $session->getID());
 
       if (!empty($docs)) {
         $log = PhabricatorUserLog::initializeNewLog(
           $viewer,
           $viewer->getPHID(),
           PhabricatorUserLog::ACTION_LOGIN_LEGALPAD);
         $log->save();
       }
     unset($unguarded);
   }
 
 
 /* -(  One Time Login URIs  )------------------------------------------------ */
 
 
   /**
    * Retrieve a temporary, one-time URI which can log in to an account.
    *
    * These URIs are used for password recovery and to regain access to accounts
    * which users have been locked out of.
    *
    * @param PhabricatorUser User to generate a URI for.
    * @param PhabricatorUserEmail Optionally, email to verify when
    *  link is used.
    * @param string Optional context string for the URI. This is purely cosmetic
    *  and used only to customize workflow and error messages.
    * @return string Login URI.
    * @task onetime
    */
   public function getOneTimeLoginURI(
     PhabricatorUser $user,
     PhabricatorUserEmail $email = null,
     $type = self::ONETIME_RESET) {
 
     $key = Filesystem::readRandomCharacters(32);
     $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key);
     $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE;
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       id(new PhabricatorAuthTemporaryToken())
         ->setTokenResource($user->getPHID())
         ->setTokenType($onetime_type)
         ->setTokenExpires(time() + phutil_units('1 day in seconds'))
         ->setTokenCode($key_hash)
         ->save();
     unset($unguarded);
 
     $uri = '/login/once/'.$type.'/'.$user->getID().'/'.$key.'/';
     if ($email) {
       $uri = $uri.$email->getID().'/';
     }
 
     try {
       $uri = PhabricatorEnv::getProductionURI($uri);
     } catch (Exception $ex) {
       // If a user runs `bin/auth recover` before configuring the base URI,
       // just show the path. We don't have any way to figure out the domain.
       // See T4132.
     }
 
     return $uri;
   }
 
 
   /**
    * Load the temporary token associated with a given one-time login key.
    *
    * @param PhabricatorUser User to load the token for.
    * @param PhabricatorUserEmail Optionally, email to verify when
    *  link is used.
    * @param string Key user is presenting as a valid one-time login key.
    * @return PhabricatorAuthTemporaryToken|null Token, if one exists.
    * @task onetime
    */
   public function loadOneTimeLoginKey(
     PhabricatorUser $user,
     PhabricatorUserEmail $email = null,
     $key = null) {
 
     $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key);
     $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE;
 
     return id(new PhabricatorAuthTemporaryTokenQuery())
       ->setViewer($user)
       ->withTokenResources(array($user->getPHID()))
       ->withTokenTypes(array($onetime_type))
       ->withTokenCodes(array($key_hash))
       ->withExpired(false)
       ->executeOne();
   }
 
 
   /**
    * Hash a one-time login key for storage as a temporary token.
    *
    * @param PhabricatorUser User this key is for.
    * @param PhabricatorUserEmail Optionally, email to verify when
    *  link is used.
    * @param string The one time login key.
    * @return string Hash of the key.
    * task onetime
    */
   private function getOneTimeLoginKeyHash(
     PhabricatorUser $user,
     PhabricatorUserEmail $email = null,
     $key = null) {
 
     $parts = array(
       $key,
       $user->getAccountSecret(),
     );
 
     if ($email) {
       $parts[] = $email->getVerificationCode();
     }
 
     return PhabricatorHash::digest(implode(':', $parts));
   }
 
 }
diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php
new file mode 100644
index 000000000..267f5be7b
--- /dev/null
+++ b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php
@@ -0,0 +1,23 @@
+<?php
+
+abstract class PhabricatorAuthSessionEngineExtension
+  extends Phobject {
+
+  final public function getExtensionKey() {
+    return $this->getPhobjectClassConstant('EXTENSIONKEY');
+  }
+
+  final public static function getAllExtensions() {
+    return id(new PhutilClassMapQuery())
+      ->setAncestorClass(__CLASS__)
+      ->setUniqueMethod('getExtensionKey')
+      ->execute();
+  }
+
+  abstract public function getExtensionName();
+
+  public function didLogout(PhabricatorUser $user, array $sessions) {
+    return;
+  }
+
+}
diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php
new file mode 100644
index 000000000..9468321d2
--- /dev/null
+++ b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php
@@ -0,0 +1,49 @@
+<?php
+
+final class PhabricatorAuthSessionEngineExtensionModule
+  extends PhabricatorConfigModule {
+
+  public function getModuleKey() {
+    return 'sessionengine';
+  }
+
+  public function getModuleName() {
+    return pht('Engine: Session');
+  }
+
+  public function renderModuleStatus(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+
+    $extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions();
+
+    $rows = array();
+    foreach ($extensions as $extension) {
+      $rows[] = array(
+        get_class($extension),
+        $extension->getExtensionKey(),
+        $extension->getExtensionName(),
+      );
+    }
+
+    $table = id(new AphrontTableView($rows))
+      ->setNoDataString(
+        pht('There are no registered session engine extensions.'))
+      ->setHeaders(
+        array(
+          pht('Class'),
+          pht('Key'),
+          pht('Name'),
+        ))
+      ->setColumnClasses(
+        array(
+          null,
+          null,
+          'wide pri',
+        ));
+
+    return id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('SessionEngine Extensions'))
+      ->setTable($table);
+  }
+
+}
diff --git a/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
index 8ea5e71f4..8799c43c6 100644
--- a/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
@@ -1,204 +1,209 @@
 <?php
 
 final class PhabricatorPhabricatorAuthProvider
   extends PhabricatorOAuth2AuthProvider {
 
   const PROPERTY_PHABRICATOR_NAME = 'oauth2:phabricator:name';
   const PROPERTY_PHABRICATOR_URI  = 'oauth2:phabricator:uri';
 
   public function getProviderName() {
     return pht('Phabricator');
   }
 
   public function getConfigurationHelp() {
     if ($this->isCreate()) {
       return pht(
         "**Step 1 of 2 - Name Phabricator OAuth Instance**\n\n".
         'Choose a permanent name for the OAuth server instance of '.
         'Phabricator. //This// instance of Phabricator uses this name '.
         'internally to keep track of the OAuth server instance of '.
         'Phabricator, in case the URL changes later.');
     }
 
     return parent::getConfigurationHelp();
   }
   protected function getProviderConfigurationHelp() {
     $config = $this->getProviderConfig();
     $base_uri = rtrim(
       $config->getProperty(self::PROPERTY_PHABRICATOR_URI), '/');
     $login_uri = PhabricatorEnv::getURI($this->getLoginURI());
 
     return pht(
       "**Step 2 of 2 - Configure Phabricator OAuth Instance**\n\n".
       "To configure Phabricator OAuth, create a new application here:".
       "\n\n".
       "%s/oauthserver/client/create/".
       "\n\n".
       "When creating your application, use these settings:".
       "\n\n".
       "  - **Redirect URI:** Set this to: `%s`".
       "\n\n".
       "After completing configuration, copy the **Client ID** and ".
       "**Client Secret** to the fields above. (You may need to generate the ".
       "client secret by clicking 'New Secret' first.)",
       $base_uri,
       $login_uri);
   }
 
   protected function newOAuthAdapter() {
     $config = $this->getProviderConfig();
     return id(new PhutilPhabricatorAuthAdapter())
       ->setAdapterDomain($config->getProviderDomain())
       ->setPhabricatorBaseURI(
         $config->getProperty(self::PROPERTY_PHABRICATOR_URI));
   }
 
   protected function getLoginIcon() {
     return 'Phabricator';
   }
 
   private function isCreate() {
     return !$this->getProviderConfig()->getID();
   }
 
   public function readFormValuesFromProvider() {
     $config = $this->getProviderConfig();
     $uri = $config->getProperty(self::PROPERTY_PHABRICATOR_URI);
 
     return parent::readFormValuesFromProvider() + array(
       self::PROPERTY_PHABRICATOR_NAME => $this->getProviderDomain(),
       self::PROPERTY_PHABRICATOR_URI  => $uri,
     );
   }
 
   public function readFormValuesFromRequest(AphrontRequest $request) {
     $is_setup = $this->isCreate();
     if ($is_setup) {
       $parent_values = array();
       $name = $request->getStr(self::PROPERTY_PHABRICATOR_NAME);
     } else {
       $parent_values = parent::readFormValuesFromRequest($request);
       $name = $this->getProviderDomain();
     }
 
     return $parent_values + array(
       self::PROPERTY_PHABRICATOR_NAME => $name,
       self::PROPERTY_PHABRICATOR_URI =>
         $request->getStr(self::PROPERTY_PHABRICATOR_URI),
     );
   }
 
   public function processEditForm(
     AphrontRequest $request,
     array $values) {
 
     $is_setup = $this->isCreate();
 
     if (!$is_setup) {
       list($errors, $issues, $values) =
         parent::processEditForm($request, $values);
     } else {
       $errors = array();
       $issues = array();
     }
 
     $key_name = self::PROPERTY_PHABRICATOR_NAME;
     $key_uri = self::PROPERTY_PHABRICATOR_URI;
 
     if (!strlen($values[$key_name])) {
       $errors[] = pht('Phabricator instance name is required.');
       $issues[$key_name] = pht('Required');
     } else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {
       $errors[] = pht(
         'Phabricator instance name must contain only lowercase letters, '.
         'digits, and periods.');
       $issues[$key_name] = pht('Invalid');
     }
 
     if (!strlen($values[$key_uri])) {
       $errors[] = pht('Phabricator base URI is required.');
       $issues[$key_uri] = pht('Required');
     } else {
       $uri = new PhutilURI($values[$key_uri]);
       if (!$uri->getProtocol()) {
         $errors[] = pht(
           'Phabricator base URI should include protocol (like "%s").',
           'https://');
         $issues[$key_uri] = pht('Invalid');
       }
     }
 
     if (!$errors && $is_setup) {
       $config = $this->getProviderConfig();
 
       $config->setProviderDomain($values[$key_name]);
     }
 
     return array($errors, $issues, $values);
   }
 
   public function extendEditForm(
     AphrontRequest $request,
     AphrontFormView $form,
     array $values,
     array $issues) {
 
     $is_setup = $this->isCreate();
 
     $e_required = $request->isFormPost() ? null : true;
 
     $v_name = $values[self::PROPERTY_PHABRICATOR_NAME];
     if ($is_setup) {
       $e_name = idx($issues, self::PROPERTY_PHABRICATOR_NAME, $e_required);
     } else {
       $e_name = null;
     }
 
     $v_uri = $values[self::PROPERTY_PHABRICATOR_URI];
     $e_uri = idx($issues, self::PROPERTY_PHABRICATOR_URI, $e_required);
 
     if ($is_setup) {
       $form
        ->appendChild(
           id(new AphrontFormTextControl())
             ->setLabel(pht('Phabricator Instance Name'))
             ->setValue($v_name)
             ->setName(self::PROPERTY_PHABRICATOR_NAME)
             ->setError($e_name)
             ->setCaption(pht(
             'Use lowercase letters, digits, and periods. For example: %s',
             phutil_tag(
               'tt',
               array(),
               '`phabricator.oauthserver`'))));
     } else {
       $form
         ->appendChild(
           id(new AphrontFormStaticControl())
             ->setLabel(pht('Phabricator Instance Name'))
             ->setValue($v_name));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Phabricator Base URI'))
           ->setValue($v_uri)
           ->setName(self::PROPERTY_PHABRICATOR_URI)
           ->setCaption(
             pht(
               'The URI where the OAuth server instance of Phabricator is '.
               'installed. For example: %s',
               phutil_tag('tt', array(), 'https://phabricator.mycompany.com/')))
           ->setError($e_uri));
 
     if (!$is_setup) {
       parent::extendEditForm($request, $form, $values, $issues);
     }
   }
 
   public function hasSetupStep() {
     return true;
   }
 
+  public function getPhabricatorURI() {
+    $config = $this->getProviderConfig();
+    return $config->getProperty(self::PROPERTY_PHABRICATOR_URI);
+  }
+
 }
diff --git a/src/applications/auth/query/PhabricatorExternalAccountQuery.php b/src/applications/auth/query/PhabricatorExternalAccountQuery.php
index 9bb611015..b34199ce6 100644
--- a/src/applications/auth/query/PhabricatorExternalAccountQuery.php
+++ b/src/applications/auth/query/PhabricatorExternalAccountQuery.php
@@ -1,211 +1,202 @@
 <?php
 
 /**
  * NOTE: When loading ExternalAccounts for use in an authentication context
  * (that is, you're going to act as the account or link identities or anything
  * like that) you should require CAN_EDIT capability even if you aren't actually
  * editing the ExternalAccount.
  *
  * ExternalAccounts have a permissive CAN_VIEW policy (like users) because they
  * interact directly with objects and can leave comments, sign documents, etc.
  * However, CAN_EDIT is restricted to users who own the accounts.
  */
 final class PhabricatorExternalAccountQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $accountTypes;
   private $accountDomains;
   private $accountIDs;
   private $userPHIDs;
   private $needImages;
   private $accountSecrets;
 
   public function withUserPHIDs(array $user_phids) {
     $this->userPHIDs = $user_phids;
     return $this;
   }
 
   public function withAccountIDs(array $account_ids) {
     $this->accountIDs = $account_ids;
     return $this;
   }
 
   public function withAccountDomains(array $account_domains) {
     $this->accountDomains = $account_domains;
     return $this;
   }
 
   public function withAccountTypes(array $account_types) {
     $this->accountTypes = $account_types;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withIDs($ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withAccountSecrets(array $secrets) {
     $this->accountSecrets = $secrets;
     return $this;
   }
 
   public function needImages($need) {
     $this->needImages = $need;
     return $this;
   }
 
+  public function newResultObject() {
+    return new PhabricatorExternalAccount();
+  }
+
   protected function loadPage() {
-    $table = new PhabricatorExternalAccount();
-    $conn_r = $table->establishConnection('r');
-
-    $data = queryfx_all(
-      $conn_r,
-      'SELECT * FROM %T %Q %Q %Q',
-      $table->getTableName(),
-      $this->buildWhereClause($conn_r),
-      $this->buildOrderClause($conn_r),
-      $this->buildLimitClause($conn_r));
-
-    return $table->loadAllFromArray($data);
+    return $this->loadStandardPage($this->newResultObject());
   }
 
   protected function willFilterPage(array $accounts) {
     if ($this->needImages) {
       $file_phids = mpull($accounts, 'getProfileImagePHID');
       $file_phids = array_filter($file_phids);
 
       if ($file_phids) {
         // NOTE: We use the omnipotent viewer here because these files are
         // usually created during registration and can't be associated with
         // the correct policies, since the relevant user account does not exist
         // yet. In effect, if you can see an ExternalAccount, you can see its
         // profile image.
         $files = id(new PhabricatorFileQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withPHIDs($file_phids)
           ->execute();
         $files = mpull($files, null, 'getPHID');
       } else {
         $files = array();
       }
 
       $default_file = null;
       foreach ($accounts as $account) {
         $image_phid = $account->getProfileImagePHID();
         if ($image_phid && isset($files[$image_phid])) {
           $account->attachProfileImageFile($files[$image_phid]);
         } else {
           if ($default_file === null) {
             $default_file = PhabricatorFile::loadBuiltin(
               $this->getViewer(),
               'profile.png');
           }
           $account->attachProfileImageFile($default_file);
         }
       }
     }
 
     return $accounts;
   }
 
-  protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
-    $where = array();
-
-    $where[] = $this->buildPagingClause($conn_r);
+  protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+    $where = parent::buildWhereClauseParts($conn);
 
-    if ($this->ids) {
+    if ($this->ids !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'id IN (%Ld)',
         $this->ids);
     }
 
-    if ($this->phids) {
+    if ($this->phids !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'phid IN (%Ls)',
         $this->phids);
     }
 
-    if ($this->accountTypes) {
+    if ($this->accountTypes !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'accountType IN (%Ls)',
         $this->accountTypes);
     }
 
-    if ($this->accountDomains) {
+    if ($this->accountDomains !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'accountDomain IN (%Ls)',
         $this->accountDomains);
     }
 
-    if ($this->accountIDs) {
+    if ($this->accountIDs !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'accountID IN (%Ls)',
         $this->accountIDs);
     }
 
-    if ($this->userPHIDs) {
+    if ($this->userPHIDs !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'userPHID IN (%Ls)',
         $this->userPHIDs);
     }
 
-    if ($this->accountSecrets) {
+    if ($this->accountSecrets !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'accountSecret IN (%Ls)',
         $this->accountSecrets);
     }
 
-    return $this->formatWhereClause($where);
+    return $where;
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorPeopleApplication';
   }
 
   /**
    * Attempts to find an external account and if none exists creates a new
    * external account with a shiny new ID and PHID.
    *
    * NOTE: This function assumes the first item in various query parameters is
    * the correct value to use in creating a new external account.
    */
   public function loadOneOrCreate() {
     $account = $this->executeOne();
     if (!$account) {
       $account = new PhabricatorExternalAccount();
       if ($this->accountIDs) {
         $account->setAccountID(reset($this->accountIDs));
       }
       if ($this->accountTypes) {
         $account->setAccountType(reset($this->accountTypes));
       }
       if ($this->accountDomains) {
         $account->setAccountDomain(reset($this->accountDomains));
       }
       if ($this->accountSecrets) {
         $account->setAccountSecret(reset($this->accountSecrets));
       }
       if ($this->userPHIDs) {
         $account->setUserPHID(reset($this->userPHIDs));
       }
       $account->save();
     }
     return $account;
   }
 
 }
diff --git a/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php b/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php
new file mode 100644
index 000000000..42be0c2ba
--- /dev/null
+++ b/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php
@@ -0,0 +1,19 @@
+<?php
+
+final class PhabricatorBadgesEditConduitAPIMethod
+  extends PhabricatorEditEngineAPIMethod {
+
+  public function getAPIMethodName() {
+    return 'badges.edit';
+  }
+
+  public function newEditEngine() {
+    return new PhabricatorBadgesEditEngine();
+  }
+
+  public function getMethodSummary() {
+    return pht(
+      'Apply transactions to create a new badge or edit an existing one.');
+  }
+
+}
diff --git a/src/applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php b/src/applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php
new file mode 100644
index 000000000..5d9fc9c5a
--- /dev/null
+++ b/src/applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorBadgesSearchConduitAPIMethod
+  extends PhabricatorSearchEngineAPIMethod {
+
+  public function getAPIMethodName() {
+    return 'badges.search';
+  }
+
+  public function newSearchEngine() {
+    return new PhabricatorBadgesSearchEngine();
+  }
+
+  public function getMethodSummary() {
+    return pht('Read information about badges.');
+  }
+
+}
diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php
index 453944058..05f628602 100644
--- a/src/applications/badges/controller/PhabricatorBadgesViewController.php
+++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php
@@ -1,159 +1,159 @@
 <?php
 
 final class PhabricatorBadgesViewController
   extends PhabricatorBadgesController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $badge = id(new PhabricatorBadgesQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needRecipients(true)
       ->executeOne();
     if (!$badge) {
       return new Aphront404Response();
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($badge->getName());
     $crumbs->setBorder(true);
     $title = $badge->getName();
 
     if ($badge->isArchived()) {
       $status_icon = 'fa-ban';
       $status_color = 'dark';
     } else {
       $status_icon = 'fa-check';
       $status_color = 'bluegrey';
     }
     $status_name = idx(
       PhabricatorBadgesBadge::getStatusNameMap(),
       $badge->getStatus());
 
     $header = id(new PHUIHeaderView())
       ->setHeader($badge->getName())
       ->setUser($viewer)
       ->setPolicyObject($badge)
       ->setStatus($status_icon, $status_color, $status_name)
       ->setHeaderIcon('fa-trophy');
 
     $curtain = $this->buildCurtain($badge);
     $details = $this->buildDetailsView($badge);
 
     $timeline = $this->buildTransactionTimeline(
       $badge,
       new PhabricatorBadgesTransactionQuery());
 
     $awards = $badge->getAwards();
     $recipient_phids = mpull($awards, 'getRecipientPHID');
     $recipient_phids = array_reverse($recipient_phids);
     $handles = $this->loadViewerHandles($recipient_phids);
 
     $recipient_list = id(new PhabricatorBadgesRecipientsListView())
       ->setBadge($badge)
       ->setHandles($handles)
       ->setUser($viewer);
 
     $comment_view = id(new PhabricatorBadgesEditEngine())
       ->setViewer($viewer)
       ->buildEditEngineCommentView($badge);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
           $recipient_list,
           $timeline,
           $comment_view,
         ))
-      ->addPropertySection(pht('DESCRIPTION'), $details);
+      ->addPropertySection(pht('Description'), $details);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($badge->getPHID()))
       ->appendChild($view);
   }
 
   private function buildDetailsView(
     PhabricatorBadgesBadge $badge) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $description = $badge->getDescription();
     if (strlen($description)) {
       $view->addTextContent(
         new PHUIRemarkupView($viewer, $description));
     }
 
     $badge = id(new PHUIBadgeView())
       ->setIcon($badge->getIcon())
       ->setHeader($badge->getName())
       ->setSubhead($badge->getFlavor())
       ->setQuality($badge->getQuality());
 
     $view->addTextContent($badge);
 
     return $view;
   }
 
   private function buildCurtain(PhabricatorBadgesBadge $badge) {
     $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $badge,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $id = $badge->getID();
     $edit_uri = $this->getApplicationURI("/edit/{$id}/");
     $archive_uri = $this->getApplicationURI("/archive/{$id}/");
     $award_uri = $this->getApplicationURI("/recipients/{$id}/");
 
     $curtain = $this->newCurtainView($badge);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Badge'))
         ->setIcon('fa-pencil')
         ->setDisabled(!$can_edit)
         ->setHref($edit_uri));
 
     if ($badge->isArchived()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Activate Badge'))
           ->setIcon('fa-check')
           ->setDisabled(!$can_edit)
           ->setWorkflow($can_edit)
           ->setHref($archive_uri));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Archive Badge'))
           ->setIcon('fa-ban')
           ->setDisabled(!$can_edit)
           ->setWorkflow($can_edit)
           ->setHref($archive_uri));
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName('Add Recipients')
         ->setIcon('fa-users')
         ->setDisabled(!$can_edit)
         ->setWorkflow(true)
         ->setHref($award_uri));
 
     return $curtain;
   }
 
 }
diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php
index 8c569b5fb..937dc1bd0 100644
--- a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php
+++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php
@@ -1,115 +1,120 @@
 <?php
 
 final class PhabricatorBadgesEditEngine
   extends PhabricatorEditEngine {
 
   const ENGINECONST = 'badges.badge';
 
   public function getEngineName() {
     return pht('Badges');
   }
 
   public function getEngineApplicationClass() {
     return 'PhabricatorBadgesApplication';
   }
 
   public function getSummaryHeader() {
     return pht('Configure Badges Forms');
   }
 
   public function getSummaryText() {
     return pht('Configure creation and editing forms in Badges.');
   }
 
   protected function newEditableObject() {
     return PhabricatorBadgesBadge::initializeNewBadge($this->getViewer());
   }
 
   protected function newObjectQuery() {
     return new PhabricatorBadgesQuery();
   }
 
   protected function getObjectCreateTitleText($object) {
     return pht('Create New Badge');
   }
 
   protected function getObjectEditTitleText($object) {
     return pht('Edit Badge: %s', $object->getName());
   }
 
   protected function getObjectEditShortText($object) {
     return $object->getName();
   }
 
   protected function getObjectCreateShortText() {
     return pht('Create Badge');
   }
 
   protected function getObjectName() {
     return pht('Badge');
   }
 
   protected function getObjectCreateCancelURI($object) {
     return $this->getApplication()->getApplicationURI('/');
   }
 
   protected function getEditorURI() {
     return $this->getApplication()->getApplicationURI('edit/');
   }
 
   protected function getCommentViewHeaderText($object) {
     return pht('Render Honors');
   }
 
   protected function getCommentViewButtonText($object) {
     return pht('Salute');
   }
 
   protected function getObjectViewURI($object) {
     return $object->getViewURI();
   }
 
   protected function getCreateNewObjectPolicy() {
     return $this->getApplication()->getPolicy(
       PhabricatorBadgesCreateCapability::CAPABILITY);
   }
 
   protected function buildCustomEditFields($object) {
 
     return array(
       id(new PhabricatorTextEditField())
         ->setKey('name')
         ->setLabel(pht('Name'))
         ->setDescription(pht('Badge name.'))
+        ->setConduitTypeDescription(pht('New badge name.'))
         ->setTransactionType(PhabricatorBadgesTransaction::TYPE_NAME)
         ->setValue($object->getName()),
       id(new PhabricatorTextEditField())
         ->setKey('flavor')
         ->setLabel(pht('Flavor text'))
         ->setDescription(pht('Short description of the badge.'))
+        ->setConduitTypeDescription(pht('New badge flavor.'))
         ->setValue($object->getFlavor())
         ->setTransactionType(PhabricatorBadgesTransaction::TYPE_FLAVOR),
       id(new PhabricatorIconSetEditField())
         ->setKey('icon')
         ->setLabel(pht('Icon'))
         ->setIconSet(new PhabricatorBadgesIconSet())
         ->setTransactionType(PhabricatorBadgesTransaction::TYPE_ICON)
         ->setConduitDescription(pht('Change the badge icon.'))
         ->setConduitTypeDescription(pht('New badge icon.'))
         ->setValue($object->getIcon()),
       id(new PhabricatorSelectEditField())
         ->setKey('quality')
         ->setLabel(pht('Quality'))
+        ->setDescription(pht('Color and rarity of the badge.'))
+        ->setConduitTypeDescription(pht('New badge quality.'))
         ->setValue($object->getQuality())
         ->setTransactionType(PhabricatorBadgesTransaction::TYPE_QUALITY)
         ->setOptions(PhabricatorBadgesQuality::getDropdownQualityMap()),
       id(new PhabricatorRemarkupEditField())
         ->setKey('description')
         ->setLabel(pht('Description'))
         ->setDescription(pht('Badge long description.'))
+        ->setConduitTypeDescription(pht('New badge description.'))
         ->setTransactionType(PhabricatorBadgesTransaction::TYPE_DESCRIPTION)
         ->setValue($object->getDescription()),
     );
   }
 
 }
diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php
index c8dc23df3..e71dd622c 100644
--- a/src/applications/badges/editor/PhabricatorBadgesEditor.php
+++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php
@@ -1,253 +1,257 @@
 <?php
 
 final class PhabricatorBadgesEditor
   extends PhabricatorApplicationTransactionEditor {
 
   public function getEditorApplicationClass() {
     return 'PhabricatorBadgesApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('Badges');
   }
 
+  protected function supportsSearch() {
+    return true;
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $types[] = PhabricatorBadgesTransaction::TYPE_NAME;
     $types[] = PhabricatorBadgesTransaction::TYPE_FLAVOR;
     $types[] = PhabricatorBadgesTransaction::TYPE_DESCRIPTION;
     $types[] = PhabricatorBadgesTransaction::TYPE_ICON;
     $types[] = PhabricatorBadgesTransaction::TYPE_STATUS;
     $types[] = PhabricatorBadgesTransaction::TYPE_QUALITY;
     $types[] = PhabricatorBadgesTransaction::TYPE_AWARD;
     $types[] = PhabricatorBadgesTransaction::TYPE_REVOKE;
 
     $types[] = PhabricatorTransactions::TYPE_COMMENT;
     $types[] = PhabricatorTransactions::TYPE_EDGE;
     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
 
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorBadgesTransaction::TYPE_NAME:
         return $object->getName();
       case PhabricatorBadgesTransaction::TYPE_FLAVOR:
         return $object->getFlavor();
       case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
         return $object->getDescription();
       case PhabricatorBadgesTransaction::TYPE_ICON:
         return $object->getIcon();
       case PhabricatorBadgesTransaction::TYPE_QUALITY:
         return $object->getQuality();
       case PhabricatorBadgesTransaction::TYPE_STATUS:
         return $object->getStatus();
       case PhabricatorBadgesTransaction::TYPE_AWARD:
         $award_phids = mpull($object->getAwards(), 'getRecipientPHID');
         return $award_phids;
       case PhabricatorBadgesTransaction::TYPE_REVOKE:
         return null;
     }
 
     return parent::getCustomTransactionOldValue($object, $xaction);
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorBadgesTransaction::TYPE_NAME:
       case PhabricatorBadgesTransaction::TYPE_FLAVOR:
       case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
       case PhabricatorBadgesTransaction::TYPE_ICON:
       case PhabricatorBadgesTransaction::TYPE_STATUS:
       case PhabricatorBadgesTransaction::TYPE_AWARD:
       case PhabricatorBadgesTransaction::TYPE_REVOKE:
         return $xaction->getNewValue();
       case PhabricatorBadgesTransaction::TYPE_QUALITY:
         return (int)$xaction->getNewValue();
     }
 
     return parent::getCustomTransactionNewValue($object, $xaction);
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $type = $xaction->getTransactionType();
     switch ($type) {
       case PhabricatorBadgesTransaction::TYPE_NAME:
         $object->setName($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_FLAVOR:
         $object->setFlavor($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
         $object->setDescription($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_ICON:
         $object->setIcon($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_QUALITY:
         $object->setQuality($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_STATUS:
         $object->setStatus($xaction->getNewValue());
         return;
       case PhabricatorBadgesTransaction::TYPE_AWARD:
       case PhabricatorBadgesTransaction::TYPE_REVOKE:
         return;
     }
 
     return parent::applyCustomInternalTransaction($object, $xaction);
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $type = $xaction->getTransactionType();
     switch ($type) {
       case PhabricatorBadgesTransaction::TYPE_NAME:
       case PhabricatorBadgesTransaction::TYPE_FLAVOR:
       case PhabricatorBadgesTransaction::TYPE_DESCRIPTION:
       case PhabricatorBadgesTransaction::TYPE_ICON:
       case PhabricatorBadgesTransaction::TYPE_STATUS:
       case PhabricatorBadgesTransaction::TYPE_QUALITY:
         return;
       case PhabricatorBadgesTransaction::TYPE_REVOKE:
         $revoked_recipient_phids = $xaction->getNewValue();
         $awards = $object->getAwards();
         $awards = mpull($awards, null, 'getRecipientPHID');
 
         foreach ($revoked_recipient_phids as $phid) {
           $awards[$phid]->delete();
         }
         $object->attachAwards($awards);
         return;
       case PhabricatorBadgesTransaction::TYPE_AWARD:
         $recipient_phids = $xaction->getNewValue();
         $awards = $object->getAwards();
         $awards = mpull($awards, null, 'getRecipientPHID');
 
         foreach ($recipient_phids as $phid) {
           $award = idx($awards, $phid);
           if (!$award) {
             $award = PhabricatorBadgesAward::initializeNewBadgesAward(
               $this->getActor(),
               $object,
               $phid);
             $award->save();
             $awards[] = $award;
           }
         }
         $object->attachAwards($awards);
         return;
     }
 
     return parent::applyCustomExternalTransaction($object, $xaction);
   }
 
   protected function validateTransaction(
     PhabricatorLiskDAO $object,
     $type,
     array $xactions) {
 
     $errors = parent::validateTransaction($object, $type, $xactions);
 
     switch ($type) {
       case PhabricatorBadgesTransaction::TYPE_NAME:
         $missing = $this->validateIsEmptyTextField(
           $object->getName(),
           $xactions);
 
         if ($missing) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Required'),
             pht('Badge name is required.'),
             nonempty(last($xactions), null));
 
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         }
       break;
     }
 
     return $errors;
   }
 
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   public function getMailTagsMap() {
     return array(
       PhabricatorBadgesTransaction::MAILTAG_DETAILS =>
         pht('Someone changes the badge\'s details.'),
       PhabricatorBadgesTransaction::MAILTAG_COMMENT =>
         pht('Someone comments on a badge.'),
       PhabricatorBadgesTransaction::MAILTAG_OTHER =>
         pht('Other badge activity not listed above occurs.'),
     );
   }
 
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new PhabricatorBadgesReplyHandler())
       ->setMailReceiver($object);
   }
 
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     $name = $object->getName();
     $id = $object->getID();
     $name = pht('Badge %d', $id);
     return id(new PhabricatorMetaMTAMail())
       ->setSubject($name)
       ->addHeader('Thread-Topic', $name);
   }
 
   protected function getMailTo(PhabricatorLiskDAO $object) {
     return array(
       $object->getCreatorPHID(),
       $this->requireActor()->getPHID(),
     );
   }
 
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $description = $object->getDescription();
     $body = parent::buildMailBody($object, $xactions);
 
     if (strlen($description)) {
       $body->addRemarkupSection(
         pht('BADGE DESCRIPTION'),
         $object->getDescription());
     }
 
     $body->addLinkSection(
       pht('BADGE DETAIL'),
       PhabricatorEnv::getProductionURI('/badges/view/'.$object->getID().'/'));
     return $body;
   }
 
   protected function getMailSubjectPrefix() {
     return pht('[Badge]');
   }
 
 }
diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php
index c5d8b2dd1..b6277c5f3 100644
--- a/src/applications/badges/query/PhabricatorBadgesQuery.php
+++ b/src/applications/badges/query/PhabricatorBadgesQuery.php
@@ -1,140 +1,150 @@
 <?php
 
 final class PhabricatorBadgesQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $qualities;
   private $statuses;
   private $recipientPHIDs;
 
   private $needRecipients;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withQualities(array $qualities) {
     $this->qualities = $qualities;
     return $this;
   }
 
   public function withStatuses(array $statuses) {
     $this->statuses = $statuses;
     return $this;
   }
 
   public function withRecipientPHIDs(array $recipient_phids) {
     $this->recipientPHIDs = $recipient_phids;
     return $this;
   }
 
+  public function withNameNgrams($ngrams) {
+    return $this->withNgramsConstraint(
+      id(new PhabricatorBadgesBadgeNameNgrams()),
+      $ngrams);
+  }
+
   public function needRecipients($need_recipients) {
     $this->needRecipients = $need_recipients;
     return $this;
   }
 
   protected function loadPage() {
     return $this->loadStandardPage($this->newResultObject());
   }
 
+  protected function getPrimaryTableAlias() {
+    return 'badges';
+  }
+
   public function newResultObject() {
     return new PhabricatorBadgesBadge();
   }
 
   protected function didFilterPage(array $badges) {
     if ($this->needRecipients) {
       $query = id(new PhabricatorBadgesAwardQuery())
         ->setViewer($this->getViewer())
         ->withBadgePHIDs(mpull($badges, 'getPHID'))
         ->execute();
 
       $awards = mgroup($query, 'getBadgePHID');
 
       foreach ($badges as $badge) {
         $badge_awards = idx($awards, $badge->getPHID(), array());
         $badge->attachAwards($badge_awards);
       }
     }
 
     return $badges;
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
-        'id IN (%Ld)',
+        'badges.id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
-        'phid IN (%Ls)',
+        'badges.phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->qualities !== null) {
       $where[] = qsprintf(
         $conn,
-        'quality IN (%Ls)',
+        'badges.quality IN (%Ls)',
         $this->qualities);
     }
 
     if ($this->statuses !== null) {
       $where[] = qsprintf(
         $conn,
-        'status IN (%Ls)',
+        'badges.status IN (%Ls)',
         $this->statuses);
     }
 
     return $where;
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorBadgesApplication';
   }
 
   public function getBuiltinOrders() {
     return array(
       'quality' => array(
         'vector' => array('quality', 'id'),
         'name' => pht('Rarity (Rarest First)'),
       ),
       'shoddiness' => array(
         'vector' => array('-quality', '-id'),
         'name' => pht('Rarity (Most Common First)'),
       ),
     ) + parent::getBuiltinOrders();
   }
 
   public function getOrderableColumns() {
     return array(
       'quality' => array(
         'table' => $this->getPrimaryTableAlias(),
         'column' => 'quality',
         'reverse' => true,
         'type' => 'int',
       ),
     ) + parent::getOrderableColumns();
   }
 
   protected function getPagingValueMap($cursor, array $keys) {
     $badge = $this->loadCursorObject($cursor);
     return array(
       'quality' => $badge->getQuality(),
       'id' => $badge->getID(),
     );
   }
 
 }
diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php
index 025ad3b53..fc2bf7ef1 100644
--- a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php
+++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php
@@ -1,162 +1,156 @@
 <?php
 
 final class PhabricatorBadgesSearchEngine
   extends PhabricatorApplicationSearchEngine {
 
   public function getResultTypeDescription() {
     return pht('Badge');
   }
 
   public function getApplicationClassName() {
     return 'PhabricatorBadgesApplication';
   }
 
   public function newQuery() {
     return new PhabricatorBadgesQuery();
   }
 
-  public function buildSavedQueryFromRequest(AphrontRequest $request) {
-    $saved = new PhabricatorSavedQuery();
-
-    $saved->setParameter(
-      'statuses',
-      $this->readListFromRequest($request, 'statuses'));
-
-    $saved->setParameter(
-      'qualities',
-      $this->readListFromRequest($request, 'qualities'));
-
-    return $saved;
-  }
-
   protected function buildCustomSearchFields() {
     return array(
+      id(new PhabricatorSearchTextField())
+        ->setLabel(pht('Name Contains'))
+        ->setKey('name')
+        ->setDescription(pht('Search for badges by name substring.')),
       id(new PhabricatorSearchCheckboxesField())
         ->setKey('qualities')
         ->setLabel(pht('Quality'))
         ->setOptions(PhabricatorBadgesQuality::getDropdownQualityMap()),
       id(new PhabricatorSearchCheckboxesField())
         ->setKey('statuses')
         ->setLabel(pht('Status'))
         ->setOptions(
           id(new PhabricatorBadgesBadge())
             ->getStatusNameMap()),
     );
   }
 
   protected function buildQueryFromParameters(array $map) {
     $query = $this->newQuery();
 
     if ($map['statuses']) {
       $query->withStatuses($map['statuses']);
     }
 
     if ($map['qualities']) {
       $query->withQualities($map['qualities']);
     }
 
+    if ($map['name'] !== null) {
+      $query->withNameNgrams($map['name']);
+    }
+
     return $query;
   }
 
   protected function getURI($path) {
     return '/badges/'.$path;
   }
 
   protected function getBuiltinQueryNames() {
     $names = array();
 
     $names['open'] = pht('Active Badges');
     $names['all'] = pht('All Badges');
 
     return $names;
   }
 
   public function buildSavedQueryFromBuiltin($query_key) {
     $query = $this->newSavedQuery();
     $query->setQueryKey($query_key);
 
     switch ($query_key) {
       case 'all':
         return $query;
       case 'open':
         return $query->setParameter(
           'statuses',
           array(
             PhabricatorBadgesBadge::STATUS_ACTIVE,
           ));
     }
 
     return parent::buildSavedQueryFromBuiltin($query_key);
   }
 
   protected function getRequiredHandlePHIDsForResultList(
     array $badges,
     PhabricatorSavedQuery $query) {
 
     $phids = array();
 
     return $phids;
   }
 
   protected function renderResultList(
     array $badges,
     PhabricatorSavedQuery $query,
     array $handles) {
     assert_instances_of($badges, 'PhabricatorBadgesBadge');
 
     $viewer = $this->requireViewer();
 
     $list = id(new PHUIObjectItemListView());
     foreach ($badges as $badge) {
       $quality_name = PhabricatorBadgesQuality::getQualityName(
         $badge->getQuality());
 
       $mini_badge = id(new PHUIBadgeMiniView())
         ->setHeader($badge->getName())
         ->setIcon($badge->getIcon())
         ->setQuality($badge->getQuality());
 
       $item = id(new PHUIObjectItemView())
         ->setHeader($badge->getName())
         ->setBadge($mini_badge)
         ->setHref('/badges/view/'.$badge->getID().'/')
         ->addAttribute($quality_name)
         ->addAttribute($badge->getFlavor());
 
       if ($badge->isArchived()) {
         $item->setDisabled(true);
         $item->addIcon('fa-ban', pht('Archived'));
       }
 
       $list->addItem($item);
     }
 
     $result = new PhabricatorApplicationSearchResultView();
     $result->setObjectList($list);
     $result->setNoDataString(pht('No badges found.'));
 
     return $result;
 
   }
 
   protected function getNewUserBody() {
     $create_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Create a Badge'))
       ->setHref('/badges/create/')
       ->setColor(PHUIButtonView::GREEN);
 
     $icon = $this->getApplication()->getIcon();
     $app_name =  $this->getApplication()->getName();
     $view = id(new PHUIBigInfoView())
       ->setIcon($icon)
       ->setTitle(pht('Welcome to %s', $app_name))
       ->setDescription(
         pht('Badges let you award and distinguish special users '.
           'throughout your instance.'))
       ->addAction($create_button);
 
       return $view;
   }
 
 }
diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php
index c5b29df09..91ed3cf34 100644
--- a/src/applications/badges/storage/PhabricatorBadgesBadge.php
+++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php
@@ -1,193 +1,239 @@
 <?php
 
 final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorSubscribableInterface,
     PhabricatorTokenReceiverInterface,
     PhabricatorFlaggableInterface,
-    PhabricatorDestructibleInterface {
+    PhabricatorDestructibleInterface,
+    PhabricatorConduitResultInterface,
+    PhabricatorNgramsInterface {
 
   protected $name;
   protected $flavor;
   protected $description;
   protected $icon;
   protected $quality;
   protected $mailKey;
   protected $editPolicy;
   protected $status;
   protected $creatorPHID;
 
   private $awards = self::ATTACHABLE;
 
   const STATUS_ACTIVE = 'open';
   const STATUS_ARCHIVED = 'closed';
 
   const DEFAULT_ICON = 'fa-star';
 
   public static function getStatusNameMap() {
     return array(
       self::STATUS_ACTIVE => pht('Active'),
       self::STATUS_ARCHIVED => pht('Archived'),
     );
   }
 
   public static function initializeNewBadge(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorBadgesApplication'))
       ->executeOne();
 
     $view_policy = PhabricatorPolicies::getMostOpenPolicy();
 
     $edit_policy =
       $app->getPolicy(PhabricatorBadgesDefaultEditCapability::CAPABILITY);
 
     return id(new PhabricatorBadgesBadge())
       ->setIcon(self::DEFAULT_ICON)
       ->setQuality(PhabricatorBadgesQuality::DEFAULT_QUALITY)
       ->setCreatorPHID($actor->getPHID())
       ->setEditPolicy($edit_policy)
+      ->setFlavor('')
+      ->setDescription('')
       ->setStatus(self::STATUS_ACTIVE);
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_COLUMN_SCHEMA => array(
-        'name' => 'text255',
+        'name' => 'sort255',
         'flavor' => 'text255',
         'description' => 'text',
         'icon' => 'text255',
         'quality' => 'uint32',
         'status' => 'text32',
         'mailKey' => 'bytes20',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_creator' => array(
           'columns' => array('creatorPHID', 'dateModified'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return
       PhabricatorPHID::generateNewPHID(PhabricatorBadgesPHIDType::TYPECONST);
   }
 
   public function isArchived() {
     return ($this->getStatus() == self::STATUS_ARCHIVED);
   }
 
   public function attachAwards(array $awards) {
     $this->awards = $awards;
     return $this;
   }
 
   public function getAwards() {
     return $this->assertAttached($this->awards);
   }
 
   public function getViewURI() {
     return '/badges/view/'.$this->getID().'/';
   }
 
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorBadgesEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorBadgesTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorSubscribableInterface  )----------------------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($this->creatorPHID == $phid);
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     return array($this->getCreatorPHID());
   }
 
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $awards = id(new PhabricatorBadgesAwardQuery())
       ->setViewer($engine->getViewer())
       ->withBadgePHIDs(array($this->getPHID()))
       ->execute();
 
     foreach ($awards as $award) {
       $engine->destroyObjectPermanently($award);
     }
 
     $this->openTransaction();
       $this->delete();
     $this->saveTransaction();
   }
 
+/* -(  PhabricatorConduitResultInterface  )---------------------------------- */
+
+
+  public function getFieldSpecificationsForConduit() {
+    return array(
+      id(new PhabricatorConduitSearchFieldSpecification())
+        ->setKey('name')
+        ->setType('string')
+        ->setDescription(pht('The name of the badge.')),
+      id(new PhabricatorConduitSearchFieldSpecification())
+        ->setKey('creatorPHID')
+        ->setType('phid')
+        ->setDescription(pht('User PHID of the creator.')),
+      id(new PhabricatorConduitSearchFieldSpecification())
+        ->setKey('status')
+        ->setType('string')
+        ->setDescription(pht('Active or archived status of the badge.')),
+    );
+  }
+
+  public function getFieldValuesForConduit() {
+    return array(
+      'name' => $this->getName(),
+      'creatorPHID' => $this->getCreatorPHID(),
+      'status' => $this->getStatus(),
+    );
+  }
+
+  public function getConduitSearchAttachments() {
+    return array();
+  }
+
+/* -(  PhabricatorNgramInterface  )------------------------------------------ */
+
+
+  public function newNgrams() {
+    return array(
+      id(new PhabricatorBadgesBadgeNameNgrams())
+        ->setValue($this->getName()),
+    );
+  }
+
 }
diff --git a/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php b/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php
new file mode 100644
index 000000000..c5db47f42
--- /dev/null
+++ b/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorBadgesBadgeNameNgrams
+  extends PhabricatorSearchNgrams {
+
+  public function getNgramKey() {
+    return 'badgename';
+  }
+
+  public function getColumnName() {
+    return 'name';
+  }
+
+  public function getApplicationName() {
+    return 'badges';
+  }
+
+}
diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php
index 20fe2a0b3..7b2ceaa38 100644
--- a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php
+++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php
@@ -1,62 +1,72 @@
 <?php
 
 final class PhabricatorBadgesRecipientsListView extends AphrontView {
 
   private $badge;
   private $handles;
 
   public function setBadge(PhabricatorBadgesBadge $badge) {
     $this->badge = $badge;
     return $this;
   }
 
   public function setHandles(array $handles) {
     $this->handles = $handles;
     return $this;
   }
 
   public function render() {
     $viewer = $this->getViewer();
 
     $badge = $this->badge;
     $handles = $this->handles;
+    $awards = mpull($badge->getAwards(), null, 'getRecipientPHID');
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $badge,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $list = id(new PHUIObjectItemListView())
       ->setNoDataString(pht('This badge does not have any recipients.'))
       ->setFlush(true);
 
     foreach ($handles as $handle) {
       $remove_uri = '/badges/recipients/'.
         $badge->getID().'/remove/?phid='.$handle->getPHID();
 
+      $award = $awards[$handle->getPHID()];
+      $awarder_handle = $viewer->renderHandle($award->getAwarderPHID());
+      $award_date = phabricator_date($award->getDateCreated(), $viewer);
+      $awarder_info = pht(
+        'Awarded by %s on %s',
+        $awarder_handle->render(),
+        $award_date);
+
       $item = id(new PHUIObjectItemView())
         ->setHeader($handle->getFullName())
+        ->setSubhead($awarder_info)
         ->setHref($handle->getURI())
         ->setImageURI($handle->getImageURI());
 
       if ($can_edit) {
         $item->addAction(
           id(new PHUIListItemView())
             ->setIcon('fa-times')
             ->setName(pht('Remove'))
             ->setHref($remove_uri)
             ->setWorkflow(true));
       }
 
       $list->addItem($item);
     }
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('RECIPIENTS'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setObjectList($list);
 
     return $box;
   }
 
 }
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
index 5880db121..7c3b34bc0 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
@@ -1,388 +1,388 @@
 <?php
 
 final class PhabricatorCalendarEventViewController
   extends PhabricatorCalendarController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $sequence = $request->getURIData('sequence');
 
     $timeline = null;
 
     $event = id(new PhabricatorCalendarEventQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$event) {
       return new Aphront404Response();
     }
 
     if ($sequence) {
       $result = $this->getEventAtIndexForGhostPHID(
         $viewer,
         $event->getPHID(),
         $sequence);
 
       if ($result) {
         $parent_event = $event;
         $event = $result;
         $event->attachParentEvent($parent_event);
         return id(new AphrontRedirectResponse())
           ->setURI('/E'.$result->getID());
       } else if ($sequence && $event->getIsRecurring()) {
         $parent_event = $event;
         $event = $event->generateNthGhost($sequence, $viewer);
         $event->attachParentEvent($parent_event);
       } else if ($sequence) {
         return new Aphront404Response();
       }
 
       $title = $event->getMonogram().' ('.$sequence.')';
       $page_title = $title.' '.$event->getName();
       $crumbs = $this->buildApplicationCrumbs();
       $crumbs->addTextCrumb($title, '/'.$event->getMonogram().'/'.$sequence);
 
 
     } else {
       $title = 'E'.$event->getID();
       $page_title = $title.' '.$event->getName();
       $crumbs = $this->buildApplicationCrumbs();
       $crumbs->addTextCrumb($title);
       $crumbs->setBorder(true);
     }
 
     if (!$event->getIsGhostEvent()) {
       $timeline = $this->buildTransactionTimeline(
         $event,
         new PhabricatorCalendarEventTransactionQuery());
     }
 
     $header = $this->buildHeaderView($event);
     $curtain = $this->buildCurtain($event);
     $details = $this->buildPropertySection($event);
     $description = $this->buildDescriptionView($event);
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     $add_comment_header = $is_serious
       ? pht('Add Comment')
       : pht('Add To Plate');
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $event->getPHID());
     if ($sequence) {
       $comment_uri = $this->getApplicationURI(
         '/event/comment/'.$event->getID().'/'.$sequence.'/');
     } else {
       $comment_uri = $this->getApplicationURI(
         '/event/comment/'.$event->getID().'/');
     }
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($event->getPHID())
       ->setDraft($draft)
       ->setHeaderText($add_comment_header)
       ->setAction($comment_uri)
       ->setSubmitButtonName(pht('Add Comment'));
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setMainColumn(array(
           $timeline,
           $add_comment_form,
         ))
       ->setCurtain($curtain)
-      ->addPropertySection(pht('DETAILS'), $details)
-      ->addPropertySection(pht('DESCRIPTION'), $description);
+      ->addPropertySection(pht('Details'), $details)
+      ->addPropertySection(pht('Description'), $description);
 
     return $this->newPage()
       ->setTitle($page_title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($event->getPHID()))
       ->appendChild(
         array(
           $view,
       ));
   }
 
   private function buildHeaderView(
     PhabricatorCalendarEvent $event) {
     $viewer = $this->getViewer();
     $id = $event->getID();
 
     $is_cancelled = $event->getIsCancelled();
     $icon = $is_cancelled ? ('fa-ban') : ('fa-check');
     $color = $is_cancelled ? ('red') : ('bluegrey');
     $status = $is_cancelled ? pht('Cancelled') : pht('Active');
 
     $invite_status = $event->getUserInviteStatus($viewer->getPHID());
     $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
     $is_invite_pending = ($invite_status == $status_invited);
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($event->getName())
       ->setStatus($icon, $color, $status)
       ->setPolicyObject($event)
       ->setHeaderIcon('fa-calendar');
 
     if ($is_invite_pending) {
       $decline_button = id(new PHUIButtonView())
         ->setTag('a')
         ->setIcon('fa-times grey')
         ->setHref($this->getApplicationURI("/event/decline/{$id}/"))
         ->setWorkflow(true)
         ->setText(pht('Decline'));
 
       $accept_button = id(new PHUIButtonView())
         ->setTag('a')
         ->setIcon('fa-check green')
         ->setHref($this->getApplicationURI("/event/accept/{$id}/"))
         ->setWorkflow(true)
         ->setText(pht('Accept'));
 
       $header->addActionLink($decline_button)
         ->addActionLink($accept_button);
     }
     return $header;
   }
 
   private function buildCurtain(PhabricatorCalendarEvent $event) {
     $viewer = $this->getRequest()->getUser();
     $id = $event->getID();
     $is_cancelled = $event->getIsCancelled();
     $is_attending = $event->getIsUserAttending($viewer->getPHID());
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $event,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $edit_label = false;
     $edit_uri = false;
 
     if ($event->getIsGhostEvent()) {
       $index = $event->getSequenceIndex();
       $edit_label = pht('Edit This Instance');
       $edit_uri = "event/edit/{$id}/{$index}/";
     } else if ($event->getIsRecurrenceException()) {
       $edit_label = pht('Edit This Instance');
       $edit_uri = "event/edit/{$id}/";
     } else {
       $edit_label = pht('Edit');
       $edit_uri = "event/edit/{$id}/";
     }
 
     $curtain = $this->newCurtainView($event);
 
     if ($edit_label && $edit_uri) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName($edit_label)
           ->setIcon('fa-pencil')
           ->setHref($this->getApplicationURI($edit_uri))
           ->setDisabled(!$can_edit)
           ->setWorkflow(!$can_edit));
     }
 
     if ($is_attending) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Decline Event'))
           ->setIcon('fa-user-times')
           ->setHref($this->getApplicationURI("event/join/{$id}/"))
           ->setWorkflow(true));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Join Event'))
           ->setIcon('fa-user-plus')
           ->setHref($this->getApplicationURI("event/join/{$id}/"))
           ->setWorkflow(true));
     }
 
     $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");
 
     if ($event->getIsGhostEvent()) {
       $index = $event->getSequenceIndex();
       $can_reinstate = $event->getIsParentCancelled();
 
       $cancel_label = pht('Cancel This Instance');
       $reinstate_label = pht('Reinstate This Instance');
       $cancel_disabled = (!$can_edit || $can_reinstate);
       $cancel_uri = $this->getApplicationURI("event/cancel/{$id}/{$index}/");
     } else if ($event->getIsRecurrenceException()) {
       $can_reinstate = $event->getIsParentCancelled();
       $cancel_label = pht('Cancel This Instance');
       $reinstate_label = pht('Reinstate This Instance');
       $cancel_disabled = (!$can_edit || $can_reinstate);
     } else if ($event->getIsRecurrenceParent()) {
       $cancel_label = pht('Cancel Recurrence');
       $reinstate_label = pht('Reinstate Recurrence');
       $cancel_disabled = !$can_edit;
     } else {
       $cancel_label = pht('Cancel Event');
       $reinstate_label = pht('Reinstate Event');
       $cancel_disabled = !$can_edit;
     }
 
     if ($is_cancelled) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName($reinstate_label)
           ->setIcon('fa-plus')
           ->setHref($cancel_uri)
           ->setDisabled($cancel_disabled)
           ->setWorkflow(true));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName($cancel_label)
           ->setIcon('fa-times')
           ->setHref($cancel_uri)
           ->setDisabled($cancel_disabled)
           ->setWorkflow(true));
     }
 
     return $curtain;
   }
 
   private function buildPropertySection(
     PhabricatorCalendarEvent $event) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     if ($event->getIsAllDay()) {
       $date_start = phabricator_date($event->getDateFrom(), $viewer);
       $date_end = phabricator_date($event->getDateTo(), $viewer);
 
       if ($date_start == $date_end) {
         $properties->addProperty(
           pht('Time'),
           phabricator_date($event->getDateFrom(), $viewer));
       } else {
         $properties->addProperty(
           pht('Starts'),
           phabricator_date($event->getDateFrom(), $viewer));
         $properties->addProperty(
           pht('Ends'),
           phabricator_date($event->getDateTo(), $viewer));
       }
     } else {
       $properties->addProperty(
         pht('Starts'),
         phabricator_datetime($event->getDateFrom(), $viewer));
 
       $properties->addProperty(
         pht('Ends'),
         phabricator_datetime($event->getDateTo(), $viewer));
     }
 
     if ($event->getIsRecurring()) {
       $properties->addProperty(
         pht('Recurs'),
         ucwords(idx($event->getRecurrenceFrequency(), 'rule')));
 
       if ($event->getRecurrenceEndDate()) {
         $properties->addProperty(
           pht('Recurrence Ends'),
           phabricator_datetime($event->getRecurrenceEndDate(), $viewer));
       }
 
       if ($event->getInstanceOfEventPHID()) {
         $properties->addProperty(
           pht('Recurrence of Event'),
           pht('%s of %s',
             $event->getSequenceIndex(),
             $viewer->renderHandle($event->getInstanceOfEventPHID())->render()));
       }
     }
 
     $properties->addProperty(
       pht('Host'),
       $viewer->renderHandle($event->getUserPHID()));
 
     $invitees = $event->getInvitees();
     foreach ($invitees as $key => $invitee) {
       if ($invitee->isUninvited()) {
         unset($invitees[$key]);
       }
     }
 
     if ($invitees) {
       $invitee_list = new PHUIStatusListView();
 
       $icon_invited = PHUIStatusItemView::ICON_OPEN;
       $icon_attending = PHUIStatusItemView::ICON_ACCEPT;
       $icon_declined = PHUIStatusItemView::ICON_REJECT;
 
       $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;
       $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
       $status_declined = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
 
       $icon_map = array(
         $status_invited => $icon_invited,
         $status_attending => $icon_attending,
         $status_declined => $icon_declined,
       );
 
       $icon_color_map = array(
         $status_invited => null,
         $status_attending => 'green',
         $status_declined => 'red',
       );
 
       foreach ($invitees as $invitee) {
         $item = new PHUIStatusItemView();
         $invitee_phid = $invitee->getInviteePHID();
         $status = $invitee->getStatus();
         $target = $viewer->renderHandle($invitee_phid);
         $icon = $icon_map[$status];
         $icon_color = $icon_color_map[$status];
 
         $item->setIcon($icon, $icon_color)
           ->setTarget($target);
         $invitee_list->addItem($item);
       }
     } else {
       $invitee_list = phutil_tag(
         'em',
         array(),
         pht('None'));
     }
 
     $properties->addProperty(
       pht('Invitees'),
       $invitee_list);
 
     $properties->invokeWillRenderEvent();
 
     $properties->addProperty(
       pht('Icon'),
       id(new PhabricatorCalendarIconSet())
         ->getIconLabel($event->getIcon()));
 
     return $properties;
   }
 
   private function buildDescriptionView(
     PhabricatorCalendarEvent $event) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     if (strlen($event->getDescription())) {
       $description = new PHUIRemarkupView($viewer, $event->getDescription());
       $properties->addTextContent($description);
       return $properties;
     }
 
     return null;
   }
 
 }
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
index 06416e43a..51cd0ea25 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
@@ -1,433 +1,435 @@
 <?php
 
 final class PhabricatorCalendarEventQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $rangeBegin;
   private $rangeEnd;
   private $inviteePHIDs;
   private $creatorPHIDs;
   private $isCancelled;
   private $eventsWithNoParent;
   private $instanceSequencePairs;
 
   private $generateGhosts = false;
 
   public function newResultObject() {
     return new PhabricatorCalendarEvent();
   }
 
   public function setGenerateGhosts($generate_ghosts) {
     $this->generateGhosts = $generate_ghosts;
     return $this;
   }
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withDateRange($begin, $end) {
     $this->rangeBegin = $begin;
     $this->rangeEnd = $end;
     return $this;
   }
 
   public function withInvitedPHIDs(array $phids) {
     $this->inviteePHIDs = $phids;
     return $this;
   }
 
   public function withCreatorPHIDs(array $phids) {
     $this->creatorPHIDs = $phids;
     return $this;
   }
 
   public function withIsCancelled($is_cancelled) {
     $this->isCancelled = $is_cancelled;
     return $this;
   }
 
   public function withEventsWithNoParent($events_with_no_parent) {
     $this->eventsWithNoParent = $events_with_no_parent;
     return $this;
   }
 
   public function withInstanceSequencePairs(array $pairs) {
     $this->instanceSequencePairs = $pairs;
     return $this;
   }
 
   protected function getDefaultOrderVector() {
     return array('start', 'id');
   }
 
   public function getOrderableColumns() {
     return array(
       'start' => array(
         'table' => $this->getPrimaryTableAlias(),
         'column' => 'dateFrom',
         'reverse' => true,
         'type' => 'int',
         'unique' => false,
       ),
     ) + parent::getOrderableColumns();
   }
 
   protected function getPagingValueMap($cursor, array $keys) {
     $event = $this->loadCursorObject($cursor);
     return array(
       'start' => $event->getDateFrom(),
       'id' => $event->getID(),
     );
   }
 
   protected function loadPage() {
-    $table = new PhabricatorCalendarEvent();
-    $conn_r = $table->establishConnection('r');
-    $viewer = $this->getViewer();
-
-    $data = queryfx_all(
-      $conn_r,
-      'SELECT event.* FROM %T event %Q %Q %Q %Q %Q',
-      $table->getTableName(),
-      $this->buildJoinClause($conn_r),
-      $this->buildWhereClause($conn_r),
-      $this->buildGroupClause($conn_r),
-      $this->buildOrderClause($conn_r),
-      $this->buildLimitClause($conn_r));
-
-    $events = $table->loadAllFromArray($data);
+    $events = $this->loadStandardPage($this->newResultObject());
 
+    $viewer = $this->getViewer();
     foreach ($events as $event) {
-      $event->applyViewerTimezone($this->getViewer());
+      $event->applyViewerTimezone($viewer);
     }
 
     if (!$this->generateGhosts) {
       return $events;
     }
 
     $enforced_end = null;
+    $raw_limit = $this->getRawResultLimit();
+
+    if (!$raw_limit && !$this->rangeEnd) {
+      throw new Exception(
+        pht(
+          'Event queries which generate ghost events must include either a '.
+          'result limit or an end date, because they may otherwise generate '.
+          'an infinite number of results. This query has neither.'));
+    }
 
     foreach ($events as $key => $event) {
       $sequence_start = 0;
       $sequence_end = null;
       $duration = $event->getDateTo() - $event->getDateFrom();
       $end = null;
 
       $instance_of = $event->getInstanceOfEventPHID();
 
       if ($instance_of == null && $this->isCancelled !== null) {
         if ($event->getIsCancelled() != $this->isCancelled) {
           unset($events[$key]);
           continue;
         }
       }
 
       if ($event->getIsRecurring() && $instance_of == null) {
         $frequency = $event->getFrequencyUnit();
         $modify_key = '+1 '.$frequency;
 
         if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) {
           $max_date = $this->rangeBegin - $duration;
           $date = $event->getDateFrom();
           $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
 
           while ($date < $max_date) {
             // TODO: optimize this to not loop through all off-screen events
             $sequence_start++;
             $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
             $date = $datetime->modify($modify_key)->format('U');
           }
 
           $start = $this->rangeBegin;
         } else {
           $start = $event->getDateFrom() - $duration;
         }
 
         $date = $start;
         $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
 
         if (($this->rangeEnd && $event->getRecurrenceEndDate()) &&
           $this->rangeEnd < $event->getRecurrenceEndDate()) {
           $end = $this->rangeEnd;
         } else if ($event->getRecurrenceEndDate()) {
           $end = $event->getRecurrenceEndDate();
         } else if ($this->rangeEnd) {
           $end = $this->rangeEnd;
         } else if ($enforced_end) {
           if ($end) {
             $end = min($end, $enforced_end);
           } else {
             $end = $enforced_end;
           }
         }
 
         if ($end) {
           $sequence_end = $sequence_start;
           while ($date < $end) {
             $sequence_end++;
             $datetime->modify($modify_key);
             $date = $datetime->format('U');
-            if ($sequence_end > $this->getRawResultLimit() + $sequence_start) {
+            if ($sequence_end > $raw_limit + $sequence_start) {
               break;
             }
           }
         } else {
-          $sequence_end = $this->getRawResultLimit() + $sequence_start;
+          $sequence_end = $raw_limit + $sequence_start;
         }
 
         $sequence_start = max(1, $sequence_start);
 
         for ($index = $sequence_start; $index < $sequence_end; $index++) {
           $events[] = $event->generateNthGhost($index, $viewer);
         }
 
-        if (count($events) >= $this->getRawResultLimit()) {
-          $events = msort($events, 'getDateFrom');
-          $events = array_slice($events, 0, $this->getRawResultLimit(), true);
-          $enforced_end = last($events)->getDateFrom();
+        // NOTE: We're slicing results every time because this makes it cheaper
+        // to generate future ghosts. If we already have 100 events that occur
+        // before July 1, we know we never need to generate ghosts after that
+        // because they couldn't possibly ever appear in the result set.
+
+        if ($raw_limit) {
+          if (count($events) >= $raw_limit) {
+            $events = msort($events, 'getDateFrom');
+            $events = array_slice($events, 0, $raw_limit, true);
+            $enforced_end = last($events)->getDateFrom();
+          }
         }
       }
     }
 
     $map = array();
     $instance_sequence_pairs = array();
 
     foreach ($events as $key => $event) {
       if ($event->getIsGhostEvent()) {
         $index = $event->getSequenceIndex();
         $instance_sequence_pairs[] = array($event->getPHID(), $index);
         $map[$event->getPHID()][$index] = $key;
       }
     }
 
     if (count($instance_sequence_pairs) > 0) {
       $sub_query = id(new PhabricatorCalendarEventQuery())
         ->setViewer($viewer)
         ->setParentQuery($this)
         ->withInstanceSequencePairs($instance_sequence_pairs)
         ->execute();
 
       foreach ($sub_query as $edited_ghost) {
         $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID());
         $key = idx($indexes, $edited_ghost->getSequenceIndex());
         $events[$key] = $edited_ghost;
       }
 
       $id_map = array();
       foreach ($events as $key => $event) {
         if ($event->getIsGhostEvent()) {
           continue;
         }
         if (isset($id_map[$event->getID()])) {
           unset($events[$key]);
         } else {
           $id_map[$event->getID()] = true;
         }
       }
     }
 
     return $events;
   }
 
   protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
     $parts = parent::buildJoinClauseParts($conn_r);
     if ($this->inviteePHIDs !== null) {
       $parts[] = qsprintf(
         $conn_r,
         'JOIN %T invitee ON invitee.eventPHID = event.phid
           AND invitee.status != %s',
         id(new PhabricatorCalendarEventInvitee())->getTableName(),
         PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
     }
     return $parts;
   }
 
-  protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
-    $where = array();
+  protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+    $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->rangeBegin) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.dateTo >= %d OR event.isRecurring = 1',
         $this->rangeBegin);
     }
 
     if ($this->rangeEnd) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.dateFrom <= %d',
         $this->rangeEnd);
     }
 
     if ($this->inviteePHIDs !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'invitee.inviteePHID IN (%Ls)',
         $this->inviteePHIDs);
     }
 
     if ($this->creatorPHIDs) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.userPHID IN (%Ls)',
         $this->creatorPHIDs);
     }
 
     if ($this->isCancelled !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.isCancelled = %d',
         (int)$this->isCancelled);
     }
 
     if ($this->eventsWithNoParent == true) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'event.instanceOfEventPHID IS NULL');
     }
 
     if ($this->instanceSequencePairs !== null) {
       $sql = array();
 
       foreach ($this->instanceSequencePairs as $pair) {
         $sql[] = qsprintf(
-          $conn_r,
+          $conn,
           '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)',
           $pair[0],
           $pair[1]);
       }
+
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         '%Q',
         implode(' OR ', $sql));
     }
 
-    $where[] = $this->buildPagingClause($conn_r);
-
-    return $this->formatWhereClause($where);
+    return $where;
   }
 
   protected function getPrimaryTableAlias() {
     return 'event';
   }
 
   protected function shouldGroupQueryResultRows() {
     if ($this->inviteePHIDs !== null) {
       return true;
     }
     return parent::shouldGroupQueryResultRows();
   }
 
   protected function getApplicationSearchObjectPHIDColumn() {
     return 'event.phid';
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorCalendarApplication';
   }
 
 
   protected function willFilterPage(array $events) {
     $range_start = $this->rangeBegin;
     $range_end = $this->rangeEnd;
     $instance_of_event_phids = array();
     $recurring_events = array();
     $viewer = $this->getViewer();
 
     foreach ($events as $key => $event) {
       $event_start = $event->getDateFrom();
       $event_end = $event->getDateTo();
 
       if ($range_start && $event_end < $range_start) {
         unset($events[$key]);
       }
       if ($range_end && $event_start > $range_end) {
         unset($events[$key]);
       }
     }
 
     $phids = array();
 
     foreach ($events as $event) {
       $phids[] = $event->getPHID();
       $instance_of = $event->getInstanceOfEventPHID();
 
       if ($instance_of) {
         $instance_of_event_phids[] = $instance_of;
       }
     }
 
     if (count($instance_of_event_phids) > 0) {
       $recurring_events = id(new PhabricatorCalendarEventQuery())
         ->setViewer($viewer)
         ->withPHIDs($instance_of_event_phids)
         ->withEventsWithNoParent(true)
         ->execute();
 
       $recurring_events = mpull($recurring_events, null, 'getPHID');
     }
 
     if ($events) {
       $invitees = id(new PhabricatorCalendarEventInviteeQuery())
         ->setViewer($viewer)
         ->withEventPHIDs($phids)
         ->execute();
       $invitees = mgroup($invitees, 'getEventPHID');
     } else {
       $invitees = array();
     }
 
     foreach ($events as $key => $event) {
       $event_invitees = idx($invitees, $event->getPHID(), array());
       $event->attachInvitees($event_invitees);
 
       $instance_of = $event->getInstanceOfEventPHID();
       if (!$instance_of) {
         continue;
       }
       $parent = idx($recurring_events, $instance_of);
 
       // should never get here
       if (!$parent) {
         unset($events[$key]);
         continue;
       }
       $event->attachParentEvent($parent);
 
       if ($this->isCancelled !== null) {
         if ($event->getIsCancelled() != $this->isCancelled) {
           unset($events[$key]);
           continue;
         }
       }
     }
 
     $events = msort($events, 'getDateFrom');
 
     return $events;
   }
 
 }
diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php
index 01f642438..4e6feeac6 100644
--- a/src/applications/celerity/controller/CelerityResourceController.php
+++ b/src/applications/celerity/controller/CelerityResourceController.php
@@ -1,172 +1,173 @@
 <?php
 
 abstract class CelerityResourceController extends PhabricatorController {
 
   protected function buildResourceTransformer() {
     return null;
   }
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function shouldRequireEnabledUser() {
     return false;
   }
 
   public function shouldAllowPartialSessions() {
     return true;
   }
 
   public function shouldAllowLegallyNonCompliantUsers() {
     return true;
   }
 
   abstract public function getCelerityResourceMap();
 
   protected function serveResource($path, $package_hash = null) {
     // Sanity checking to keep this from exposing anything sensitive, since it
     // ultimately boils down to disk reads.
     if (preg_match('@(//|\.\.)@', $path)) {
       return new Aphront400Response();
     }
 
     $type = CelerityResourceTransformer::getResourceType($path);
     $type_map = self::getSupportedResourceTypes();
 
     if (empty($type_map[$type])) {
       throw new Exception(pht('Only static resources may be served.'));
     }
 
     $dev_mode = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
 
     if (AphrontRequest::getHTTPHeader('If-Modified-Since') && !$dev_mode) {
       // Return a "304 Not Modified". We don't care about the value of this
       // field since we never change what resource is served by a given URI.
       return $this->makeResponseCacheable(new Aphront304Response());
     }
 
     $is_cacheable = (!$dev_mode) &&
                     $this->isCacheableResourceType($type);
 
     $cache = null;
     $data = null;
     if ($is_cacheable) {
       $cache = PhabricatorCaches::getImmutableCache();
 
       $request_path = $this->getRequest()->getPath();
       $cache_key = $this->getCacheKey($request_path);
 
       $data = $cache->getKey($cache_key);
     }
 
     if ($data === null) {
       $map = $this->getCelerityResourceMap();
 
       if ($map->isPackageResource($path)) {
         $resource_names = $map->getResourceNamesForPackageName($path);
         if (!$resource_names) {
           return new Aphront404Response();
         }
 
         try {
           $data = array();
           foreach ($resource_names as $resource_name) {
             $data[] = $map->getResourceDataForName($resource_name);
           }
           $data = implode("\n\n", $data);
         } catch (Exception $ex) {
           return new Aphront404Response();
         }
       } else {
         try {
           $data = $map->getResourceDataForName($path);
         } catch (Exception $ex) {
           return new Aphront404Response();
         }
       }
 
       $xformer = $this->buildResourceTransformer();
       if ($xformer) {
         $data = $xformer->transformResource($path, $data);
       }
 
       if ($cache) {
         $cache->setKey($cache_key, $data);
       }
     }
 
     $response = new AphrontFileResponse();
     $response->setContent($data);
     $response->setMimeType($type_map[$type]);
 
     // NOTE: This is a piece of magic required to make WOFF fonts work in
     // Firefox and IE. Possibly we should generalize this more.
 
     $cross_origin_types = array(
       'woff' => true,
       'woff2' => true,
       'eot' => true,
     );
 
     if (isset($cross_origin_types[$type])) {
       // We could be more tailored here, but it's not currently trivial to
       // generate a comprehensive list of valid origins (an install may have
       // arbitrarily many Phame blogs, for example), and we lose nothing by
       // allowing access from anywhere.
       $response->addAllowOrigin('*');
     }
 
     return $this->makeResponseCacheable($response);
   }
 
   public static function getSupportedResourceTypes() {
     return array(
       'css' => 'text/css; charset=utf-8',
       'js'  => 'text/javascript; charset=utf-8',
       'png' => 'image/png',
       'svg' => 'image/svg+xml',
       'gif' => 'image/gif',
       'jpg' => 'image/jpeg',
       'swf' => 'application/x-shockwave-flash',
       'woff' => 'font/woff',
       'woff2' => 'font/woff2',
       'eot' => 'font/eot',
       'ttf' => 'font/ttf',
       'mp3' => 'audio/mpeg',
     );
   }
 
   private function makeResponseCacheable(AphrontResponse $response) {
     $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
     $response->setLastModified(time());
+    $response->setCanCDN(true);
 
     return $response;
   }
 
 
   /**
    * Is it appropriate to cache the data for this resource type in the fast
    * immutable cache?
    *
    * Generally, text resources (which are small, and expensive to process)
    * are cached, while other types of resources (which are large, and cheap
    * to process) are not.
    *
    * @param string  Resource type.
    * @return bool   True to enable caching.
    */
   private function isCacheableResourceType($type) {
     $types = array(
       'js' => true,
       'css' => true,
     );
 
     return isset($types[$type]);
   }
 
   protected function getCacheKey($path) {
     return 'celerity:'.PhabricatorHash::digestToLength($path, 64);
   }
 
 }
diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
index cdff18963..530c26770 100644
--- a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
+++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
@@ -1,44 +1,41 @@
 <?php
 
 final class PhabricatorChatLogChannelListController
   extends PhabricatorChatLogController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $channels = id(new PhabricatorChatLogChannelQuery())
       ->setViewer($viewer)
       ->execute();
 
     $list = new PHUIObjectItemListView();
     foreach ($channels as $channel) {
         $item = id(new PHUIObjectItemView())
           ->setHeader($channel->getChannelName())
           ->setHref('/chatlog/channel/'.$channel->getID().'/')
           ->addAttribute($channel->getServiceName())
           ->addAttribute($channel->getServiceType());
         $list->addItem($item);
     }
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb(pht('Channel List'), $this->getApplicationURI());
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText('Channel List')
       ->setObjectList($list);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('Channel List'),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('Channel List'))
+      ->setCrumbs($crumbs)
+      ->appendChild($box);
+
   }
 }
diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
index fe505ecf6..2c6e58da5 100644
--- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
+++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
@@ -1,324 +1,321 @@
 <?php
 
 final class PhabricatorChatLogChannelLogController
   extends PhabricatorChatLogController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('channelID');
 
     $uri = clone $request->getRequestURI();
     $uri->setQueryParams(array());
 
     $pager = new AphrontCursorPagerView();
     $pager->setURI($uri);
     $pager->setPageSize(250);
 
     $query = id(new PhabricatorChatLogQuery())
       ->setViewer($viewer)
       ->withChannelIDs(array($id));
 
     $channel = id(new PhabricatorChatLogChannelQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
 
     if (!$channel) {
       return new Aphront404Response();
     }
 
     list($after, $before, $map) = $this->getPagingParameters($request, $query);
 
     $pager->setAfterID($after);
     $pager->setBeforeID($before);
 
     $logs = $query->executeWithCursorPager($pager);
 
     // Show chat logs oldest-first.
     $logs = array_reverse($logs);
 
 
     // Divide all the logs into blocks, where a block is the same author saying
     // several things in a row. A block ends when another user speaks, or when
     // two minutes pass without the author speaking.
 
     $blocks = array();
     $block = null;
 
     $last_author = null;
     $last_epoch = null;
     foreach ($logs as $log) {
       $this_author = $log->getAuthor();
       $this_epoch  = $log->getEpoch();
 
       // Decide whether we should start a new block or not.
       $new_block = ($this_author !== $last_author) ||
                    ($this_epoch - (60 * 2) > $last_epoch);
 
       if ($new_block) {
         if ($block) {
           $blocks[] = $block;
         }
         $block = array(
           'id'      => $log->getID(),
           'epoch'   => $this_epoch,
           'author'  => $this_author,
           'logs'    => array($log),
         );
       } else {
         $block['logs'][] = $log;
       }
 
       $last_author = $this_author;
       $last_epoch = $this_epoch;
     }
     if ($block) {
       $blocks[] = $block;
     }
 
     // Figure out CSS classes for the blocks. We alternate colors between
     // lines, and highlight the entire block which contains the target ID or
     // date, if applicable.
 
     foreach ($blocks as $key => $block) {
       $classes = array();
       if ($key % 2) {
         $classes[] = 'alternate';
       }
       $ids = mpull($block['logs'], 'getID', 'getID');
       if (array_intersect_key($ids, $map)) {
         $classes[] = 'highlight';
       }
       $blocks[$key]['class'] = $classes ? implode(' ', $classes) : null;
     }
 
 
     require_celerity_resource('phabricator-chatlog-css');
 
     $out = array();
     foreach ($blocks as $block) {
       $author = $block['author'];
       $author = id(new PhutilUTF8StringTruncator())
         ->setMaximumGlyphs(18)
         ->truncateString($author);
       $author = phutil_tag('td', array('class' => 'author'), $author);
 
       $href = $uri->alter('at', $block['id']);
       $timestamp = $block['epoch'];
       $timestamp = phabricator_datetime($timestamp, $viewer);
       $timestamp = phutil_tag(
         'a',
           array(
             'href' => $href,
             'class' => 'timestamp',
           ),
         $timestamp);
 
       $message = mpull($block['logs'], 'getMessage');
       $message = implode("\n", $message);
       $message = phutil_tag(
         'td',
           array(
             'class' => 'message',
           ),
           array(
             $timestamp,
             $message,
           ));
 
       $out[] = phutil_tag(
         'tr',
         array(
           'class' => $block['class'],
         ),
         array(
           $author,
           $message,
         ));
     }
 
     $links = array();
 
     $first_uri = $pager->getFirstPageURI();
     if ($first_uri) {
       $links[] = phutil_tag(
         'a',
         array(
           'href' => $first_uri,
         ),
         "\xC2\xAB ".pht('Newest'));
     }
 
     $prev_uri = $pager->getPrevPageURI();
     if ($prev_uri) {
       $links[] = phutil_tag(
         'a',
         array(
           'href' => $prev_uri,
         ),
         "\xE2\x80\xB9 ".pht('Newer'));
     }
 
     $next_uri = $pager->getNextPageURI();
     if ($next_uri) {
       $links[] = phutil_tag(
         'a',
         array(
           'href' => $next_uri,
         ),
         pht('Older')." \xE2\x80\xBA");
     }
 
     $pager_bottom = phutil_tag(
       'div',
       array('class' => 'phabricator-chat-log-pager-bottom'),
       $links);
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb($channel->getChannelName(), $uri);
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setMethod('GET')
       ->setAction($uri)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Date'))
           ->setName('date')
           ->setValue($request->getStr('date')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Jump')));
 
     $table = phutil_tag(
       'table',
         array(
           'class' => 'phabricator-chat-log',
         ),
       $out);
 
     $log = phutil_tag(
       'div',
         array(
           'class' => 'phabricator-chat-log-panel',
         ),
         $table);
 
     $jump_link = id(new PHUIButtonView())
       ->setTag('a')
       ->setHref('#latest')
       ->setText(pht('Jump to Bottom'))
       ->setIcon('fa-arrow-circle-down');
 
     $jump_target = phutil_tag(
       'div',
         array(
           'id' => 'latest',
         ));
 
     $content = phutil_tag(
       'div',
         array(
           'class' => 'phabricator-chat-log-wrap',
         ),
         array(
           $log,
           $jump_target,
           $pager_bottom,
         ));
 
     $header = id(new PHUIHeaderView())
       ->setHeader($channel->getChannelName())
       ->setSubHeader($channel->getServiceName())
       ->addActionLink($jump_link);
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setCollapsed(true)
       ->appendChild($content);
 
     $box->setShowHide(
       pht('Search Dates'),
       pht('Hide Dates'),
       $form,
       '#');
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('Channel Log'),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('Channel Log'))
+      ->setCrumbs($crumbs)
+      ->appendChild($box);
+
   }
 
   /**
    * From request parameters, figure out where we should jump to in the log.
    * We jump to either a date or log ID, but load a few lines of context before
    * it so the user can see the nearby conversation.
    */
   private function getPagingParameters(
     AphrontRequest $request,
     PhabricatorChatLogQuery $query) {
 
     $viewer = $request->getViewer();
 
     $at_id = $request->getInt('at');
     $at_date = $request->getStr('date');
 
     $context_log = null;
     $map = array();
 
     $query = clone $query;
     $query->setLimit(8);
 
     if ($at_id) {
       // Jump to the log in question, and load a few lines of context before
       // it.
       $context_logs = $query
         ->setAfterID($at_id)
         ->execute();
 
       $context_log = last($context_logs);
 
       $map = array(
         $at_id => true,
       );
 
     } else if ($at_date) {
       $timestamp = PhabricatorTime::parseLocalTime($at_date, $viewer);
 
       if ($timestamp) {
         $context_logs = $query
           ->withMaximumEpoch($timestamp)
           ->execute();
 
         $context_log = last($context_logs);
 
         $target_log = head($context_logs);
         if ($target_log) {
           $map = array(
             $target_log->getID() => true,
           );
         }
       }
     }
 
     if ($context_log) {
       $after = null;
       $before = $context_log->getID() - 1;
     } else {
       $after = $request->getInt('after');
       $before = $request->getInt('before');
     }
 
     return array($after, $before, $map);
   }
 
 }
diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php
index 89bddad54..017d96ae8 100644
--- a/src/applications/conduit/call/ConduitCall.php
+++ b/src/applications/conduit/call/ConduitCall.php
@@ -1,159 +1,155 @@
 <?php
 
 /**
  * Run a conduit method in-process, without requiring HTTP requests. Usage:
  *
  *   $call = new ConduitCall('method.name', array('param' => 'value'));
  *   $call->setUser($user);
  *   $result = $call->execute();
  *
  */
 final class ConduitCall extends Phobject {
 
   private $method;
   private $handler;
   private $request;
   private $user;
 
   public function __construct($method, array $params) {
     $this->method = $method;
     $this->handler = $this->buildMethodHandler($method);
 
     $param_types = $this->handler->getParamTypes();
 
     foreach ($param_types as $key => $spec) {
       if (ConduitAPIMethod::getParameterMetadataKey($key) !== null) {
         throw new ConduitException(
           pht(
             'API Method "%s" defines a disallowed parameter, "%s". This '.
             'parameter name is reserved.',
             $method,
             $key));
       }
     }
 
     $invalid_params = array_diff_key($params, $param_types);
     if ($invalid_params) {
       throw new ConduitException(
         pht(
           'API Method "%s" does not define these parameters: %s.',
           $method,
           "'".implode("', '", array_keys($invalid_params))."'"));
     }
 
     $this->request = new ConduitAPIRequest($params);
   }
 
   public function getAPIRequest() {
     return $this->request;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   public function getUser() {
     return $this->user;
   }
 
   public function shouldRequireAuthentication() {
     return $this->handler->shouldRequireAuthentication();
   }
 
   public function shouldAllowUnguardedWrites() {
     return $this->handler->shouldAllowUnguardedWrites();
   }
 
-  public function getRequiredScope() {
-    return $this->handler->getRequiredScope();
-  }
-
   public function getErrorDescription($code) {
     return $this->handler->getErrorDescription($code);
   }
 
   public function execute() {
     $profiler = PhutilServiceProfiler::getInstance();
     $call_id = $profiler->beginServiceCall(
       array(
         'type' => 'conduit',
         'method' => $this->method,
       ));
 
     try {
       $result = $this->executeMethod();
     } catch (Exception $ex) {
       $profiler->endServiceCall($call_id, array());
       throw $ex;
     }
 
     $profiler->endServiceCall($call_id, array());
     return $result;
   }
 
   private function executeMethod() {
     $user = $this->getUser();
     if (!$user) {
       $user = new PhabricatorUser();
     }
 
     $this->request->setUser($user);
 
     if (!$this->shouldRequireAuthentication()) {
       // No auth requirement here.
     } else {
 
       $allow_public = $this->handler->shouldAllowPublic() &&
                       PhabricatorEnv::getEnvConfig('policy.allow-public');
       if (!$allow_public) {
         if (!$user->isLoggedIn() && !$user->isOmnipotent()) {
           // TODO: As per below, this should get centralized and cleaned up.
           throw new ConduitException('ERR-INVALID-AUTH');
         }
       }
 
       // TODO: This would be slightly cleaner by just using a Query, but the
       // Conduit auth workflow requires the Call and User be built separately.
       // Just do it this way for the moment.
       $application = $this->handler->getApplication();
       if ($application) {
         $can_view = PhabricatorPolicyFilter::hasCapability(
           $user,
           $application,
           PhabricatorPolicyCapability::CAN_VIEW);
 
         if (!$can_view) {
           throw new ConduitException(
             pht(
               'You do not have access to the application which provides this '.
               'API method.'));
         }
       }
     }
 
     return $this->handler->executeMethod($this->request);
   }
 
   protected function buildMethodHandler($method_name) {
     $method = ConduitAPIMethod::getConduitMethod($method_name);
 
     if (!$method) {
       throw new ConduitMethodDoesNotExistException($method_name);
     }
 
     $application = $method->getApplication();
     if ($application && !$application->isInstalled()) {
       $app_name = $application->getName();
       throw new ConduitApplicationNotInstalledException($method, $app_name);
     }
 
     return $method;
   }
 
   public function getMethodImplementation() {
     return $this->handler;
   }
 
 
 }
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index ce215468d..005b23d50 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,645 +1,685 @@
 <?php
 
 final class PhabricatorConduitAPIController
   extends PhabricatorConduitController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $method = $request->getURIData('method');
     $time_start = microtime(true);
 
     $api_request = null;
     $method_implementation = null;
 
     $log = new PhabricatorConduitMethodCallLog();
     $log->setMethod($method);
     $metadata = array();
 
     $multimeter = MultimeterControl::getInstance();
     if ($multimeter) {
       $multimeter->setEventContext('api.'.$method);
     }
 
     try {
 
       list($metadata, $params) = $this->decodeConduitParams($request, $method);
 
       $call = new ConduitCall($method, $params);
       $method_implementation = $call->getMethodImplementation();
 
       $result = null;
 
       // TODO: The relationship between ConduitAPIRequest and ConduitCall is a
       // little odd here and could probably be improved. Specifically, the
       // APIRequest is a sub-object of the Call, which does not parallel the
       // role of AphrontRequest (which is an indepenent object).
       // In particular, the setUser() and getUser() existing independently on
       // the Call and APIRequest is very awkward.
 
       $api_request = $call->getAPIRequest();
 
       $allow_unguarded_writes = false;
       $auth_error = null;
       $conduit_username = '-';
       if ($call->shouldRequireAuthentication()) {
-        $metadata['scope'] = $call->getRequiredScope();
         $auth_error = $this->authenticateUser($api_request, $metadata, $method);
         // If we've explicitly authenticated the user here and either done
         // CSRF validation or are using a non-web authentication mechanism.
         $allow_unguarded_writes = true;
 
         if ($auth_error === null) {
           $conduit_user = $api_request->getUser();
           if ($conduit_user && $conduit_user->getPHID()) {
             $conduit_username = $conduit_user->getUsername();
           }
           $call->setUser($api_request->getUser());
         }
       }
 
       $access_log = PhabricatorAccessLog::getLog();
       if ($access_log) {
         $access_log->setData(
           array(
             'u' => $conduit_username,
             'm' => $method,
           ));
       }
 
       if ($call->shouldAllowUnguardedWrites()) {
         $allow_unguarded_writes = true;
       }
 
       if ($auth_error === null) {
         if ($allow_unguarded_writes) {
           $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         }
 
         try {
           $result = $call->execute();
           $error_code = null;
           $error_info = null;
         } catch (ConduitException $ex) {
           $result = null;
           $error_code = $ex->getMessage();
           if ($ex->getErrorDescription()) {
             $error_info = $ex->getErrorDescription();
           } else {
             $error_info = $call->getErrorDescription($error_code);
           }
         }
         if ($allow_unguarded_writes) {
           unset($unguarded);
         }
       } else {
         list($error_code, $error_info) = $auth_error;
       }
     } catch (Exception $ex) {
       if (!($ex instanceof ConduitMethodNotFoundException)) {
         phlog($ex);
       }
       $result = null;
       $error_code = ($ex instanceof ConduitException
         ? 'ERR-CONDUIT-CALL'
         : 'ERR-CONDUIT-CORE');
       $error_info = $ex->getMessage();
     }
 
     $time_end = microtime(true);
 
     $log
       ->setCallerPHID(
         isset($conduit_user)
           ? $conduit_user->getPHID()
           : null)
       ->setError((string)$error_code)
       ->setDuration(1000000 * ($time_end - $time_start));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $log->save();
     unset($unguarded);
 
     $response = id(new ConduitAPIResponse())
       ->setResult($result)
       ->setErrorCode($error_code)
       ->setErrorInfo($error_info);
 
     switch ($request->getStr('output')) {
       case 'human':
         return $this->buildHumanReadableResponse(
           $method,
           $api_request,
           $response->toDictionary(),
           $method_implementation);
       case 'json':
       default:
         return id(new AphrontJSONResponse())
           ->setAddJSONShield(false)
           ->setContent($response->toDictionary());
     }
   }
 
   /**
    * Authenticate the client making the request to a Phabricator user account.
    *
    * @param   ConduitAPIRequest Request being executed.
    * @param   dict              Request metadata.
    * @return  null|pair         Null to indicate successful authentication, or
    *                            an error code and error message pair.
    */
   private function authenticateUser(
     ConduitAPIRequest $api_request,
     array $metadata,
     $method) {
 
     $request = $this->getRequest();
 
     if ($request->getUser()->getPHID()) {
       $request->validateCSRF();
       return $this->validateAuthenticatedUser(
         $api_request,
         $request->getUser());
     }
 
     $auth_type = idx($metadata, 'auth.type');
     if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) {
       $host = idx($metadata, 'auth.host');
       if (!$host) {
         return array(
           'ERR-INVALID-AUTH',
           pht(
             'Request is missing required "%s" parameter.',
             'auth.host'),
         );
       }
 
       // TODO: Validate that we are the host!
 
       $raw_key = idx($metadata, 'auth.key');
       $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key);
       $ssl_public_key = $public_key->toPKCS8();
 
       // First, verify the signature.
       try {
         $protocol_data = $metadata;
-
-        // TODO: We should stop writing this into the protocol data when
-        // processing a request.
-        unset($protocol_data['scope']);
-
         ConduitClient::verifySignature(
           $method,
           $api_request->getAllParameters(),
           $protocol_data,
           $ssl_public_key);
       } catch (Exception $ex) {
         return array(
           'ERR-INVALID-AUTH',
           pht(
             'Signature verification failure. %s',
             $ex->getMessage()),
         );
       }
 
       // If the signature is valid, find the user or device which is
       // associated with this public key.
 
       $stored_key = id(new PhabricatorAuthSSHKeyQuery())
         ->setViewer(PhabricatorUser::getOmnipotentUser())
         ->withKeys(array($public_key))
         ->executeOne();
       if (!$stored_key) {
         return array(
           'ERR-INVALID-AUTH',
           pht('No user or device is associated with that public key.'),
         );
       }
 
       $object = $stored_key->getObject();
 
       if ($object instanceof PhabricatorUser) {
         $user = $object;
       } else {
         if (!$stored_key->getIsTrusted()) {
           return array(
             'ERR-INVALID-AUTH',
             pht(
               'The key which signed this request is not trusted. Only '.
               'trusted keys can be used to sign API calls.'),
           );
         }
 
         if (!PhabricatorEnv::isClusterRemoteAddress()) {
           return array(
             'ERR-INVALID-AUTH',
             pht(
               'This request originates from outside of the Phabricator '.
               'cluster address range. Requests signed with trusted '.
               'device keys must originate from within the cluster.'),
           );
         }
 
         $user = PhabricatorUser::getOmnipotentUser();
 
         // Flag this as an intracluster request.
         $api_request->setIsClusterRequest(true);
       }
 
       return $this->validateAuthenticatedUser(
         $api_request,
         $user);
     } else if ($auth_type === null) {
       // No specified authentication type, continue with other authentication
       // methods below.
     } else {
       return array(
         'ERR-INVALID-AUTH',
         pht(
           'Provided "%s" ("%s") is not recognized.',
           'auth.type',
           $auth_type),
       );
     }
 
     $token_string = idx($metadata, 'token');
     if (strlen($token_string)) {
 
       if (strlen($token_string) != 32) {
         return array(
           'ERR-INVALID-AUTH',
           pht(
             'API token "%s" has the wrong length. API tokens should be '.
             '32 characters long.',
             $token_string),
         );
       }
 
       $type = head(explode('-', $token_string));
       $valid_types = PhabricatorConduitToken::getAllTokenTypes();
       $valid_types = array_fuse($valid_types);
       if (empty($valid_types[$type])) {
         return array(
           'ERR-INVALID-AUTH',
           pht(
             'API token "%s" has the wrong format. API tokens should be '.
             '32 characters long and begin with one of these prefixes: %s.',
             $token_string,
             implode(', ', $valid_types)),
           );
       }
 
       $token = id(new PhabricatorConduitTokenQuery())
         ->setViewer(PhabricatorUser::getOmnipotentUser())
         ->withTokens(array($token_string))
         ->withExpired(false)
         ->executeOne();
       if (!$token) {
         $token = id(new PhabricatorConduitTokenQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withTokens(array($token_string))
           ->withExpired(true)
           ->executeOne();
         if ($token) {
           return array(
             'ERR-INVALID-AUTH',
             pht(
               'API token "%s" was previously valid, but has expired.',
               $token_string),
           );
         } else {
           return array(
             'ERR-INVALID-AUTH',
             pht(
               'API token "%s" is not valid.',
               $token_string),
           );
         }
       }
 
       // If this is a "cli-" token, it expires shortly after it is generated
       // by default. Once it is actually used, we extend its lifetime and make
       // it permanent. This allows stray tokens to get cleaned up automatically
       // if they aren't being used.
       if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) {
         if ($token->getExpires()) {
           $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $token->setExpires(null);
             $token->save();
           unset($unguarded);
         }
       }
 
       // If this is a "clr-" token, Phabricator must be configured in cluster
       // mode and the remote address must be a cluster node.
       if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) {
         if (!PhabricatorEnv::isClusterRemoteAddress()) {
           return array(
             'ERR-INVALID-AUTH',
             pht(
               'This request originates from outside of the Phabricator '.
               'cluster address range. Requests signed with cluster API '.
               'tokens must originate from within the cluster.'),
           );
         }
 
         // Flag this as an intracluster request.
         $api_request->setIsClusterRequest(true);
       }
 
       $user = $token->getObject();
       if (!($user instanceof PhabricatorUser)) {
         return array(
           'ERR-INVALID-AUTH',
           pht('API token is not associated with a valid user.'),
         );
       }
 
       return $this->validateAuthenticatedUser(
         $api_request,
         $user);
     }
 
-    // handle oauth
     $access_token = idx($metadata, 'access_token');
-    $method_scope = idx($metadata, 'scope');
-    if ($access_token &&
-        $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) {
+    if ($access_token) {
       $token = id(new PhabricatorOAuthServerAccessToken())
         ->loadOneWhere('token = %s', $access_token);
       if (!$token) {
         return array(
           'ERR-INVALID-AUTH',
           pht('Access token does not exist.'),
         );
       }
 
       $oauth_server = new PhabricatorOAuthServer();
-      $valid = $oauth_server->validateAccessToken($token,
-                                                  $method_scope);
-      if (!$valid) {
+      $authorization = $oauth_server->authorizeToken($token);
+      if (!$authorization) {
         return array(
           'ERR-INVALID-AUTH',
-          pht('Access token is invalid.'),
+          pht('Access token is invalid or expired.'),
         );
       }
 
-      // valid token, so let's log in the user!
-      $user_phid = $token->getUserPHID();
-      $user = id(new PhabricatorUser())
-        ->loadOneWhere('phid = %s', $user_phid);
+      $user = id(new PhabricatorPeopleQuery())
+        ->setViewer(PhabricatorUser::getOmnipotentUser())
+        ->withPHIDs(array($token->getUserPHID()))
+        ->executeOne();
       if (!$user) {
         return array(
           'ERR-INVALID-AUTH',
           pht('Access token is for invalid user.'),
         );
       }
+
+      $ok = $this->authorizeOAuthMethodAccess($authorization, $method);
+      if (!$ok) {
+        return array(
+          'ERR-OAUTH-ACCESS',
+          pht('You do not have authorization to call this method.'),
+        );
+      }
+
+      $api_request->setOAuthToken($token);
+
       return $this->validateAuthenticatedUser(
         $api_request,
         $user);
     }
 
     // Handle sessionless auth.
     // TODO: This is super messy.
     // TODO: Remove this in favor of token-based auth.
 
     if (isset($metadata['authUser'])) {
       $user = id(new PhabricatorUser())->loadOneWhere(
         'userName = %s',
         $metadata['authUser']);
       if (!$user) {
         return array(
           'ERR-INVALID-AUTH',
           pht('Authentication is invalid.'),
         );
       }
       $token = idx($metadata, 'authToken');
       $signature = idx($metadata, 'authSignature');
       $certificate = $user->getConduitCertificate();
       $hash = sha1($token.$certificate);
       if (!phutil_hashes_are_identical($hash, $signature)) {
         return array(
           'ERR-INVALID-AUTH',
           pht('Authentication is invalid.'),
         );
       }
       return $this->validateAuthenticatedUser(
         $api_request,
         $user);
     }
 
     // Handle session-based auth.
     // TODO: Remove this in favor of token-based auth.
 
     $session_key = idx($metadata, 'sessionKey');
     if (!$session_key) {
       return array(
         'ERR-INVALID-SESSION',
         pht('Session key is not present.'),
       );
     }
 
     $user = id(new PhabricatorAuthSessionEngine())
       ->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key);
 
     if (!$user) {
       return array(
         'ERR-INVALID-SESSION',
         pht('Session key is invalid.'),
       );
     }
 
     return $this->validateAuthenticatedUser(
       $api_request,
       $user);
   }
 
   private function validateAuthenticatedUser(
     ConduitAPIRequest $request,
     PhabricatorUser $user) {
 
     if (!$user->canEstablishAPISessions()) {
       return array(
         'ERR-INVALID-AUTH',
         pht('User account is not permitted to use the API.'),
       );
     }
 
     $request->setUser($user);
     return null;
   }
 
   private function buildHumanReadableResponse(
     $method,
     ConduitAPIRequest $request = null,
     $result = null,
     ConduitAPIMethod $method_implementation = null) {
 
     $param_rows = array();
     $param_rows[] = array('Method', $this->renderAPIValue($method));
     if ($request) {
       foreach ($request->getAllParameters() as $key => $value) {
         $param_rows[] = array(
           $key,
           $this->renderAPIValue($value),
         );
       }
     }
 
     $param_table = new AphrontTableView($param_rows);
     $param_table->setColumnClasses(
       array(
         'header',
         'wide',
       ));
 
     $result_rows = array();
     foreach ($result as $key => $value) {
       $result_rows[] = array(
         $key,
         $this->renderAPIValue($value),
       );
     }
 
     $result_table = new AphrontTableView($result_rows);
     $result_table->setColumnClasses(
       array(
         'header',
         'wide',
       ));
 
-    $param_panel = new PHUIObjectBoxView();
-    $param_panel->setHeaderText(pht('Method Parameters'));
-    $param_panel->setTable($param_table);
+    $param_panel = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Method Parameters'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->setTable($param_table);
 
-    $result_panel = new PHUIObjectBoxView();
-    $result_panel->setHeaderText(pht('Method Result'));
-    $result_panel->setTable($result_table);
+    $result_panel = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Method Result'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->setTable($result_table);
 
     $method_uri = $this->getApplicationURI('method/'.$method.'/');
 
     $crumbs = $this->buildApplicationCrumbs()
       ->addTextCrumb($method, $method_uri)
-      ->addTextCrumb(pht('Call'));
+      ->addTextCrumb(pht('Call'))
+      ->setBorder(true);
 
     $example_panel = null;
     if ($request && $method_implementation) {
       $params = $request->getAllParameters();
       $example_panel = $this->renderExampleBox(
         $method_implementation,
         $params);
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $title = pht('Method Call Result');
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-exchange');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $param_panel,
         $result_panel,
         $example_panel,
-      ),
-      array(
-        'title' => pht('Method Call Result'),
       ));
+
+    $title = pht('Method Call Result');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function renderAPIValue($value) {
     $json = new PhutilJSON();
     if (is_array($value)) {
       $value = $json->encodeFormatted($value);
     }
 
     $value = phutil_tag(
       'pre',
       array('style' => 'white-space: pre-wrap;'),
       $value);
 
     return $value;
   }
 
   private function decodeConduitParams(
     AphrontRequest $request,
     $method) {
 
     // Look for parameters from the Conduit API Console, which are encoded
     // as HTTP POST parameters in an array, e.g.:
     //
     //   params[name]=value&params[name2]=value2
     //
     // The fields are individually JSON encoded, since we require users to
     // enter JSON so that we avoid type ambiguity.
 
     $params = $request->getArr('params', null);
     if ($params !== null) {
       foreach ($params as $key => $value) {
         if ($value == '') {
           // Interpret empty string null (e.g., the user didn't type anything
           // into the box).
           $value = 'null';
         }
         $decoded_value = json_decode($value, true);
         if ($decoded_value === null && strtolower($value) != 'null') {
           // When json_decode() fails, it returns null. This almost certainly
           // indicates that a user was using the web UI and didn't put quotes
           // around a string value. We can either do what we think they meant
           // (treat it as a string) or fail. For now, err on the side of
           // caution and fail. In the future, if we make the Conduit API
           // actually do type checking, it might be reasonable to treat it as
           // a string if the parameter type is string.
           throw new Exception(
             pht(
               "The value for parameter '%s' is not valid JSON. All ".
               "parameters must be encoded as JSON values, including strings ".
               "(which means you need to surround them in double quotes). ".
               "Check your syntax. Value was: %s.",
               $key,
               $value));
         }
         $params[$key] = $decoded_value;
       }
 
       $metadata = idx($params, '__conduit__', array());
       unset($params['__conduit__']);
 
       return array($metadata, $params);
     }
 
     // Otherwise, look for a single parameter called 'params' which has the
     // entire param dictionary JSON encoded.
     $params_json = $request->getStr('params');
     if (strlen($params_json)) {
       $params = null;
       try {
         $params = phutil_json_decode($params_json);
       } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(
           pht(
             "Invalid parameter information was passed to method '%s'.",
             $method),
           $ex);
       }
 
       $metadata = idx($params, '__conduit__', array());
       unset($params['__conduit__']);
 
       return array($metadata, $params);
     }
 
     // If we do not have `params`, assume this is a simple HTTP request with
     // HTTP key-value pairs.
     $params = array();
     $metadata = array();
     foreach ($request->getPassthroughRequestData() as $key => $value) {
       $meta_key = ConduitAPIMethod::getParameterMetadataKey($key);
       if ($meta_key !== null) {
         $metadata[$meta_key] = $value;
       } else {
         $params[$key] = $value;
       }
     }
 
     return array($metadata, $params);
   }
 
+  private function authorizeOAuthMethodAccess(
+    PhabricatorOAuthClientAuthorization $authorization,
+    $method_name) {
+
+    $method = ConduitAPIMethod::getConduitMethod($method_name);
+    if (!$method) {
+      return false;
+    }
+
+    $required_scope = $method->getRequiredScope();
+    switch ($required_scope) {
+      case ConduitAPIMethod::SCOPE_ALWAYS:
+        return true;
+      case ConduitAPIMethod::SCOPE_NEVER:
+        return false;
+    }
+
+    $authorization_scope = $authorization->getScope();
+    if (!empty($authorization_scope[$required_scope])) {
+      return true;
+    }
+
+    return false;
+  }
+
+
 }
diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
index a0481126c..5ee76b44f 100644
--- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
@@ -1,154 +1,188 @@
 <?php
 
 final class PhabricatorConduitConsoleController
   extends PhabricatorConduitController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $method_name = $request->getURIData('method');
 
     $method = id(new PhabricatorConduitMethodQuery())
       ->setViewer($viewer)
       ->withMethods(array($method_name))
       ->executeOne();
     if (!$method) {
       return new Aphront404Response();
     }
 
     $method->setViewer($viewer);
 
     $call_uri = '/api/'.$method->getAPIMethodName();
 
     $status = $method->getMethodStatus();
     $reason = $method->getMethodStatusDescription();
     $errors = array();
 
     switch ($status) {
       case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
         $reason = nonempty($reason, pht('This method is deprecated.'));
         $errors[] = pht('Deprecated Method: %s', $reason);
         break;
       case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
         $reason = nonempty(
           $reason,
           pht(
             'This method is new and unstable. Its interface is subject '.
             'to change.'));
         $errors[] = pht('Unstable Method: %s', $reason);
         break;
     }
 
     $form = id(new AphrontFormView())
       ->setAction($call_uri)
       ->setUser($request->getUser())
       ->appendRemarkupInstructions(
         pht(
           'Enter parameters using **JSON**. For instance, to enter a '.
           'list, type: `%s`',
           '["apple", "banana", "cherry"]'));
 
     $params = $method->getParamTypes();
     foreach ($params as $param => $desc) {
       $form->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel($param)
           ->setName("params[{$param}]")
           ->setCaption($desc));
     }
 
     $must_login = !$viewer->isLoggedIn() &&
                   $method->shouldRequireAuthentication();
     if ($must_login) {
       $errors[] = pht(
         'Login Required: This method requires authentication. You must '.
         'log in before you can make calls to it.');
     } else {
       $form
         ->appendChild(
           id(new AphrontFormSelectControl())
             ->setLabel(pht('Output Format'))
             ->setName('output')
             ->setOptions(
               array(
                 'human' => pht('Human Readable'),
                 'json'  => pht('JSON'),
               )))
         ->appendChild(
           id(new AphrontFormSubmitControl())
             ->addCancelButton($this->getApplicationURI())
             ->setValue(pht('Call Method')));
     }
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
-      ->setHeader($method->getAPIMethodName());
+      ->setHeader($method->getAPIMethodName())
+      ->setHeaderIcon('fa-tty');
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Call Method'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    $content = array();
-
     $properties = $this->buildMethodProperties($method);
 
     $info_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName()))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
 
-    $content[] = $info_box;
-    $content[] = $method->getMethodDocumentation();
-    $content[] = $form_box;
-    $content[] = $this->renderExampleBox($method, null);
-
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($method->getAPIMethodName());
-
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $content,
-      ),
-      array(
-        'title' => $method->getAPIMethodName(),
+    $crumbs->setBorder(true);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $info_box,
+        $method->getMethodDocumentation(),
+        $form_box,
+        $this->renderExampleBox($method, null),
       ));
+
+    $title = $method->getAPIMethodName();
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildMethodProperties(ConduitAPIMethod $method) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView());
 
     $view->addProperty(
       pht('Returns'),
       $method->getReturnType());
 
     $error_types = $method->getErrorTypes();
     $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.');
     $error_description = array();
     foreach ($error_types as $error => $meaning) {
       $error_description[] = hsprintf(
         '<li><strong>%s:</strong> %s</li>',
         $error,
         $meaning);
     }
     $error_description = phutil_tag('ul', array(), $error_description);
 
     $view->addProperty(
       pht('Errors'),
       $error_description);
 
+
+    $scope = $method->getRequiredScope();
+    switch ($scope) {
+      case ConduitAPIMethod::SCOPE_ALWAYS:
+        $oauth_icon = 'fa-globe green';
+        $oauth_description = pht(
+          'OAuth clients may always call this method.');
+        break;
+      case ConduitAPIMethod::SCOPE_NEVER:
+        $oauth_icon = 'fa-ban red';
+        $oauth_description = pht(
+          'OAuth clients may never call this method.');
+        break;
+      default:
+        $oauth_icon = 'fa-unlock-alt blue';
+        $oauth_description = pht(
+          'OAuth clients may call this method after requesting access to '.
+          'the "%s" scope.',
+          $scope);
+        break;
+    }
+
+    $view->addProperty(
+      pht('OAuth Scope'),
+      array(
+        id(new PHUIIconView())->setIcon($oauth_icon),
+        ' ',
+        $oauth_description,
+      ));
+
     $view->addSectionHeader(
       pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
     $view->addTextContent(
       new PHUIRemarkupView($viewer, $method->getMethodDescription()));
 
     return $view;
   }
 
 
 }
diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php
index 4fa11dbfa..000d01f88 100644
--- a/src/applications/conduit/controller/PhabricatorConduitController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitController.php
@@ -1,273 +1,274 @@
 <?php
 
 abstract class PhabricatorConduitController extends PhabricatorController {
 
   protected function buildSideNavView() {
     $viewer = $this->getRequest()->getUser();
 
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
 
     id(new PhabricatorConduitSearchEngine())
       ->setViewer($viewer)
       ->addNavigationItems($nav->getMenu());
 
     $nav->addLabel('Logs');
     $nav->addFilter('log', pht('Call Logs'));
 
     $nav->selectFilter(null);
 
     return $nav;
   }
 
   public function buildApplicationMenu() {
     return $this->buildSideNavView()->getMenu();
   }
 
   protected function renderExampleBox(ConduitAPIMethod $method, $params) {
     $arc_example = id(new PHUIPropertyListView())
       ->addRawContent($this->renderExample($method, 'arc', $params));
 
     $curl_example = id(new PHUIPropertyListView())
       ->addRawContent($this->renderExample($method, 'curl', $params));
 
     $php_example = id(new PHUIPropertyListView())
       ->addRawContent($this->renderExample($method, 'php', $params));
 
     $panel_link = phutil_tag(
       'a',
       array(
         'href' => '/settings/panel/apitokens/',
       ),
       pht('Conduit API Tokens'));
 
     $panel_link = phutil_tag('strong', array(), $panel_link);
 
     $messages = array(
       pht(
         'Use the %s panel in Settings to generate or manage API tokens.',
         $panel_link),
     );
 
     $info_view = id(new PHUIInfoView())
       ->setErrors($messages)
       ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Examples'))
       ->setInfoView($info_view)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($arc_example, pht('arc call-conduit'))
       ->addPropertyList($curl_example, pht('cURL'))
       ->addPropertyList($php_example, pht('PHP'));
   }
 
   private function renderExample(
     ConduitAPIMethod $method,
     $kind,
     $params) {
 
     switch ($kind) {
       case 'arc':
         $example = $this->buildArcanistExample($method, $params);
         break;
       case 'php':
         $example = $this->buildPHPExample($method, $params);
         break;
       case 'curl':
         $example = $this->buildCURLExample($method, $params);
         break;
       default:
         throw new Exception(pht('Conduit client "%s" is not known.', $kind));
     }
 
     return $example;
   }
 
   private function buildArcanistExample(
     ConduitAPIMethod $method,
     $params) {
 
     $parts = array();
 
     $parts[] = '$ echo ';
     if ($params === null) {
       $parts[] = phutil_tag('strong', array(), '<json-parameters>');
     } else {
       $params = $this->simplifyParams($params);
       $params = id(new PhutilJSON())->encodeFormatted($params);
       $params = trim($params);
       $params = csprintf('%s', $params);
       $parts[] = phutil_tag('strong', array('class' => 'real'), $params);
     }
 
     $parts[] = ' | ';
     $parts[] = 'arc call-conduit ';
 
     $parts[] = '--conduit-uri ';
     $parts[] = phutil_tag(
       'strong',
       array('class' => 'real'),
       PhabricatorEnv::getURI('/'));
     $parts[] = ' ';
 
     $parts[] = '--conduit-token ';
     $parts[] = phutil_tag('strong', array(), '<conduit-token>');
     $parts[] = ' ';
 
     $parts[] = $method->getAPIMethodName();
 
     return $this->renderExampleCode($parts);
   }
 
   private function buildPHPExample(
     ConduitAPIMethod $method,
     $params) {
 
     $parts = array();
 
     $libphutil_path = 'path/to/libphutil/src/__phutil_library_init__.php';
 
     $parts[] = '<?php';
     $parts[] = "\n\n";
 
     $parts[] = 'require_once ';
     $parts[] = phutil_var_export($libphutil_path, true);
     $parts[] = ";\n\n";
 
     $parts[] = '$api_token = "';
     $parts[] = phutil_tag('strong', array(), pht('<api-token>'));
     $parts[] = "\";\n";
 
     $parts[] = '$api_parameters = ';
     if ($params === null) {
       $parts[] = 'array(';
       $parts[] = phutil_tag('strong', array(), pht('<parameters>'));
       $parts[] = ');';
     } else {
       $params = $this->simplifyParams($params);
       $params = phutil_var_export($params, true);
       $parts[] = phutil_tag('strong', array('class' => 'real'), $params);
       $parts[] = ';';
     }
     $parts[] = "\n\n";
 
     $parts[] = '$client = new ConduitClient(';
     $parts[] = phutil_tag(
       'strong',
       array('class' => 'real'),
       phutil_var_export(PhabricatorEnv::getURI('/'), true));
     $parts[] = ");\n";
 
     $parts[] = '$client->setConduitToken($api_token);';
     $parts[] = "\n\n";
 
     $parts[] = '$result = $client->callMethodSynchronous(';
     $parts[] = phutil_tag(
       'strong',
       array('class' => 'real'),
       phutil_var_export($method->getAPIMethodName(), true));
     $parts[] = ', ';
     $parts[] = '$api_parameters';
     $parts[] = ");\n";
 
     $parts[] = 'print_r($result);';
 
     return $this->renderExampleCode($parts);
   }
 
   private function buildCURLExample(
     ConduitAPIMethod $method,
     $params) {
 
     $call_uri = '/api/'.$method->getAPIMethodName();
 
     $parts = array();
 
     $linebreak = array('\\', phutil_tag('br'), '    ');
 
     $parts[] = '$ curl ';
     $parts[] = phutil_tag(
       'strong',
       array('class' => 'real'),
       csprintf('%R', PhabricatorEnv::getURI($call_uri)));
     $parts[] = ' ';
     $parts[] = $linebreak;
 
     $parts[] = '-d api.token=';
     $parts[] = phutil_tag('strong', array(), 'api-token');
     $parts[] = ' ';
     $parts[] = $linebreak;
 
     if ($params === null) {
       $parts[] = '-d ';
       $parts[] = phutil_tag('strong', array(), 'param');
       $parts[] = '=';
       $parts[] = phutil_tag('strong', array(), 'value');
       $parts[] = ' ';
       $parts[] = $linebreak;
       $parts[] = phutil_tag('strong', array(), '...');
     } else {
       $lines = array();
       $params = $this->simplifyParams($params);
 
       foreach ($params as $key => $value) {
         $pieces = $this->getQueryStringParts(null, $key, $value);
         foreach ($pieces as $piece) {
           $lines[] = array(
             '-d ',
             phutil_tag('strong', array('class' => 'real'), $piece),
           );
         }
       }
 
       $parts[] = phutil_implode_html(array(' ', $linebreak), $lines);
     }
 
     return $this->renderExampleCode($parts);
   }
 
   private function renderExampleCode($example) {
     require_celerity_resource('conduit-api-css');
 
     return phutil_tag(
       'div',
       array(
         'class' => 'PhabricatorMonospaced conduit-api-example-code',
       ),
       $example);
   }
 
   private function simplifyParams(array $params) {
     foreach ($params as $key => $value) {
       if ($value === null) {
         unset($params[$key]);
       }
     }
     return $params;
   }
 
   private function getQueryStringParts($prefix, $key, $value) {
     if ($prefix === null) {
       $head = phutil_escape_uri($key);
     } else {
       $head = $prefix.'['.phutil_escape_uri($key).']';
     }
 
     if (!is_array($value)) {
       return array(
         $head.'='.phutil_escape_uri($value),
       );
     }
 
     $results = array();
     foreach ($value as $subkey => $subvalue) {
       $subparts = $this->getQueryStringParts($head, $subkey, $subvalue);
       foreach ($subparts as $subpart) {
         $results[] = $subpart;
       }
     }
 
     return $results;
   }
 
 }
diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenController.php b/src/applications/conduit/controller/PhabricatorConduitTokenController.php
index b5501a280..fe6d676b6 100644
--- a/src/applications/conduit/controller/PhabricatorConduitTokenController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitTokenController.php
@@ -1,73 +1,80 @@
 <?php
 
 final class PhabricatorConduitTokenController
   extends PhabricatorConduitController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
       $viewer,
       $this->getRequest(),
       '/');
 
     // Ideally we'd like to verify this, but it's fine to leave it unguarded
     // for now and verifying it would need some Ajax junk or for the user to
     // click a button or similar.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 
     $old_token = id(new PhabricatorConduitCertificateToken())
       ->loadOneWhere(
         'userPHID = %s',
         $viewer->getPHID());
     if ($old_token) {
       $old_token->delete();
     }
 
     $token = id(new PhabricatorConduitCertificateToken())
       ->setUserPHID($viewer->getPHID())
       ->setToken(Filesystem::readRandomCharacters(40))
       ->save();
 
     unset($unguarded);
 
     $pre_instructions = pht(
       'Copy and paste this token into the prompt given to you by '.
       '`arc install-certificate`');
 
     $post_instructions = pht(
       'After you copy and paste this token, `arc` will complete '.
       'the certificate install process for you.');
 
     Javelin::initBehavior('select-on-click');
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendRemarkupInstructions($pre_instructions)
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Token'))
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
           ->setReadonly(true)
           ->setSigil('select-on-click')
           ->setValue($token->getToken()))
       ->appendRemarkupInstructions($post_instructions);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Install Certificate'));
+    $crumbs->setBorder(true);
 
     $object_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Certificate Token'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $object_box,
-      ),
-      array(
-        'title' => pht('Certificate Install Token'),
-      ));
+    $title = pht('Certificate Install Token');
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($object_box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php
index 4fd7c416b..5b6c16bb9 100644
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -1,406 +1,407 @@
 <?php
 
 /**
  * @task info Method Information
  * @task status Method Status
  * @task pager Paging Results
  */
 abstract class ConduitAPIMethod
   extends Phobject
   implements PhabricatorPolicyInterface {
 
   private $viewer;
 
   const METHOD_STATUS_STABLE      = 'stable';
   const METHOD_STATUS_UNSTABLE    = 'unstable';
   const METHOD_STATUS_DEPRECATED  = 'deprecated';
 
+  const SCOPE_NEVER = 'scope.never';
+  const SCOPE_ALWAYS = 'scope.always';
 
   /**
    * Get a short, human-readable text summary of the method.
    *
    * @return string Short summary of method.
    * @task info
    */
   public function getMethodSummary() {
     return $this->getMethodDescription();
   }
 
 
   /**
    * Get a detailed description of the method.
    *
    * This method should return remarkup.
    *
    * @return string Detailed description of the method.
    * @task info
    */
   abstract public function getMethodDescription();
 
   public function getMethodDocumentation() {
     return null;
   }
 
   abstract protected function defineParamTypes();
   abstract protected function defineReturnType();
 
   protected function defineErrorTypes() {
     return array();
   }
 
   abstract protected function execute(ConduitAPIRequest $request);
 
 
   public function getParamTypes() {
     $types = $this->defineParamTypes();
 
     $query = $this->newQueryObject();
     if ($query) {
       $types['order'] = 'optional order';
       $types += $this->getPagerParamTypes();
     }
 
     return $types;
   }
 
   public function getReturnType() {
     return $this->defineReturnType();
   }
 
   public function getErrorTypes() {
     return $this->defineErrorTypes();
   }
 
   /**
    * This is mostly for compatibility with
    * @{class:PhabricatorCursorPagedPolicyAwareQuery}.
    */
   public function getID() {
     return $this->getAPIMethodName();
   }
 
   /**
    * Get the status for this method (e.g., stable, unstable or deprecated).
    * Should return a METHOD_STATUS_* constant. By default, methods are
    * "stable".
    *
    * @return const  METHOD_STATUS_* constant.
    * @task status
    */
   public function getMethodStatus() {
     return self::METHOD_STATUS_STABLE;
   }
 
   /**
    * Optional description to supplement the method status. In particular, if
    * a method is deprecated, you can return a string here describing the reason
    * for deprecation and stable alternatives.
    *
    * @return string|null  Description of the method status, if available.
    * @task status
    */
   public function getMethodStatusDescription() {
     return null;
   }
 
   public function getErrorDescription($error_code) {
     return idx($this->getErrorTypes(), $error_code, pht('Unknown Error'));
   }
 
   public function getRequiredScope() {
-    // by default, conduit methods are not accessible via OAuth
-    return PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE;
+    return self::SCOPE_NEVER;
   }
 
   public function executeMethod(ConduitAPIRequest $request) {
     $this->setViewer($request->getUser());
 
     return $this->execute($request);
   }
 
   abstract public function getAPIMethodName();
 
   /**
    * Return a key which sorts methods by application name, then method status,
    * then method name.
    */
   public function getSortOrder() {
     $name = $this->getAPIMethodName();
 
     $map = array(
       self::METHOD_STATUS_STABLE      => 0,
       self::METHOD_STATUS_UNSTABLE    => 1,
       self::METHOD_STATUS_DEPRECATED  => 2,
     );
     $ord = idx($map, $this->getMethodStatus(), 0);
 
     list($head, $tail) = explode('.', $name, 2);
 
     return "{$head}.{$ord}.{$tail}";
   }
 
   public static function getMethodStatusMap() {
     $map = array(
       self::METHOD_STATUS_STABLE => pht('Stable'),
       self::METHOD_STATUS_UNSTABLE => pht('Unstable'),
       self::METHOD_STATUS_DEPRECATED => pht('Deprecated'),
     );
 
     return $map;
   }
 
   public function getApplicationName() {
     return head(explode('.', $this->getAPIMethodName(), 2));
   }
 
   public static function loadAllConduitMethods() {
     return id(new PhutilClassMapQuery())
       ->setAncestorClass(__CLASS__)
       ->setUniqueMethod('getAPIMethodName')
       ->execute();
   }
 
   public static function getConduitMethod($method_name) {
     $method_map = self::loadAllConduitMethods();
     return idx($method_map, $method_name);
   }
 
   public function shouldRequireAuthentication() {
     return true;
   }
 
   public function shouldAllowPublic() {
     return false;
   }
 
   public function shouldAllowUnguardedWrites() {
     return false;
   }
 
 
   /**
    * Optionally, return a @{class:PhabricatorApplication} which this call is
    * part of. The call will be disabled when the application is uninstalled.
    *
    * @return PhabricatorApplication|null  Related application.
    */
   public function getApplication() {
     return null;
   }
 
   protected function formatStringConstants($constants) {
     foreach ($constants as $key => $value) {
       $constants[$key] = '"'.$value.'"';
     }
     $constants = implode(', ', $constants);
     return 'string-constant<'.$constants.'>';
   }
 
   public static function getParameterMetadataKey($key) {
     if (strncmp($key, 'api.', 4) === 0) {
       // All keys passed beginning with "api." are always metadata keys.
       return substr($key, 4);
     } else {
       switch ($key) {
         // These are real keys which always belong to request metadata.
         case 'access_token':
         case 'scope':
         case 'output':
 
         // This is not a real metadata key; it is included here only to
         // prevent Conduit methods from defining it.
         case '__conduit__':
 
         // This is prevented globally as a blanket defense against OAuth
         // redirection attacks. It is included here to stop Conduit methods
         // from defining it.
         case 'code':
 
         // This is not a real metadata key, but the presence of this
         // parameter triggers an alternate request decoding pathway.
         case 'params':
           return $key;
       }
     }
 
     return null;
   }
 
   final public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
   }
 
   final public function getViewer() {
     return $this->viewer;
   }
 
 /* -(  Paging Results  )----------------------------------------------------- */
 
 
   /**
    * @task pager
    */
   protected function getPagerParamTypes() {
     return array(
       'before' => 'optional string',
       'after'  => 'optional string',
       'limit'  => 'optional int (default = 100)',
     );
   }
 
 
   /**
    * @task pager
    */
   protected function newPager(ConduitAPIRequest $request) {
     $limit = $request->getValue('limit', 100);
     $limit = min(1000, $limit);
     $limit = max(1, $limit);
 
     $pager = id(new AphrontCursorPagerView())
       ->setPageSize($limit);
 
     $before_id = $request->getValue('before');
     if ($before_id !== null) {
       $pager->setBeforeID($before_id);
     }
 
     $after_id = $request->getValue('after');
     if ($after_id !== null) {
       $pager->setAfterID($after_id);
     }
 
     return $pager;
   }
 
 
   /**
    * @task pager
    */
   protected function addPagerResults(
     array $results,
     AphrontCursorPagerView $pager) {
 
     $results['cursor'] = array(
       'limit' => $pager->getPageSize(),
       'after' => $pager->getNextPageID(),
       'before' => $pager->getPrevPageID(),
     );
 
     return $results;
   }
 
 
 /* -(  Implementing Query Methods  )----------------------------------------- */
 
 
   public function newQueryObject() {
     return null;
   }
 
 
   protected function newQueryForRequest(ConduitAPIRequest $request) {
     $query = $this->newQueryObject();
 
     if (!$query) {
       throw new Exception(
         pht(
           'You can not call newQueryFromRequest() in this method ("%s") '.
           'because it does not implement newQueryObject().',
           get_class($this)));
     }
 
     if (!($query instanceof PhabricatorCursorPagedPolicyAwareQuery)) {
       throw new Exception(
         pht(
           'Call to method newQueryObject() did not return an object of class '.
           '"%s".',
           'PhabricatorCursorPagedPolicyAwareQuery'));
     }
 
     $query->setViewer($request->getUser());
 
     $order = $request->getValue('order');
     if ($order !== null) {
       if (is_scalar($order)) {
         $query->setOrder($order);
       } else {
         $query->setOrderVector($order);
       }
     }
 
     return $query;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getPHID() {
     return null;
   }
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
     );
   }
 
   public function getPolicy($capability) {
     // Application methods get application visibility; other methods get open
     // visibility.
 
     $application = $this->getApplication();
     if ($application) {
       return $application->getPolicy($capability);
     }
 
     return PhabricatorPolicies::getMostOpenPolicy();
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     if (!$this->shouldRequireAuthentication()) {
       // Make unauthenticated methods universally visible.
       return true;
     }
 
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 
   protected function hasApplicationCapability(
     $capability,
     PhabricatorUser $viewer) {
 
     $application = $this->getApplication();
 
     if (!$application) {
       return false;
     }
 
     return PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $application,
       $capability);
   }
 
   protected function requireApplicationCapability(
     $capability,
     PhabricatorUser $viewer) {
 
     $application = $this->getApplication();
     if (!$application) {
       return;
     }
 
     PhabricatorPolicyFilter::requireCapability(
       $viewer,
       $this->getApplication(),
       $capability);
   }
 
 }
diff --git a/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php b/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php
index 44acf3e0d..2cb84b1f8 100644
--- a/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php
@@ -1,56 +1,60 @@
 <?php
 
 final class ConduitGetCapabilitiesConduitAPIMethod extends ConduitAPIMethod {
 
   public function getAPIMethodName() {
     return 'conduit.getcapabilities';
   }
 
   public function shouldRequireAuthentication() {
     return false;
   }
 
   public function getMethodDescription() {
     return pht(
       'List capabilities, wire formats, and authentication protocols '.
       'available on this server.');
   }
 
   protected function defineParamTypes() {
     return array();
   }
 
   protected function defineReturnType() {
     return 'dict<string, any>';
   }
 
+  public function getRequiredScope() {
+    return self::SCOPE_ALWAYS;
+  }
+
   protected function execute(ConduitAPIRequest $request) {
     $authentication = array(
       'token',
       'asymmetric',
       'session',
       'sessionless',
     );
 
     $oauth_app = 'PhabricatorOAuthServerApplication';
     if (PhabricatorApplication::isClassInstalled($oauth_app)) {
       $authentication[] = 'oauth';
     }
 
     return array(
       'authentication' => $authentication,
       'signatures' => array(
         'consign',
       ),
       'input' => array(
         'json',
         'urlencoded',
       ),
       'output' => array(
         'json',
         'human',
       ),
     );
   }
 
 }
diff --git a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php
index 04e8b6d05..a63d004e5 100644
--- a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php
@@ -1,38 +1,42 @@
 <?php
 
 final class ConduitQueryConduitAPIMethod extends ConduitAPIMethod {
 
   public function getAPIMethodName() {
     return 'conduit.query';
   }
 
   public function getMethodDescription() {
     return pht('Returns the parameters of the Conduit methods.');
   }
 
   protected function defineParamTypes() {
     return array();
   }
 
   protected function defineReturnType() {
     return 'dict<dict>';
   }
 
+  public function getRequiredScope() {
+    return self::SCOPE_ALWAYS;
+  }
+
   protected function execute(ConduitAPIRequest $request) {
     $methods = id(new PhabricatorConduitMethodQuery())
       ->setViewer($request->getUser())
       ->execute();
 
     $map = array();
     foreach ($methods as $method) {
       $map[$method->getAPIMethodName()] = array(
         'description' => $method->getMethodDescription(),
         'params' => $method->getParamTypes(),
         'return' => $method->getReturnType(),
       );
     }
 
     return $map;
   }
 
 }
diff --git a/src/applications/conduit/parametertype/ConduitColumnsParameterType.php b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php
new file mode 100644
index 000000000..c6669fae0
--- /dev/null
+++ b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php
@@ -0,0 +1,38 @@
+<?php
+
+final class ConduitColumnsParameterType
+  extends ConduitParameterType {
+
+  protected function getParameterValue(array $request, $key) {
+    // We don't do any meaningful validation here because the transaction
+    // itself validates everything and the input format is flexible.
+    return parent::getParameterValue($request, $key);
+  }
+
+  protected function getParameterTypeName() {
+    return 'columns';
+  }
+
+  protected function getParameterDefault() {
+    return array();
+  }
+
+  protected function getParameterFormatDescriptions() {
+    return array(
+      pht('Single column PHID.'),
+      pht('List of column PHIDs.'),
+      pht('List of position dictionaries.'),
+      pht('List with a mixture of PHIDs and dictionaries.'),
+    );
+  }
+
+  protected function getParameterExamples() {
+    return array(
+      '"PHID-PCOL-1111"',
+      '["PHID-PCOL-2222", "PHID-PCOL-3333"]',
+      '[{"columnPHID": "PHID-PCOL-4444", "afterPHID": "PHID-TASK-5555"}]',
+      '[{"columnPHID": "PHID-PCOL-4444", "beforePHID": "PHID-TASK-6666"}]',
+    );
+  }
+
+}
diff --git a/src/applications/conduit/protocol/ConduitAPIRequest.php b/src/applications/conduit/protocol/ConduitAPIRequest.php
index 3e322fcb3..47cc31fba 100644
--- a/src/applications/conduit/protocol/ConduitAPIRequest.php
+++ b/src/applications/conduit/protocol/ConduitAPIRequest.php
@@ -1,65 +1,76 @@
 <?php
 
 final class ConduitAPIRequest extends Phobject {
 
   protected $params;
   private $user;
   private $isClusterRequest = false;
+  private $oauthToken;
 
   public function __construct(array $params) {
     $this->params = $params;
   }
 
   public function getValue($key, $default = null) {
     return coalesce(idx($this->params, $key), $default);
   }
 
   public function getValueExists($key) {
     return array_key_exists($key, $this->params);
   }
 
   public function getAllParameters() {
     return $this->params;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   /**
    * Retrieve the authentic identity of the user making the request. If a
    * method requires authentication (the default) the user object will always
    * be available. If a method does not require authentication (i.e., overrides
    * shouldRequireAuthentication() to return false) the user object will NEVER
    * be available.
    *
    * @return PhabricatorUser Authentic user, available ONLY if the method
    *                         requires authentication.
    */
   public function getUser() {
     if (!$this->user) {
       throw new Exception(
         pht(
           'You can not access the user inside the implementation of a Conduit '.
           'method which does not require authentication (as per %s).',
           'shouldRequireAuthentication()'));
     }
     return $this->user;
   }
 
+  public function setOAuthToken(
+    PhabricatorOAuthServerAccessToken $oauth_token) {
+    $this->oauthToken = $oauth_token;
+    return $this;
+  }
+
+  public function getOAuthToken() {
+    return $this->oauthToken;
+  }
+
   public function setIsClusterRequest($is_cluster_request) {
     $this->isClusterRequest = $is_cluster_request;
     return $this;
   }
 
   public function getIsClusterRequest() {
     return $this->isClusterRequest;
   }
 
   public function newContentSource() {
     return PhabricatorContentSource::newForSource(
       PhabricatorConduitContentSource::SOURCECONST);
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php
index d805168eb..e46db665d 100644
--- a/src/applications/config/controller/PhabricatorConfigAllController.php
+++ b/src/applications/config/controller/PhabricatorConfigAllController.php
@@ -1,75 +1,77 @@
 <?php
 
 final class PhabricatorConfigAllController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $db_values = id(new PhabricatorConfigEntry())
       ->loadAllWhere('namespace = %s', 'default');
     $db_values = mpull($db_values, null, 'getConfigKey');
 
     $rows = array();
     $options = PhabricatorApplicationConfigOptions::loadAllOptions();
     ksort($options);
     foreach ($options as $option) {
       $key = $option->getKey();
 
       if ($option->getHidden()) {
         $value = phutil_tag('em', array(), pht('Hidden'));
       } else {
         $value = PhabricatorEnv::getEnvConfig($key);
         $value = PhabricatorConfigJSON::prettyPrintJSON($value);
       }
 
       $db_value = idx($db_values, $key);
       $rows[] = array(
         phutil_tag(
           'a',
           array(
             'href' => $this->getApplicationURI('edit/'.$key.'/'),
           ),
           $key),
         $value,
         $db_value && !$db_value->getIsDeleted() ? pht('Customized') : '',
       );
     }
     $table = id(new AphrontTableView($rows))
       ->setColumnClasses(
         array(
           '',
           'wide',
         ))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Value'),
           pht('Customized'),
         ));
 
     $title = pht('Current Settings');
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb($title);
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Current Settings'));
     $panel->setTable($table);
 
-
     $nav = $this->buildSideNavView();
     $nav->selectFilter('all/');
-    $nav->setCrumbs($crumbs);
-    $nav->appendChild($panel);
-
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $panel,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php
index 67fbf6e12..ab9ddb3ad 100644
--- a/src/applications/config/controller/PhabricatorConfigCacheController.php
+++ b/src/applications/config/controller/PhabricatorConfigCacheController.php
@@ -1,183 +1,182 @@
 <?php
 
 final class PhabricatorConfigCacheController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('cache/');
 
     $title = pht('Cache Status');
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb(pht('Cache Status'));
 
     $code_box = $this->renderCodeBox();
     $data_box = $this->renderDataBox();
 
-    $nav->appendChild(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
         $code_box,
         $data_box,
-      ));
+    ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function renderCodeBox() {
     $cache = PhabricatorOpcodeCacheSpec::getActiveCacheSpec();
 
     $properties = id(new PHUIPropertyListView());
 
     $this->renderCommonProperties($properties, $cache);
 
     $purge_button = id(new PHUIButtonView())
       ->setText(pht('Purge Caches'))
       ->setHref('/config/cache/purge/')
       ->setTag('a')
       ->setWorkflow(true)
       ->setIcon('fa-exclamation-triangle');
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Opcode Cache'))
       ->addActionLink($purge_button);
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
   }
 
   private function renderDataBox() {
     $cache = PhabricatorDataCacheSpec::getActiveCacheSpec();
 
     $properties = id(new PHUIPropertyListView());
 
     $this->renderCommonProperties($properties, $cache);
 
     $table = null;
     if ($cache->getName() !== null) {
       $total_memory = $cache->getTotalMemory();
 
       $summary = $cache->getCacheSummary();
       $summary = isort($summary, 'total');
       $summary = array_reverse($summary, true);
 
       $rows = array();
       foreach ($summary as $key => $info) {
         $rows[] = array(
           $key,
           pht('%s', new PhutilNumber($info['count'])),
           phutil_format_bytes($info['max']),
           phutil_format_bytes($info['total']),
           sprintf('%.1f%%', (100 * ($info['total'] / $total_memory))),
         );
       }
 
       $table = id(new AphrontTableView($rows))
         ->setHeaders(
           array(
             pht('Pattern'),
             pht('Count'),
             pht('Largest'),
             pht('Total'),
             pht('Usage'),
           ))
         ->setColumnClasses(
           array(
             'wide',
             'n',
             'n',
             'n',
             'n',
           ));
     }
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Data Cache'))
       ->addPropertyList($properties)
       ->setTable($table);
   }
 
   private function renderCommonProperties(
     PHUIPropertyListView $properties,
     PhabricatorCacheSpec $cache) {
 
     if ($cache->getName() !== null) {
       $name = $this->renderYes($cache->getName());
     } else {
       $name = $this->renderNo(pht('None'));
     }
     $properties->addProperty(pht('Cache'), $name);
 
     if ($cache->getIsEnabled()) {
       $enabled = $this->renderYes(pht('Enabled'));
     } else {
       $enabled = $this->renderNo(pht('Not Enabled'));
     }
     $properties->addProperty(pht('Enabled'), $enabled);
 
     $version = $cache->getVersion();
     if ($version) {
       $properties->addProperty(pht('Version'), $this->renderInfo($version));
     }
 
     if ($cache->getName() === null) {
       return;
     }
 
     $mem_total = $cache->getTotalMemory();
     $mem_used = $cache->getUsedMemory();
 
     if ($mem_total) {
       $percent = 100 * ($mem_used / $mem_total);
 
       $properties->addProperty(
         pht('Memory Usage'),
         pht(
           '%s of %s',
           phutil_tag('strong', array(), sprintf('%.1f%%', $percent)),
           phutil_format_bytes($mem_total)));
     }
 
     $entry_count = $cache->getEntryCount();
     if ($entry_count !== null) {
       $properties->addProperty(
         pht('Cache Entries'),
         pht('%s', new PhutilNumber($entry_count)));
     }
 
   }
 
   private function renderYes($info) {
     return array(
       id(new PHUIIconView())->setIcon('fa-check', 'green'),
       ' ',
       $info,
     );
   }
 
   private function renderNo($info) {
     return array(
       id(new PHUIIconView())->setIcon('fa-times-circle', 'red'),
       ' ',
       $info,
     );
   }
 
   private function renderInfo($info) {
     return array(
       id(new PHUIIconView())->setIcon('fa-info-circle', 'grey'),
       ' ',
       $info,
     );
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
index 22df5104e..0366b90b1 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php
@@ -1,169 +1,169 @@
 <?php
 
 final class PhabricatorConfigDatabaseIssueController
   extends PhabricatorConfigDatabaseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $query = $this->buildSchemaQuery();
 
     $actual = $query->loadActualSchema();
     $expect = $query->loadExpectedSchema();
     $comp = $query->buildComparisonSchema($expect, $actual);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Database Issues'));
 
     // Collect all open issues.
     $issues = array();
     foreach ($comp->getDatabases() as $database_name => $database) {
       foreach ($database->getLocalIssues() as $issue) {
         $issues[] = array(
           $database_name,
           null,
           null,
           null,
           $issue,
         );
       }
       foreach ($database->getTables() as $table_name => $table) {
         foreach ($table->getLocalIssues() as $issue) {
           $issues[] = array(
             $database_name,
             $table_name,
             null,
             null,
             $issue,
           );
         }
         foreach ($table->getColumns() as $column_name => $column) {
           foreach ($column->getLocalIssues() as $issue) {
             $issues[] = array(
               $database_name,
               $table_name,
               'column',
               $column_name,
               $issue,
             );
           }
         }
         foreach ($table->getKeys() as $key_name => $key) {
           foreach ($key->getLocalIssues() as $issue) {
             $issues[] = array(
               $database_name,
               $table_name,
               'key',
               $key_name,
               $issue,
             );
           }
         }
       }
     }
 
 
     // Sort all open issues so that the most severe issues appear first.
     $order = array();
     $counts = array();
     foreach ($issues as $key => $issue) {
       $const = $issue[4];
       $status = PhabricatorConfigStorageSchema::getIssueStatus($const);
       $severity = PhabricatorConfigStorageSchema::getStatusSeverity($status);
       $order[$key] = sprintf(
         '~%d~%s%s%s',
         9 - $severity,
         $issue[0],
         $issue[1],
         $issue[3]);
 
       if (empty($counts[$status])) {
         $counts[$status] = 0;
       }
 
       $counts[$status]++;
     }
     asort($order);
     $issues = array_select_keys($issues, array_keys($order));
 
 
     // Render the issues.
     $rows = array();
     foreach ($issues as $issue) {
       $const = $issue[4];
 
       $database_link = phutil_tag(
         'a',
         array(
           'href' => $this->getApplicationURI('/database/'.$issue[0].'/'),
         ),
         $issue[0]);
 
       $rows[] = array(
         $this->renderIcon(
           PhabricatorConfigStorageSchema::getIssueStatus($const)),
         $database_link,
         $issue[1],
         $issue[2],
         $issue[3],
         PhabricatorConfigStorageSchema::getIssueDescription($const),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           null,
           pht('Database'),
           pht('Table'),
           pht('Type'),
           pht('Column/Key'),
           pht('Issue'),
         ))
       ->setColumnClasses(
         array(
           null,
           null,
           null,
           null,
           null,
           'wide',
         ));
 
     $errors = array();
 
     if (isset($counts[PhabricatorConfigStorageSchema::STATUS_FAIL])) {
       $errors[] = pht(
         'Detected %s serious issue(s) with the schemata.',
         new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_FAIL]));
     }
 
     if (isset($counts[PhabricatorConfigStorageSchema::STATUS_WARN])) {
       $errors[] = pht(
         'Detected %s warning(s) with the schemata.',
         new PhutilNumber($counts[PhabricatorConfigStorageSchema::STATUS_WARN]));
     }
 
     $title = pht('Database Issues');
 
     $table_box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->setFormErrors($errors)
       ->setTable($table);
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('dbissue/');
-    $nav->appendChild(
-      array(
-        $crumbs,
+
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
         $table_box,
-      ));
+    ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
index d886e98d7..b03ce2a9f 100644
--- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
+++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php
@@ -1,764 +1,766 @@
 <?php
 
 final class PhabricatorConfigDatabaseStatusController
   extends PhabricatorConfigDatabaseController {
 
   private $database;
   private $table;
   private $column;
   private $key;
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $this->database = $request->getURIData('database');
     $this->table = $request->getURIData('table');
     $this->column = $request->getURIData('column');
     $this->key = $request->getURIData('key');
 
     $query = $this->buildSchemaQuery();
 
     $actual = $query->loadActualSchema();
     $expect = $query->loadExpectedSchema();
     $comp = $query->buildComparisonSchema($expect, $actual);
 
     if ($this->column) {
       return $this->renderColumn(
         $comp,
         $expect,
         $actual,
         $this->database,
         $this->table,
         $this->column);
     } else if ($this->key) {
       return $this->renderKey(
         $comp,
         $expect,
         $actual,
         $this->database,
         $this->table,
         $this->key);
     } else if ($this->table) {
       return $this->renderTable(
         $comp,
         $expect,
         $actual,
         $this->database,
         $this->table);
     } else if ($this->database) {
       return $this->renderDatabase(
         $comp,
         $expect,
         $actual,
         $this->database);
     } else {
       return $this->renderServer(
         $comp,
         $expect,
         $actual);
     }
   }
 
   private function buildResponse($title, $body) {
     $nav = $this->buildSideNavView();
     $nav->selectFilter('database/');
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($this->database) {
       $crumbs->addTextCrumb(
         pht('Database Status'),
         $this->getApplicationURI('database/'));
       if ($this->table) {
         $crumbs->addTextCrumb(
           $this->database,
           $this->getApplicationURI('database/'.$this->database.'/'));
         if ($this->column || $this->key) {
           $crumbs->addTextCrumb(
             $this->table,
             $this->getApplicationURI(
               'database/'.$this->database.'/'.$this->table.'/'));
           if ($this->column) {
             $crumbs->addTextCrumb($this->column);
           } else {
             $crumbs->addTextCrumb($this->key);
           }
         } else {
           $crumbs->addTextCrumb($this->table);
         }
       } else {
         $crumbs->addTextCrumb($this->database);
       }
     } else {
       $crumbs->addTextCrumb(pht('Database Status'));
     }
 
-    $nav->setCrumbs($crumbs);
-    $nav->appendChild($body);
-
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $body,
+    ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 
   private function renderServer(
     PhabricatorConfigServerSchema $comp,
     PhabricatorConfigServerSchema $expect,
     PhabricatorConfigServerSchema $actual) {
 
     $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
     $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
 
     $rows = array();
     foreach ($comp->getDatabases() as $database_name => $database) {
       $actual_database = $actual->getDatabase($database_name);
       if ($actual_database) {
         $charset = $actual_database->getCharacterSet();
         $collation = $actual_database->getCollation();
       } else {
         $charset = null;
         $collation = null;
       }
 
       $status = $database->getStatus();
       $issues = $database->getIssues();
 
       $rows[] = array(
         $this->renderIcon($status),
         phutil_tag(
           'a',
           array(
             'href' => $this->getApplicationURI(
               '/database/'.$database_name.'/'),
           ),
           $database_name),
         $this->renderAttr($charset, $database->hasIssue($charset_issue)),
         $this->renderAttr($collation, $database->hasIssue($collation_issue)),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           null,
           pht('Database'),
           pht('Charset'),
           pht('Collation'),
         ))
       ->setColumnClasses(
         array(
           null,
           'wide pri',
           null,
           null,
         ));
 
     $title = pht('Database Status');
 
     $properties = $this->buildProperties(
       array(
       ),
       $comp->getIssues());
 
     $prop_box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->addPropertyList($properties);
 
     $table_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Databases'))
       ->setTable($table);
 
     return $this->buildResponse($title, array($prop_box, $table_box));
   }
 
   private function renderDatabase(
     PhabricatorConfigServerSchema $comp,
     PhabricatorConfigServerSchema $expect,
     PhabricatorConfigServerSchema $actual,
     $database_name) {
 
     $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
 
     $database = $comp->getDatabase($database_name);
     if (!$database) {
       return new Aphront404Response();
     }
 
     $rows = array();
     foreach ($database->getTables() as $table_name => $table) {
       $status = $table->getStatus();
 
       $rows[] = array(
         $this->renderIcon($status),
         phutil_tag(
           'a',
           array(
             'href' => $this->getApplicationURI(
               '/database/'.$database_name.'/'.$table_name.'/'),
           ),
           $table_name),
         $this->renderAttr(
           $table->getCollation(),
           $table->hasIssue($collation_issue)),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           null,
           pht('Table'),
           pht('Collation'),
         ))
       ->setColumnClasses(
         array(
           null,
           'wide pri',
           null,
         ));
 
     $title = pht('Database: %s', $database_name);
 
     $actual_database = $actual->getDatabase($database_name);
     if ($actual_database) {
       $actual_charset = $actual_database->getCharacterSet();
       $actual_collation = $actual_database->getCollation();
     } else {
       $actual_charset = null;
       $actual_collation = null;
     }
 
     $expect_database = $expect->getDatabase($database_name);
     if ($expect_database) {
       $expect_charset = $expect_database->getCharacterSet();
       $expect_collation = $expect_database->getCollation();
     } else {
       $expect_charset = null;
       $expect_collation = null;
     }
 
     $properties = $this->buildProperties(
       array(
         array(
           pht('Character Set'),
           $actual_charset,
         ),
         array(
           pht('Expected Character Set'),
           $expect_charset,
         ),
         array(
           pht('Collation'),
           $actual_collation,
         ),
         array(
           pht('Expected Collation'),
           $expect_collation,
         ),
       ),
       $database->getIssues());
 
     $prop_box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->addPropertyList($properties);
 
     $table_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Database Status'))
       ->setTable($table);
 
     return $this->buildResponse($title, array($prop_box, $table_box));
   }
 
   private function renderTable(
     PhabricatorConfigServerSchema $comp,
     PhabricatorConfigServerSchema $expect,
     PhabricatorConfigServerSchema $actual,
     $database_name,
     $table_name) {
 
     $type_issue = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
     $charset_issue = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
     $collation_issue = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
     $nullable_issue = PhabricatorConfigStorageSchema::ISSUE_NULLABLE;
     $unique_issue = PhabricatorConfigStorageSchema::ISSUE_UNIQUE;
     $columns_issue = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS;
     $longkey_issue = PhabricatorConfigStorageSchema::ISSUE_LONGKEY;
     $auto_issue = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT;
 
     $database = $comp->getDatabase($database_name);
     if (!$database) {
       return new Aphront404Response();
     }
 
     $table = $database->getTable($table_name);
     if (!$table) {
       return new Aphront404Response();
     }
 
     $actual_database = $actual->getDatabase($database_name);
     $actual_table = null;
     if ($actual_database) {
       $actual_table = $actual_database->getTable($table_name);
     }
 
     $expect_database = $expect->getDatabase($database_name);
     $expect_table = null;
     if ($expect_database) {
       $expect_table = $expect_database->getTable($table_name);
     }
 
     $rows = array();
     foreach ($table->getColumns() as $column_name => $column) {
       $expect_column = null;
       if ($expect_table) {
         $expect_column = $expect_table->getColumn($column_name);
       }
 
       $status = $column->getStatus();
 
       $data_type = null;
       if ($expect_column) {
         $data_type = $expect_column->getDataType();
       }
 
       $rows[] = array(
         $this->renderIcon($status),
         phutil_tag(
           'a',
           array(
             'href' => $this->getApplicationURI(
               'database/'.
               $database_name.'/'.
               $table_name.'/'.
               'col/'.
               $column_name.'/'),
           ),
           $column_name),
         $data_type,
         $this->renderAttr(
           $column->getColumnType(),
           $column->hasIssue($type_issue)),
         $this->renderAttr(
           $this->renderBoolean($column->getNullable()),
           $column->hasIssue($nullable_issue)),
         $this->renderAttr(
           $this->renderBoolean($column->getAutoIncrement()),
           $column->hasIssue($auto_issue)),
         $this->renderAttr(
           $column->getCharacterSet(),
           $column->hasIssue($charset_issue)),
         $this->renderAttr(
           $column->getCollation(),
           $column->hasIssue($collation_issue)),
       );
     }
 
     $table_view = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           null,
           pht('Column'),
           pht('Data Type'),
           pht('Column Type'),
           pht('Nullable'),
           pht('Autoincrement'),
           pht('Character Set'),
           pht('Collation'),
         ))
       ->setColumnClasses(
         array(
           null,
           'wide pri',
           null,
           null,
           null,
           null,
           null,
         ));
 
     $key_rows = array();
     foreach ($table->getKeys() as $key_name => $key) {
       $expect_key = null;
       if ($expect_table) {
         $expect_key = $expect_table->getKey($key_name);
       }
 
       $status = $key->getStatus();
 
       $size = 0;
       foreach ($key->getColumnNames() as $column_spec) {
         list($column_name, $prefix) = $key->getKeyColumnAndPrefix($column_spec);
         $column = $table->getColumn($column_name);
         if (!$column) {
           $size = 0;
           break;
         }
         $size += $column->getKeyByteLength($prefix);
       }
 
       $size_formatted = null;
       if ($size) {
         $size_formatted = $this->renderAttr(
           $size,
           $key->hasIssue($longkey_issue));
       }
 
       $key_rows[] = array(
         $this->renderIcon($status),
         phutil_tag(
           'a',
           array(
             'href' => $this->getApplicationURI(
               'database/'.
               $database_name.'/'.
               $table_name.'/'.
               'key/'.
               $key_name.'/'),
           ),
           $key_name),
         $this->renderAttr(
           implode(', ', $key->getColumnNames()),
           $key->hasIssue($columns_issue)),
         $this->renderAttr(
           $this->renderBoolean($key->getUnique()),
           $key->hasIssue($unique_issue)),
         $size_formatted,
       );
     }
 
     $keys_view = id(new AphrontTableView($key_rows))
       ->setHeaders(
         array(
           null,
           pht('Key'),
           pht('Columns'),
           pht('Unique'),
           pht('Size'),
         ))
       ->setColumnClasses(
         array(
           null,
           'wide pri',
           null,
           null,
           null,
         ));
 
     $title = pht('Database: %s.%s', $database_name, $table_name);
 
     if ($actual_table) {
       $actual_collation = $actual_table->getCollation();
     } else {
       $actual_collation = null;
     }
 
     if ($expect_table) {
       $expect_collation = $expect_table->getCollation();
     } else {
       $expect_collation = null;
     }
 
     $properties = $this->buildProperties(
       array(
         array(
           pht('Collation'),
           $actual_collation,
         ),
         array(
           pht('Expected Collation'),
           $expect_collation,
         ),
       ),
       $table->getIssues());
 
     $prop_box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->addPropertyList($properties);
 
     $table_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Database'))
       ->setTable($table_view);
 
     $key_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Keys'))
       ->setTable($keys_view);
 
     return $this->buildResponse($title, array($prop_box, $table_box, $key_box));
   }
 
   private function renderColumn(
     PhabricatorConfigServerSchema $comp,
     PhabricatorConfigServerSchema $expect,
     PhabricatorConfigServerSchema $actual,
     $database_name,
     $table_name,
     $column_name) {
 
     $database = $comp->getDatabase($database_name);
     if (!$database) {
       return new Aphront404Response();
     }
 
     $table = $database->getTable($table_name);
     if (!$table) {
       return new Aphront404Response();
     }
 
     $column = $table->getColumn($column_name);
     if (!$column) {
       return new Aphront404Response();
     }
 
     $actual_database = $actual->getDatabase($database_name);
     $actual_table = null;
     $actual_column = null;
     if ($actual_database) {
       $actual_table = $actual_database->getTable($table_name);
       if ($actual_table) {
         $actual_column = $actual_table->getColumn($column_name);
       }
     }
 
     $expect_database = $expect->getDatabase($database_name);
     $expect_table = null;
     $expect_column = null;
     if ($expect_database) {
       $expect_table = $expect_database->getTable($table_name);
       if ($expect_table) {
         $expect_column = $expect_table->getColumn($column_name);
       }
     }
 
     if ($actual_column) {
       $actual_coltype = $actual_column->getColumnType();
       $actual_charset = $actual_column->getCharacterSet();
       $actual_collation = $actual_column->getCollation();
       $actual_nullable = $actual_column->getNullable();
       $actual_auto = $actual_column->getAutoIncrement();
     } else {
       $actual_coltype = null;
       $actual_charset = null;
       $actual_collation = null;
       $actual_nullable = null;
       $actual_auto = null;
     }
 
     if ($expect_column) {
       $data_type = $expect_column->getDataType();
       $expect_coltype = $expect_column->getColumnType();
       $expect_charset = $expect_column->getCharacterSet();
       $expect_collation = $expect_column->getCollation();
       $expect_nullable = $expect_column->getNullable();
       $expect_auto = $expect_column->getAutoIncrement();
     } else {
       $data_type = null;
       $expect_coltype = null;
       $expect_charset = null;
       $expect_collation = null;
       $expect_nullable = null;
       $expect_auto = null;
     }
 
 
     $title = pht(
       'Database Status: %s.%s.%s',
       $database_name,
       $table_name,
       $column_name);
 
     $properties = $this->buildProperties(
       array(
         array(
           pht('Data Type'),
           $data_type,
         ),
         array(
           pht('Column Type'),
           $actual_coltype,
         ),
         array(
           pht('Expected Column Type'),
           $expect_coltype,
         ),
         array(
           pht('Character Set'),
           $actual_charset,
         ),
         array(
           pht('Expected Character Set'),
           $expect_charset,
         ),
         array(
           pht('Collation'),
           $actual_collation,
         ),
         array(
           pht('Expected Collation'),
           $expect_collation,
         ),
         array(
           pht('Nullable'),
           $this->renderBoolean($actual_nullable),
         ),
         array(
           pht('Expected Nullable'),
           $this->renderBoolean($expect_nullable),
         ),
         array(
           pht('Autoincrement'),
           $this->renderBoolean($actual_auto),
         ),
         array(
           pht('Expected Autoincrement'),
           $this->renderBoolean($expect_auto),
         ),
       ),
       $column->getIssues());
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->addPropertyList($properties);
 
     return $this->buildResponse($title, $box);
   }
 
   private function renderKey(
     PhabricatorConfigServerSchema $comp,
     PhabricatorConfigServerSchema $expect,
     PhabricatorConfigServerSchema $actual,
     $database_name,
     $table_name,
     $key_name) {
 
     $database = $comp->getDatabase($database_name);
     if (!$database) {
       return new Aphront404Response();
     }
 
     $table = $database->getTable($table_name);
     if (!$table) {
       return new Aphront404Response();
     }
 
     $key = $table->getKey($key_name);
     if (!$key) {
       return new Aphront404Response();
     }
 
     $actual_database = $actual->getDatabase($database_name);
     $actual_table = null;
     $actual_key = null;
     if ($actual_database) {
       $actual_table = $actual_database->getTable($table_name);
       if ($actual_table) {
         $actual_key = $actual_table->getKey($key_name);
       }
     }
 
     $expect_database = $expect->getDatabase($database_name);
     $expect_table = null;
     $expect_key = null;
     if ($expect_database) {
       $expect_table = $expect_database->getTable($table_name);
       if ($expect_table) {
         $expect_key = $expect_table->getKey($key_name);
       }
     }
 
     if ($actual_key) {
       $actual_columns = $actual_key->getColumnNames();
       $actual_unique = $actual_key->getUnique();
     } else {
       $actual_columns = array();
       $actual_unique = null;
     }
 
     if ($expect_key) {
       $expect_columns = $expect_key->getColumnNames();
       $expect_unique = $expect_key->getUnique();
     } else {
       $expect_columns = array();
       $expect_unique = null;
     }
 
     $title = pht(
       'Database Status: %s.%s (%s)',
       $database_name,
       $table_name,
       $key_name);
 
     $properties = $this->buildProperties(
       array(
         array(
           pht('Unique'),
           $this->renderBoolean($actual_unique),
         ),
         array(
           pht('Expected Unique'),
           $this->renderBoolean($expect_unique),
         ),
         array(
           pht('Columns'),
           implode(', ', $actual_columns),
         ),
         array(
           pht('Expected Columns'),
           implode(', ', $expect_columns),
         ),
       ),
       $key->getIssues());
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($this->buildHeaderWithDocumentationLink($title))
       ->addPropertyList($properties);
 
     return $this->buildResponse($title, $box);
   }
 
   private function buildProperties(array $properties, array $issues) {
     $view = id(new PHUIPropertyListView())
       ->setUser($this->getRequest()->getUser());
 
     foreach ($properties as $property) {
       list($key, $value) = $property;
       $view->addProperty($key, $value);
     }
 
     $status_view = new PHUIStatusListView();
     if (!$issues) {
       $status_view->addItem(
         id(new PHUIStatusItemView())
           ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
           ->setTarget(pht('No Schema Issues')));
     } else {
       foreach ($issues as $issue) {
         $note = PhabricatorConfigStorageSchema::getIssueDescription($issue);
 
         $status = PhabricatorConfigStorageSchema::getIssueStatus($issue);
         switch ($status) {
           case PhabricatorConfigStorageSchema::STATUS_WARN:
             $icon = PHUIStatusItemView::ICON_WARNING;
             $color = 'yellow';
             break;
           case PhabricatorConfigStorageSchema::STATUS_FAIL:
           default:
             $icon = PHUIStatusItemView::ICON_REJECT;
             $color = 'red';
             break;
         }
 
         $item = id(new PHUIStatusItemView())
           ->setTarget(PhabricatorConfigStorageSchema::getIssueName($issue))
           ->setIcon($icon, $color)
           ->setNote($note);
 
         $status_view->addItem($item);
       }
     }
     $view->addProperty(pht('Schema Status'), $status_view);
 
     return $view;
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php
index 0c93c86f0..ab246b56c 100644
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -1,586 +1,592 @@
 <?php
 
 final class PhabricatorConfigEditController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $key = $request->getURIData('key');
 
 
     $options = PhabricatorApplicationConfigOptions::loadAllOptions();
     if (empty($options[$key])) {
       $ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig();
       if (isset($ancient[$key])) {
         $desc = pht(
           "This configuration has been removed. You can safely delete ".
           "it.\n\n%s",
           $ancient[$key]);
       } else {
         $desc = pht(
           'This configuration option is unknown. It may be misspelled, '.
           'or have existed in a previous version of Phabricator.');
       }
 
       // This may be a dead config entry, which existed in the past but no
       // longer exists. Allow it to be edited so it can be reviewed and
       // deleted.
       $option = id(new PhabricatorConfigOption())
         ->setKey($key)
         ->setType('wild')
         ->setDefault(null)
         ->setDescription($desc);
       $group = null;
       $group_uri = $this->getApplicationURI();
     } else {
       $option = $options[$key];
       $group = $option->getGroup();
       $group_uri = $this->getApplicationURI('group/'.$group->getKey().'/');
     }
 
     $issue = $request->getStr('issue');
     if ($issue) {
       // If the user came here from an open setup issue, send them back.
       $done_uri = $this->getApplicationURI('issue/'.$issue.'/');
     } else {
       $done_uri = $group_uri;
     }
 
     // Check if the config key is already stored in the database.
     // Grab the value if it is.
     $config_entry = id(new PhabricatorConfigEntry())
       ->loadOneWhere(
         'configKey = %s AND namespace = %s',
         $key,
         'default');
     if (!$config_entry) {
       $config_entry = id(new PhabricatorConfigEntry())
         ->setConfigKey($key)
         ->setNamespace('default')
         ->setIsDeleted(true);
       $config_entry->setPHID($config_entry->generatePHID());
     }
 
     $e_value = null;
     $errors = array();
     if ($request->isFormPost() && !$option->getLocked()) {
 
       $result = $this->readRequest(
         $option,
         $request);
 
       list($e_value, $value_errors, $display_value, $xaction) = $result;
       $errors = array_merge($errors, $value_errors);
 
       if (!$errors) {
 
         $editor = id(new PhabricatorConfigEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request);
 
         try {
           $editor->applyTransactions($config_entry, array($xaction));
           return id(new AphrontRedirectResponse())->setURI($done_uri);
         } catch (PhabricatorConfigValidationException $ex) {
           $e_value = pht('Invalid');
           $errors[] = $ex->getMessage();
         }
       }
     } else {
       if ($config_entry->getIsDeleted()) {
         $display_value = null;
       } else {
         $display_value = $this->getDisplayValue(
           $option,
           $config_entry,
           $config_entry->getValue());
       }
     }
 
     $form = new AphrontFormView();
 
     $error_view = null;
     if ($errors) {
       $error_view = id(new PHUIInfoView())
         ->setErrors($errors);
     }
 
     $status_items = array();
     if ($option->getHidden()) {
       $message = pht(
         'This configuration is hidden and can not be edited or viewed from '.
         'the web interface.');
 
       $status_items[] = id(new PHUIStatusItemView())
         ->setIcon('fa-eye-slash red')
         ->setTarget(phutil_tag('strong', array(), pht('Configuration Hidden')))
         ->setNote($message);
     } else if ($option->getLocked()) {
       $message = $option->getLockedMessage();
 
       $status_items[] = id(new PHUIStatusItemView())
         ->setIcon('fa-lock red')
         ->setTarget(phutil_tag('strong', array(), pht('Configuration Locked')))
         ->setNote($message);
     }
 
     if ($status_items) {
       $doc_href = PhabricatorEnv::getDoclink(
         'Configuration Guide: Locked and Hidden Configuration');
 
       $doc_link = phutil_tag(
         'a',
         array(
           'href' => $doc_href,
           'target' => '_blank',
         ),
         pht('Configuration Guide: Locked and Hidden Configuration'));
 
       $status_items[] = id(new PHUIStatusItemView())
         ->setIcon('fa-book')
         ->setTarget(phutil_tag('strong', array(), pht('Learn More')))
         ->setNote($doc_link);
     }
 
     if ($option->getHidden() || $option->getLocked()) {
       $control = null;
     } else {
       $control = $this->renderControl(
         $option,
         $display_value,
         $e_value);
     }
 
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($viewer);
     $engine->addObject($option, 'description');
     $engine->process();
     $description = phutil_tag(
       'div',
       array(
         'class' => 'phabricator-remarkup',
       ),
       $engine->getOutput($option, 'description'));
 
     $form
       ->setUser($viewer)
       ->addHiddenInput('issue', $request->getStr('issue'));
 
     if ($status_items) {
       $status_view = id(new PHUIStatusListView());
 
       foreach ($status_items as $status_item) {
         $status_view->addItem($status_item);
       }
 
       $form->appendControl(
         id(new AphrontFormMarkupControl())
           ->setValue($status_view));
     }
 
     $description = $option->getDescription();
     if (strlen($description)) {
       $description_view = new PHUIRemarkupView($viewer, $description);
 
       $form
         ->appendChild(
           id(new AphrontFormMarkupControl())
             ->setLabel(pht('Description'))
             ->setValue($description_view));
     }
 
     if ($group) {
       $extra = $group->renderContextualDescription(
         $option,
         $request);
       if ($extra !== null) {
         $form->appendChild(
           id(new AphrontFormMarkupControl())
             ->setValue($extra));
       }
     }
 
     $form
       ->appendChild($control);
 
 
     if (!$option->getLocked()) {
       $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($done_uri)
           ->setValue(pht('Save Config Entry')));
     }
 
     if (!$option->getHidden()) {
       $form->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Current Configuration'))
           ->setValue($this->renderDefaults($option, $config_entry)));
     }
 
     $examples = $this->renderExamples($option);
     if ($examples) {
       $form->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Examples'))
           ->setValue($examples));
     }
 
-    $title = pht('Edit %s', $key);
+    $title = pht('Edit Option: %s', $key);
+    $header_icon = 'fa-pencil';
     $short = pht('Edit');
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Config Option'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     if ($error_view) {
       $form_box->setInfoView($error_view);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Config'), $this->getApplicationURI());
 
     if ($group) {
       $crumbs->addTextCrumb($group->getName(), $group_uri);
     }
 
     $crumbs->addTextCrumb($key, '/config/edit/'.$key);
+    $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $config_entry,
       new PhabricatorConfigTransactionQuery());
     $timeline->setShouldTerminate(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-        $timeline,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($form_box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function readRequest(
     PhabricatorConfigOption $option,
     AphrontRequest $request) {
 
     $xaction = new PhabricatorConfigTransaction();
     $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT);
 
     $e_value = null;
     $errors = array();
 
     $value = $request->getStr('value');
     if (!strlen($value)) {
       $value = null;
 
       $xaction->setNewValue(
         array(
           'deleted' => true,
           'value'   => null,
         ));
 
       return array($e_value, $errors, $value, $xaction);
     }
 
     if ($option->isCustomType()) {
       $info = $option->getCustomObject()->readRequest($option, $request);
       list($e_value, $errors, $set_value, $value) = $info;
     } else {
       $type = $option->getType();
       $set_value = null;
 
       switch ($type) {
         case 'int':
           if (preg_match('/^-?[0-9]+$/', trim($value))) {
             $set_value = (int)$value;
           } else {
             $e_value = pht('Invalid');
             $errors[] = pht('Value must be an integer.');
           }
           break;
         case 'string':
         case 'enum':
           $set_value = (string)$value;
           break;
         case 'list<string>':
         case 'list<regex>':
           $set_value = phutil_split_lines(
             $request->getStr('value'),
             $retain_endings = false);
 
           foreach ($set_value as $key => $v) {
             if (!strlen($v)) {
               unset($set_value[$key]);
             }
           }
           $set_value = array_values($set_value);
 
           break;
         case 'set':
           $set_value = array_fill_keys($request->getStrList('value'), true);
           break;
         case 'bool':
           switch ($value) {
             case 'true':
               $set_value = true;
               break;
             case 'false':
               $set_value = false;
               break;
             default:
               $e_value = pht('Invalid');
               $errors[] = pht('Value must be boolean, "true" or "false".');
               break;
           }
           break;
         case 'class':
           if (!class_exists($value)) {
             $e_value = pht('Invalid');
             $errors[] = pht('Class does not exist.');
           } else {
             $base = $option->getBaseClass();
             if (!is_subclass_of($value, $base)) {
               $e_value = pht('Invalid');
               $errors[] = pht('Class is not of valid type.');
             } else {
               $set_value = $value;
             }
           }
           break;
         default:
           $json = json_decode($value, true);
           if ($json === null && strtolower($value) != 'null') {
             $e_value = pht('Invalid');
             $errors[] = pht(
               'The given value must be valid JSON. This means, among '.
               'other things, that you must wrap strings in double-quotes.');
           } else {
             $set_value = $json;
           }
           break;
       }
     }
 
     if (!$errors) {
       $xaction->setNewValue(
         array(
           'deleted' => false,
           'value'   => $set_value,
         ));
     } else {
       $xaction = null;
     }
 
     return array($e_value, $errors, $value, $xaction);
   }
 
   private function getDisplayValue(
     PhabricatorConfigOption $option,
     PhabricatorConfigEntry $entry,
     $value) {
 
     if ($option->isCustomType()) {
       return $option->getCustomObject()->getDisplayValue(
         $option,
         $entry,
         $value);
     } else {
       $type = $option->getType();
       switch ($type) {
         case 'int':
         case 'string':
         case 'enum':
         case 'class':
           return $value;
         case 'bool':
           return $value ? 'true' : 'false';
         case 'list<string>':
         case 'list<regex>':
           return implode("\n", nonempty($value, array()));
         case 'set':
           return implode("\n", nonempty(array_keys($value), array()));
         default:
           return PhabricatorConfigJSON::prettyPrintJSON($value);
       }
     }
   }
 
   private function renderControl(
     PhabricatorConfigOption $option,
     $display_value,
     $e_value) {
 
     if ($option->isCustomType()) {
       $control = $option->getCustomObject()->renderControl(
         $option,
         $display_value,
         $e_value);
     } else {
       $type = $option->getType();
       switch ($type) {
         case 'int':
         case 'string':
           $control = id(new AphrontFormTextControl());
           break;
         case 'bool':
           $control = id(new AphrontFormSelectControl())
             ->setOptions(
               array(
                 ''      => pht('(Use Default)'),
                 'true'  => idx($option->getBoolOptions(), 0),
                 'false' => idx($option->getBoolOptions(), 1),
               ));
           break;
         case 'enum':
           $options = array_mergev(
             array(
               array('' => pht('(Use Default)')),
               $option->getEnumOptions(),
             ));
           $control = id(new AphrontFormSelectControl())
             ->setOptions($options);
           break;
         case 'class':
           $symbols = id(new PhutilSymbolLoader())
             ->setType('class')
             ->setAncestorClass($option->getBaseClass())
             ->setConcreteOnly(true)
             ->selectSymbolsWithoutLoading();
           $names = ipull($symbols, 'name', 'name');
           asort($names);
           $names = array(
             '' => pht('(Use Default)'),
           ) + $names;
 
           $control = id(new AphrontFormSelectControl())
             ->setOptions($names);
           break;
         case 'list<string>':
         case 'list<regex>':
           $control = id(new AphrontFormTextAreaControl())
             ->setCaption(pht('Separate values with newlines.'));
           break;
         case 'set':
           $control = id(new AphrontFormTextAreaControl())
             ->setCaption(pht('Separate values with newlines or commas.'));
           break;
         default:
           $control = id(new AphrontFormTextAreaControl())
             ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
             ->setCustomClass('PhabricatorMonospaced')
             ->setCaption(pht('Enter value in JSON.'));
           break;
       }
 
       $control
         ->setLabel(pht('Database Value'))
         ->setError($e_value)
         ->setValue($display_value)
         ->setName('value');
     }
 
     return $control;
   }
 
   private function renderExamples(PhabricatorConfigOption $option) {
     $examples = $option->getExamples();
     if (!$examples) {
       return null;
     }
 
     $table = array();
     $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
       phutil_tag('th', array(), pht('Example')),
       phutil_tag('th', array(), pht('Value')),
     ));
     foreach ($examples as $example) {
       list($value, $description) = $example;
 
       if ($value === null) {
         $value = phutil_tag('em', array(), pht('(empty)'));
       } else {
         if (is_array($value)) {
           $value = implode("\n", $value);
         }
       }
 
       $table[] = phutil_tag('tr', array(), array(
         phutil_tag('th', array(), $description),
         phutil_tag('td', array(), $value),
       ));
     }
 
     require_celerity_resource('config-options-css');
 
     return phutil_tag(
       'table',
       array(
         'class' => 'config-option-table',
       ),
       $table);
   }
 
   private function renderDefaults(
     PhabricatorConfigOption $option,
     PhabricatorConfigEntry $entry) {
 
     $stack = PhabricatorEnv::getConfigSourceStack();
     $stack = $stack->getStack();
 
     $table = array();
     $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
       phutil_tag('th', array(), pht('Source')),
       phutil_tag('th', array(), pht('Value')),
     ));
 
     $is_effective_value = true;
     foreach ($stack as $key => $source) {
       $row_classes = array(
         'column-labels',
       );
 
       $value = $source->getKeys(
         array(
           $option->getKey(),
         ));
 
       if (!array_key_exists($option->getKey(), $value)) {
         $value = phutil_tag('em', array(), pht('(No Value Configured)'));
       } else {
         $value = $this->getDisplayValue(
           $option,
           $entry,
           $value[$option->getKey()]);
 
         if ($is_effective_value) {
           $is_effective_value = false;
           $row_classes[] = 'config-options-effective-value';
         }
       }
 
       $table[] = phutil_tag(
         'tr',
         array(
           'class' => implode(' ', $row_classes),
         ),
         array(
           phutil_tag('th', array(), $source->getName()),
           phutil_tag('td', array(), $value),
         ));
     }
 
     require_celerity_resource('config-options-css');
 
     return phutil_tag(
       'table',
       array(
         'class' => 'config-option-table',
       ),
       $table);
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php
index 456949142..af5ed4122 100644
--- a/src/applications/config/controller/PhabricatorConfigGroupController.php
+++ b/src/applications/config/controller/PhabricatorConfigGroupController.php
@@ -1,102 +1,106 @@
 <?php
 
 final class PhabricatorConfigGroupController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $group_key = $request->getURIData('key');
 
     $groups = PhabricatorApplicationConfigOptions::loadAll();
     $options = idx($groups, $group_key);
     if (!$options) {
       return new Aphront404Response();
     }
 
     $title = pht('%s Configuration', $options->getName());
     $list = $this->buildOptionList($options->getOptions());
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
       ->setObjectList($list);
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb(pht('Config'), $this->getApplicationURI())
-      ->addTextCrumb($options->getName(), $this->getApplicationURI());
-
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+      ->addTextCrumb($options->getName(), $this->getApplicationURI())
+      ->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-sliders');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildOptionList(array $options) {
     assert_instances_of($options, 'PhabricatorConfigOption');
 
     require_celerity_resource('config-options-css');
 
     $db_values = array();
     if ($options) {
       $db_values = id(new PhabricatorConfigEntry())->loadAllWhere(
         'configKey IN (%Ls) AND namespace = %s',
         mpull($options, 'getKey'),
         'default');
       $db_values = mpull($db_values, null, 'getConfigKey');
     }
 
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($this->getRequest()->getUser());
     foreach ($options as $option) {
       $engine->addObject($option, 'summary');
     }
     $engine->process();
 
     $list = new PHUIObjectItemListView();
     foreach ($options as $option) {
       $summary = $engine->getOutput($option, 'summary');
 
       $item = id(new PHUIObjectItemView())
         ->setHeader($option->getKey())
         ->setHref('/config/edit/'.$option->getKey().'/')
         ->addAttribute($summary);
 
       if (!$option->getHidden()) {
         $current_value = PhabricatorEnv::getEnvConfig($option->getKey());
         $current_value = PhabricatorConfigJSON::prettyPrintJSON(
           $current_value);
         $current_value = phutil_tag(
           'div',
           array(
             'class' => 'config-options-current-value',
           ),
           array(
             phutil_tag('span', array(), pht('Current Value:')),
             ' '.$current_value,
           ));
 
         $item->appendChild($current_value);
       }
 
       $db_value = idx($db_values, $option->getKey());
       if ($db_value && !$db_value->getIsDeleted()) {
         $item->addIcon('edit', pht('Customized'));
       }
 
       if ($option->getHidden()) {
         $item->addIcon('unpublish', pht('Hidden'));
       } else if ($option->getLocked()) {
         $item->addIcon('lock', pht('Locked'));
       }
 
       $list->addItem($item);
     }
 
     return $list;
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php
index d86fb7987..2709041f9 100644
--- a/src/applications/config/controller/PhabricatorConfigHistoryController.php
+++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php
@@ -1,52 +1,52 @@
 <?php
 
 final class PhabricatorConfigHistoryController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $xactions = id(new PhabricatorConfigTransactionQuery())
       ->setViewer($viewer)
       ->needComments(true)
       ->execute();
 
     $object = new PhabricatorConfigEntry();
 
     $xaction = $object->getApplicationTransactionTemplate();
 
     $view = $xaction->getApplicationTransactionViewObject();
 
     $timeline = $view
       ->setUser($viewer)
       ->setTransactions($xactions)
       ->setRenderAsFeed(true)
       ->setObjectPHID(PhabricatorPHIDConstants::PHID_VOID);
 
     $timeline->setShouldTerminate(true);
 
     $object->willRenderTimeline($timeline, $this->getRequest());
 
     $title = pht('Settings History');
 
     $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->setBorder(true);
     $crumbs->addTextCrumb('Config', $this->getApplicationURI());
     $crumbs->addTextCrumb($title, '/config/history/');
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('history/');
-    $nav->setCrumbs($crumbs);
-    $nav->appendChild($timeline);
-
-    return $this->buildApplicationPage(
-      array(
-        $nav,
-      ),
-      array(
-        'title' => $title,
-      ));
+
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $timeline,
+    ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigIgnoreController.php b/src/applications/config/controller/PhabricatorConfigIgnoreController.php
index 80a859c14..cfe5a225e 100644
--- a/src/applications/config/controller/PhabricatorConfigIgnoreController.php
+++ b/src/applications/config/controller/PhabricatorConfigIgnoreController.php
@@ -1,63 +1,61 @@
 <?php
 
 final class PhabricatorConfigIgnoreController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $issue = $request->getURIData('key');
     $verb = $request->getURIData('verb');
 
     $issue_uri = $this->getApplicationURI('issue/'.$issue.'/');
 
     if ($request->isDialogFormPost()) {
       $this->manageApplication($issue);
       return id(new AphrontRedirectResponse())->setURI($issue_uri);
     }
 
     if ($verb == 'ignore') {
       $title = pht('Really ignore this setup issue?');
       $submit_title = pht('Ignore');
       $body = pht(
         "You can ignore an issue if you don't want to fix it, or plan to ".
         "fix it later. Ignored issues won't appear on every page but will ".
         "still be shown in the list of open issues.");
     } else if ($verb == 'unignore') {
       $title = pht('Unignore this setup issue?');
       $submit_title = pht('Unignore');
       $body = pht(
         'This issue will no longer be suppressed, and will return to its '.
         'rightful place as a global setup warning.');
     } else {
       throw new Exception(pht('Unrecognized verb: %s', $verb));
     }
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($request->getUser())
+    return $this->newDialog()
       ->setTitle($title)
       ->appendChild($body)
       ->addSubmitButton($submit_title)
       ->addCancelButton($issue_uri);
 
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
   public function manageApplication($issue) {
     $key = 'config.ignore-issues';
     $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
     $list = $config_entry->getValue();
 
     if (isset($list[$issue])) {
       unset($list[$issue]);
     } else {
       $list[$issue] = true;
     }
 
     PhabricatorConfigEditor::storeNewValue(
       $this->getRequest()->getUser(),
       $config_entry,
       $list,
       PhabricatorContentSource::newFromRequest($this->getRequest()));
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php
index 89b8ea7cd..02bec4e08 100644
--- a/src/applications/config/controller/PhabricatorConfigIssueListController.php
+++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php
@@ -1,113 +1,114 @@
 <?php
 
 final class PhabricatorConfigIssueListController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('issue/');
 
     $issues = PhabricatorSetupCheck::runAllChecks();
     PhabricatorSetupCheck::setOpenSetupIssueKeys(
       PhabricatorSetupCheck::getUnignoredIssueKeys($issues));
 
     $important = $this->buildIssueList(
       $issues, PhabricatorSetupCheck::GROUP_IMPORTANT);
     $php = $this->buildIssueList(
       $issues, PhabricatorSetupCheck::GROUP_PHP);
     $mysql = $this->buildIssueList(
       $issues, PhabricatorSetupCheck::GROUP_MYSQL);
     $other = $this->buildIssueList(
       $issues, PhabricatorSetupCheck::GROUP_OTHER);
 
     $setup_issues = array();
     if ($important) {
       $setup_issues[] = id(new PHUIObjectBoxView())
         ->setHeaderText(pht('Important Setup Issues'))
         ->setColor(PHUIObjectBoxView::COLOR_RED)
         ->setObjectList($important);
     }
 
     if ($php) {
       $setup_issues[] = id(new PHUIObjectBoxView())
         ->setHeaderText(pht('PHP Setup Issues'))
         ->setObjectList($php);
     }
 
     if ($mysql) {
       $setup_issues[] = id(new PHUIObjectBoxView())
         ->setHeaderText(pht('MySQL Setup Issues'))
         ->setObjectList($mysql);
     }
 
     if ($other) {
       $setup_issues[] = id(new PHUIObjectBoxView())
         ->setHeaderText(pht('Other Setup Issues'))
         ->setObjectList($other);
     }
 
     if (empty($setup_issues)) {
       $setup_issues[] = id(new PHUIInfoView())
         ->setTitle(pht('No Issues'))
         ->appendChild(
           pht('Your install has no current setup issues to resolve.'))
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
     }
 
-    $nav->appendChild($setup_issues);
-
     $title = pht('Setup Issues');
 
     $crumbs = $this
       ->buildApplicationCrumbs($nav)
       ->addTextCrumb(pht('Setup'), $this->getApplicationURI('issue/'));
 
-    $nav->setCrumbs($crumbs);
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $setup_issues,
+    ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildIssueList(array $issues, $group) {
     assert_instances_of($issues, 'PhabricatorSetupIssue');
     $list = new PHUIObjectItemListView();
     $ignored_items = array();
     $items = 0;
 
     foreach ($issues as $issue) {
       if ($issue->getGroup() == $group) {
         $items++;
         $href = $this->getApplicationURI('/issue/'.$issue->getIssueKey().'/');
         $item = id(new PHUIObjectItemView())
           ->setHeader($issue->getName())
           ->setHref($href)
           ->addAttribute($issue->getSummary());
         if (!$issue->getIsIgnored()) {
           $item->setStatusIcon('fa-warning yellow');
           $list->addItem($item);
         } else {
           $item->addIcon('fa-eye-slash', pht('Ignored'));
           $item->setDisabled(true);
           $item->setStatusIcon('fa-warning grey');
           $ignored_items[] = $item;
         }
       }
     }
 
     foreach ($ignored_items as $item) {
       $list->addItem($item);
     }
 
     if ($items == 0) {
       return null;
     } else {
       return $list;
     }
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php
index e8d6e188a..b1d1c299a 100644
--- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php
+++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php
@@ -1,65 +1,61 @@
 <?php
 
 final class PhabricatorConfigIssueViewController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $issue_key = $request->getURIData('key');
 
     $issues = PhabricatorSetupCheck::runAllChecks();
     PhabricatorSetupCheck::setOpenSetupIssueKeys(
       PhabricatorSetupCheck::getUnignoredIssueKeys($issues));
 
     if (empty($issues[$issue_key])) {
       $content = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
         ->setTitle(pht('Issue Resolved'))
         ->appendChild(pht('This setup issue has been resolved. '))
         ->appendChild(
           phutil_tag(
             'a',
             array(
               'href' => $this->getApplicationURI('issue/'),
             ),
             pht('Return to Open Issue List')));
       $title = pht('Resolved Issue');
     } else {
       $issue = $issues[$issue_key];
       $content = $this->renderIssue($issue);
       $title = $issue->getShortName();
     }
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->setBorder(true)
       ->addTextCrumb(pht('Setup Issues'), $this->getApplicationURI('issue/'))
       ->addTextCrumb($title, $request->getRequestURI());
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $content,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($content);
   }
 
   private function renderIssue(PhabricatorSetupIssue $issue) {
     require_celerity_resource('setup-issue-css');
 
     $view = new PhabricatorSetupIssueView();
     $view->setIssue($issue);
 
     $container = phutil_tag(
       'div',
       array(
         'class' => 'setup-issue-background',
       ),
       $view->render());
 
     return $container;
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php
index 9e87995eb..8b0ff42e5 100644
--- a/src/applications/config/controller/PhabricatorConfigListController.php
+++ b/src/applications/config/controller/PhabricatorConfigListController.php
@@ -1,64 +1,62 @@
 <?php
 
 final class PhabricatorConfigListController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('/');
 
     $groups = PhabricatorApplicationConfigOptions::loadAll();
     $core_list = $this->buildConfigOptionsList($groups, 'core');
     $apps_list = $this->buildConfigOptionsList($groups, 'apps');
 
     $title = pht('Phabricator Configuration');
 
     $core = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setObjectList($core_list);
 
     $apps = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Applications Configuration'))
       ->setObjectList($apps_list);
 
-    $nav->appendChild(
-      array(
-        $core,
-        $apps,
-      ));
-
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb(pht('Config'), $this->getApplicationURI());
 
-    $nav->setCrumbs($crumbs);
-
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $core,
+        $apps,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildConfigOptionsList(array $groups, $type) {
     assert_instances_of($groups, 'PhabricatorApplicationConfigOptions');
 
     $list = new PHUIObjectItemListView();
     $groups = msort($groups, 'getName');
     foreach ($groups as $group) {
       if ($group->getGroup() == $type) {
         $item = id(new PHUIObjectItemView())
           ->setHeader($group->getName())
           ->setHref('/config/group/'.$group->getKey().'/')
           ->addAttribute($group->getDescription())
           ->setIcon($group->getIcon());
         $list->addItem($item);
       }
     }
 
     return $list;
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php
index 76343ecc1..3a67a8cdb 100644
--- a/src/applications/config/controller/PhabricatorConfigModuleController.php
+++ b/src/applications/config/controller/PhabricatorConfigModuleController.php
@@ -1,37 +1,37 @@
 <?php
 
 final class PhabricatorConfigModuleController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $key = $request->getURIData('module');
 
     $all_modules = PhabricatorConfigModule::getAllModules();
     if (empty($all_modules[$key])) {
       return new Aphront404Response();
     }
 
     $module = $all_modules[$key];
     $content = $module->renderModuleStatus($request);
-    $name = $module->getModuleName();
+    $title = $module->getModuleName();
 
     $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->addTextCrumb($name);
+    $crumbs->addTextCrumb($title);
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('module/'.$key.'/');
-    $nav->appendChild(
-      array(
-        $crumbs,
+
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
         $content,
       ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $name,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/config/controller/PhabricatorConfigWelcomeController.php b/src/applications/config/controller/PhabricatorConfigWelcomeController.php
index e2d704e0b..addf80e62 100644
--- a/src/applications/config/controller/PhabricatorConfigWelcomeController.php
+++ b/src/applications/config/controller/PhabricatorConfigWelcomeController.php
@@ -1,399 +1,401 @@
 <?php
 
 final class PhabricatorConfigWelcomeController
   extends PhabricatorConfigController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('welcome/');
 
     $title = pht('Welcome');
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb(pht('Welcome'));
 
-    $nav->setCrumbs($crumbs);
-    $nav->appendChild($this->buildWelcomeScreen($request));
-
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn(array(
+        $this->buildWelcomeScreen($request),
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   public function buildWelcomeScreen(AphrontRequest $request) {
     $viewer = $request->getUser();
     $this->requireResource('config-welcome-css');
 
     $content = pht(
       "=== Install Phabricator ===\n\n".
       "You have successfully installed Phabricator. This screen will guide ".
       "you through configuration and orientation. ".
       "These steps are optional, and you can go through them in any order. ".
       "If you want to get back to this screen later on, you can find it in ".
       "the **Config** application under **Welcome Screen**.");
 
     $setup = array();
 
     $setup[] = $this->newItem(
       $request,
       'fa-check-square-o green',
       $content);
 
     $issues_resolved = !PhabricatorSetupCheck::getOpenSetupIssueKeys();
 
     $setup_href = PhabricatorEnv::getURI('/config/issue/');
     if ($issues_resolved) {
       $content = pht(
         "=== Resolve Setup Issues ===\n\n".
         "You've resolved (or ignored) all outstanding setup issues. ".
         "You can review issues in the **Config** application, under ".
         "**[[ %s | Setup Issues ]]**.",
         $setup_href);
         $icon = 'fa-check-square-o green';
     } else {
       $content = pht(
         "=== Resolve Setup Issues ===\n\n".
         "You have some unresolved setup issues to take care of. Click ".
         "the link in the yellow banner at the top of the screen to see ".
         "them, or find them in the **Config** application under ".
         "**[[ %s | Setup Issues ]]**. ".
         "Although most setup issues should be resolved, sometimes an issue ".
         "is not applicable to an install. ".
         "If you don't intend to fix a setup issue (or don't want to fix ".
         "it for now), you can use the \"Ignore\" action to mark it as ".
         "something you don't plan to deal with.",
         $setup_href);
         $icon = 'fa-warning red';
     }
 
     $setup[] = $this->newItem(
       $request,
       $icon,
       $content);
 
     $configs = id(new PhabricatorAuthProviderConfigQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->execute();
 
     $auth_href = PhabricatorEnv::getURI('/auth/');
     $have_auth = (bool)$configs;
     if ($have_auth) {
       $content = pht(
         "=== Login and Registration ===\n\n".
         "You've configured at least one authentication provider, so users ".
         "can register or log in. ".
         "To configure more providers or adjust settings, use the ".
         "**[[ %s | Auth Application ]]**.",
         $auth_href);
         $icon = 'fa-check-square-o green';
     } else {
       $content = pht(
         "=== Login and Registration ===\n\n".
         "You haven't configured any authentication providers yet. ".
         "Authentication providers allow users to register accounts and ".
         "log in to Phabricator. You can configure Phabricator to accept ".
         "credentials like username and password, LDAP, or Google OAuth. ".
         "You can configure authentication using the ".
         "**[[ %s | Auth Application ]]**.",
         $auth_href);
         $icon = 'fa-warning red';
     }
 
     $setup[] = $this->newItem(
       $request,
       $icon,
       $content);
 
     $config_href = PhabricatorEnv::getURI('/config/');
 
     // Just load any config value at all; if one exists the install has figured
     // out how to configure things.
     $have_config = (bool)id(new PhabricatorConfigEntry())->loadAllWhere(
       '1 = 1 LIMIT 1');
 
     if ($have_config) {
       $content = pht(
         "=== Configure Phabricator Settings ===\n\n".
         "You've configured at least one setting from the web interface. ".
         "To configure more settings later, use the ".
         "**[[ %s | Config Application ]]**.",
         $config_href);
         $icon = 'fa-check-square-o green';
     } else {
       $content = pht(
         "=== Configure Phabricator Settings ===\n\n".
         'Many aspects of Phabricator are configurable. To explore and '.
         'adjust settings, use the **[[ %s | Config Application ]]**.',
         $config_href);
         $icon = 'fa-info-circle';
     }
 
     $setup[] = $this->newItem(
       $request,
       $icon,
       $content);
 
     $settings_href = PhabricatorEnv::getURI('/settings/');
     $prefs = $viewer->loadPreferences()->getPreferences();
     $have_settings = !empty($prefs);
     if ($have_settings) {
       $content = pht(
         "=== Adjust Account Settings ===\n\n".
         "You've adjusted at least one setting on your account. ".
         "To make more adjustments, visit the ".
         "**[[ %s | Settings Application ]]**.",
         $settings_href);
         $icon = 'fa-check-square-o green';
     } else {
       $content = pht(
         "=== Adjust Account Settings ===\n\n".
         'You can configure settings for your account by clicking the '.
         'wrench icon in the main menu bar, or visiting the '.
         '**[[ %s | Settings Application ]]** directly.',
         $settings_href);
         $icon = 'fa-info-circle';
     }
 
     $setup[] = $this->newItem(
       $request,
       $icon,
       $content);
 
     $dashboard_href = PhabricatorEnv::getURI('/dashboard/');
     $have_dashboard = (bool)PhabricatorDashboardInstall::getDashboard(
       $viewer,
       PhabricatorHomeApplication::DASHBOARD_DEFAULT,
       'PhabricatorHomeApplication');
     if ($have_dashboard) {
       $content = pht(
         "=== Customize Home Page ===\n\n".
         "You've installed a default dashboard to replace this welcome screen ".
         "on the home page. ".
         "You can still visit the welcome screen here at any time if you ".
         "have steps you want to complete later, or if you feel lonely. ".
         "If you've changed your mind about the dashboard you installed, ".
         "you can install a different default dashboard with the ".
         "**[[ %s | Dashboards Application ]]**.",
         $dashboard_href);
         $icon = 'fa-check-square-o green';
     } else {
       $content = pht(
         "=== Customize Home Page ===\n\n".
         "When you're done setting things up, you can create a custom ".
         "dashboard and install it. Your dashboard will replace this ".
         "welcome screen on the Phabricator home page. ".
         "Dashboards can show users the information that's most important to ".
         "your organization. You can configure them to display things like: ".
         "a custom welcome message, a feed of recent activity, or a list of ".
         "open tasks, waiting reviews, recent commits, and so on. ".
         "After you install a default dashboard, it will replace this page. ".
         "You can find this page later by visiting the **Config** ".
         "application, under **Welcome Page**. ".
         "To get started building a dashboard, use the ".
         "**[[ %s | Dashboards Application ]]**. ",
         $dashboard_href);
         $icon = 'fa-info-circle';
     }
 
     $setup[] = $this->newItem(
       $request,
       $icon,
       $content);
 
     $apps_href = PhabricatorEnv::getURI('/applications/');
     $content = pht(
       "=== Explore Applications ===\n\n".
       "Phabricator is a large suite of applications that work together to ".
       "help you develop software, manage tasks, and communicate. A few of ".
       "the most commonly used applications are pinned to the left navigation ".
       "bar by default.\n\n".
       "To explore all of the Phabricator applications, adjust settings, or ".
       "uninstall applications you don't plan to use, visit the ".
       "**[[ %s | Applications Application ]]**. You can also click the ".
       "**Applications** button in the left navigation menu, or search for an ".
       "application by name in the main menu bar. ",
       $apps_href);
 
     $explore = array();
     $explore[] = $this->newItem(
       $request,
       'fa-globe',
       $content);
 
     // TODO: Restore some sort of "Support" link here, but just nuke it for
     // now as we figure stuff out.
 
     $differential_uri = PhabricatorEnv::getURI('/differential/');
     $differential_create_uri = PhabricatorEnv::getURI(
       '/differential/diff/create/');
     $differential_all_uri = PhabricatorEnv::getURI('/differential/query/all/');
 
     $differential_user_guide = PhabricatorEnv::getDoclink(
       'Differential User Guide');
     $differential_vs_uri = PhabricatorEnv::getDoclink(
       'User Guide: Review vs Audit');
 
     $quick = array();
     $quick[] = $this->newItem(
       $request,
       'fa-gear',
       pht(
         "=== Quick Start: Code Review ===\n\n".
         "Review code with **[[ %s | Differential ]]**. ".
         "Engineers can use Differential to share, review, and approve ".
         "changes to source code. ".
         "To get started with code review:\n\n".
         "  - **[[ %s | Create a Revision ]]** //(Copy and paste a diff from ".
         "    the command line into the web UI to quickly get a feel for ".
         "    review.)//\n".
         "  - **[[ %s | View All Revisions ]]**\n\n".
         "For more information, see these articles in the documentation:\n\n".
         "  - **[[ %s | Differential User Guide ]]**, for a general overview ".
         "    of Differential.\n".
         "  - **[[ %s | User Guide: Review vs Audit ]]**, for a discussion ".
         "    of different code review workflows.",
         $differential_uri,
         $differential_create_uri,
         $differential_all_uri,
         $differential_user_guide,
         $differential_vs_uri));
 
 
     $maniphest_uri = PhabricatorEnv::getURI('/maniphest/');
     $maniphest_create_uri = PhabricatorEnv::getURI('/maniphest/task/edit/');
     $maniphest_all_uri = PhabricatorEnv::getURI('/maniphest/query/all/');
     $quick[] = $this->newItem(
       $request,
       'fa-anchor',
       pht(
         "=== Quick Start: Bugs and Tasks ===\n\n".
         "Track bugs and tasks in Phabricator with ".
         "**[[ %s | Maniphest ]]**. ".
         "Users in all roles can use Maniphest to manage current and ".
         "planned work and to track bugs and issues. ".
         "To get started with bugs and tasks:\n\n".
         "  - **[[ %s | Create a Task ]]**\n".
         "  - **[[ %s | View All Tasks ]]**\n",
         $maniphest_uri,
         $maniphest_create_uri,
         $maniphest_all_uri));
 
 
     $pholio_uri = PhabricatorEnv::getURI('/pholio/');
     $pholio_create_uri = PhabricatorEnv::getURI('/pholio/new/');
     $pholio_all_uri = PhabricatorEnv::getURI('/pholio/query/all/');
 
     $quick[] = $this->newItem(
       $request,
       'fa-camera-retro',
       pht(
         "=== Quick Start: Design Review ===\n\n".
         "Review proposed designs with **[[ %s | Pholio ]]**. ".
         "Designers can use Pholio to share images of what they're working on ".
         "and show off things they've made. ".
         "To get started with design review:\n\n".
         "  - **[[ %s | Create a Mock ]]**\n".
         "  - **[[ %s | View All Mocks ]]**",
         $pholio_uri,
         $pholio_create_uri,
         $pholio_all_uri));
 
 
     $diffusion_uri = PhabricatorEnv::getURI('/diffusion/');
     $diffusion_create_uri = PhabricatorEnv::getURI('/diffusion/create/');
     $diffusion_all_uri = PhabricatorEnv::getURI('/diffusion/query/all/');
 
     $diffusion_user_guide = PhabricatorEnv::getDoclink('Diffusion User Guide');
     $diffusion_setup_guide = PhabricatorEnv::getDoclink(
       'Diffusion User Guide: Repository Hosting');
 
     $quick[] = $this->newItem(
       $request,
       'fa-code',
       pht(
         "=== Quick Start: Repositories ===\n\n".
         "Manage and browse source code repositories with ".
         "**[[ %s | Diffusion ]]**. ".
         "Engineers can use Diffusion to browse and audit source code. ".
         "You can configure Phabricator to host repositories, or have it ".
         "track existing repositories hosted elsewhere (like GitHub, ".
         "Bitbucket, or an internal server). ".
         "To get started with repositories:\n\n".
         "  - **[[ %s | Create a New Repository ]]**\n".
         "  - **[[ %s | View All Repositories ]]**\n\n".
         "For more information, see these articles in the documentation:\n\n".
         "  - **[[ %s | Diffusion User Guide ]]**, for a general overview of ".
         "    Diffusion.\n".
         "  - **[[ %s | Diffusion User Guide: Repository Hosting ]]**, ".
         "    for instructions on configuring repository hosting.\n\n".
         "Phabricator supports Git, Mercurial and Subversion.",
         $diffusion_uri,
         $diffusion_create_uri,
         $diffusion_all_uri,
         $diffusion_user_guide,
         $diffusion_setup_guide));
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Welcome to Phabricator'));
 
     $setup_header = new PHUIRemarkupView(
       $viewer, pht('=Setup and Configuration'));
 
     $explore_header = new PHUIRemarkupView(
       $viewer, pht('=Explore Phabricator'));
 
     $quick_header = new PHUIRemarkupView(
       $viewer, pht('=Quick Start Guide'));
 
     return id(new PHUIDocumentView())
       ->setHeader($header)
       ->setFluid(true)
       ->appendChild($setup_header)
       ->appendChild($setup)
       ->appendChild($explore_header)
       ->appendChild($explore)
       ->appendChild($quick_header)
       ->appendChild($quick);
   }
 
   private function newItem(AphrontRequest $request, $icon, $content) {
     $viewer = $request->getUser();
 
     $icon = id(new PHUIIconView())
       ->setIcon($icon.' fa-2x');
 
     $content = new PHUIRemarkupView($viewer, $content);
 
     $icon = phutil_tag(
       'div',
       array(
         'class' => 'config-welcome-icon',
       ),
       $icon);
 
     $content = phutil_tag(
       'div',
       array(
         'class' => 'config-welcome-content',
       ),
       $content);
 
     $view = phutil_tag(
       'div',
       array(
         'class' => 'config-welcome-box grouped',
       ),
       array(
         $icon,
         $content,
       ));
 
     return $view;
   }
 
 }
diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php
index dbabc9b7d..6f06a36fb 100644
--- a/src/applications/conpherence/controller/ConpherenceListController.php
+++ b/src/applications/conpherence/controller/ConpherenceListController.php
@@ -1,174 +1,172 @@
 <?php
 
 final class ConpherenceListController extends ConpherenceController {
 
   const SELECTED_MODE = 'selected';
   const UNSELECTED_MODE = 'unselected';
 
   /**
    * Two main modes of operation...
    *
    * 1 - /conpherence/ - UNSELECTED_MODE
    * 2 - /conpherence/<id>/ - SELECTED_MODE
    *
    * UNSELECTED_MODE is not an Ajax request while the other two are Ajax
    * requests.
    */
   private function determineMode() {
     $request = $this->getRequest();
 
     $mode = self::UNSELECTED_MODE;
     if ($request->isAjax()) {
       $mode = self::SELECTED_MODE;
     }
 
     return $mode;
   }
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $user = $request->getUser();
     $title = pht('Conpherence');
     $conpherence = null;
 
     $limit = (ConpherenceThreadListView::SEE_MORE_LIMIT * 2) + 1;
     $all_participation = array();
 
     $mode = $this->determineMode();
     switch ($mode) {
       case self::SELECTED_MODE:
         $conpherence_id = $request->getURIData('id');
         $conpherence = id(new ConpherenceThreadQuery())
           ->setViewer($user)
           ->withIDs(array($conpherence_id))
           ->executeOne();
         if (!$conpherence) {
           return new Aphront404Response();
         }
         if ($conpherence->getTitle()) {
           $title = $conpherence->getTitle();
         }
         $cursor = $conpherence->getParticipantIfExists($user->getPHID());
         $data = $this->loadDefaultParticipation($limit);
         $all_participation = $data['all_participation'];
         if (!$cursor) {
           $menu_participation = id(new ConpherenceParticipant())
             ->makeEphemeral()
             ->setConpherencePHID($conpherence->getPHID())
             ->setParticipantPHID($user->getPHID());
         } else {
           $menu_participation = $cursor;
         }
 
         // check to see if the loaded conpherence is going to show up
         // within the SEE_MORE_LIMIT amount of conpherences.
         // If its not there, then we just pre-pend it as the "first"
         // conpherence so folks have a navigation item in the menu.
         $count = 0;
         $found = false;
         foreach ($all_participation as $phid => $curr_participation) {
           if ($conpherence->getPHID() == $phid) {
             $found = true;
             break;
           }
           $count++;
           if ($count > ConpherenceThreadListView::SEE_MORE_LIMIT) {
             break;
           }
         }
         if (!$found) {
           $all_participation =
             array($conpherence->getPHID() => $menu_participation) +
             $all_participation;
         }
         break;
       case self::UNSELECTED_MODE:
       default:
         $data = $this->loadDefaultParticipation($limit);
         $all_participation = $data['all_participation'];
         break;
     }
 
     $threads = $this->loadConpherenceThreadData(
       $all_participation);
 
     $thread_view = id(new ConpherenceThreadListView())
       ->setUser($user)
       ->setBaseURI($this->getApplicationURI())
       ->setThreads($threads);
 
     switch ($mode) {
       case self::SELECTED_MODE:
         $response = id(new AphrontAjaxResponse())->setContent($thread_view);
         break;
       case self::UNSELECTED_MODE:
       default:
         $layout = id(new ConpherenceLayoutView())
           ->setUser($user)
           ->setBaseURI($this->getApplicationURI())
           ->setThreadView($thread_view)
           ->setRole('list');
         if ($conpherence) {
           $layout->setThread($conpherence);
         } else {
           // make a dummy conpherence so we can render something
           $conpherence = ConpherenceThread::initializeNewRoom($user);
           $conpherence->attachHandles(array());
           $conpherence->attachTransactions(array());
           $conpherence->makeEphemeral();
         }
         $policy_objects = id(new PhabricatorPolicyQuery())
           ->setViewer($user)
           ->setObject($conpherence)
           ->execute();
         $layout->setHeader($this->buildHeaderPaneContent(
             $conpherence,
             $policy_objects));
-        $response = $this->buildApplicationPage(
-          $layout,
-          array(
-            'title' => $title,
-          ));
+        $response = $this->newPage()
+          ->setTitle($title)
+          ->appendChild($layout);
         break;
     }
 
     return $response;
 
   }
 
   private function loadDefaultParticipation($limit) {
     $viewer = $this->getRequest()->getUser();
 
     $all_participation = id(new ConpherenceParticipantQuery())
       ->withParticipantPHIDs(array($viewer->getPHID()))
       ->setLimit($limit)
       ->execute();
 
     return array(
       'all_participation' => $all_participation,
     );
   }
 
   private function loadConpherenceThreadData($participation) {
     $user = $this->getRequest()->getUser();
     $conpherence_phids = array_keys($participation);
     $conpherences = array();
     if ($conpherence_phids) {
       $conpherences = id(new ConpherenceThreadQuery())
         ->setViewer($user)
         ->withPHIDs($conpherence_phids)
         ->needCropPics(true)
         ->needParticipantCache(true)
         ->execute();
 
       // this will re-sort by participation data
       $conpherences = array_select_keys($conpherences, $conpherence_phids);
     }
 
     return $conpherences;
   }
 
 }
diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php
index ca2a87b6d..2f01979df 100644
--- a/src/applications/conpherence/controller/ConpherenceViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceViewController.php
@@ -1,234 +1,232 @@
 <?php
 
 final class ConpherenceViewController extends
   ConpherenceController {
 
   const OLDER_FETCH_LIMIT = 5;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   protected function buildApplicationCrumbs() {
     $crumbs = $this->buildConpherenceApplicationCrumbs();
     $crumbs->setBorder(true);
     return $crumbs;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $user = $request->getUser();
 
     $conpherence_id = $request->getURIData('id');
     if (!$conpherence_id) {
       return new Aphront404Response();
     }
     $query = id(new ConpherenceThreadQuery())
       ->setViewer($user)
       ->withIDs(array($conpherence_id))
       ->needCropPics(true)
       ->needParticipantCache(true)
       ->needTransactions(true)
       ->setTransactionLimit($this->getMainQueryLimit());
 
     $before_transaction_id = $request->getInt('oldest_transaction_id');
     $after_transaction_id = $request->getInt('newest_transaction_id');
     $old_message_id = $request->getURIData('messageID');
     if ($before_transaction_id && ($old_message_id || $after_transaction_id)) {
       throw new Aphront400Response();
     }
     if ($old_message_id && $after_transaction_id) {
       throw new Aphront400Response();
     }
 
     $marker_type = 'older';
     if ($before_transaction_id) {
       $query
         ->setBeforeTransactionID($before_transaction_id);
     }
     if ($old_message_id) {
       $marker_type = 'olderandnewer';
       $query
         ->setAfterTransactionID($old_message_id - 1);
     }
     if ($after_transaction_id) {
       $marker_type = 'newer';
       $query
         ->setAfterTransactionID($after_transaction_id);
     }
 
     $conpherence = $query->executeOne();
     if (!$conpherence) {
       return new Aphront404Response();
     }
     $this->setConpherence($conpherence);
 
     $transactions = $this->getNeededTransactions(
       $conpherence,
       $old_message_id);
     $latest_transaction = head($transactions);
     $participant = $conpherence->getParticipantIfExists($user->getPHID());
     if ($participant) {
       $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
       $participant->markUpToDate($conpherence, $latest_transaction);
       unset($write_guard);
     }
 
     $data = ConpherenceTransactionRenderer::renderTransactions(
       $user,
       $conpherence,
       $full_display = true,
       $marker_type);
     $messages = ConpherenceTransactionRenderer::renderMessagePaneContent(
       $data['transactions'],
       $data['oldest_transaction_id'],
       $data['newest_transaction_id']);
     if ($before_transaction_id || $after_transaction_id) {
       $header = null;
       $form = null;
       $content = array('transactions' => $messages);
     } else {
       $policy_objects = id(new PhabricatorPolicyQuery())
         ->setViewer($user)
         ->setObject($conpherence)
         ->execute();
       $header = $this->buildHeaderPaneContent($conpherence, $policy_objects);
       $form = $this->renderFormContent();
       $content = array(
         'header' => $header,
         'transactions' => $messages,
         'form' => $form,
       );
     }
 
     $d_data = $conpherence->getDisplayData($user);
     $content['title'] = $title = $d_data['title'];
 
     if ($request->isAjax()) {
       $dropdown_query = id(new AphlictDropdownDataQuery())
         ->setViewer($user);
       $dropdown_query->execute();
       $content['threadID'] = $conpherence->getID();
       $content['threadPHID'] = $conpherence->getPHID();
       $content['latestTransactionID'] = $data['latest_transaction_id'];
       $content['canEdit'] = PhabricatorPolicyFilter::hasCapability(
         $user,
         $conpherence,
         PhabricatorPolicyCapability::CAN_EDIT);
       $content['aphlictDropdownData'] = array(
         $dropdown_query->getNotificationData(),
         $dropdown_query->getConpherenceData(),
       );
       return id(new AphrontAjaxResponse())->setContent($content);
     }
 
     $layout = id(new ConpherenceLayoutView())
       ->setUser($user)
       ->setBaseURI($this->getApplicationURI())
       ->setThread($conpherence)
       ->setHeader($header)
       ->setMessages($messages)
       ->setReplyForm($form)
       ->setLatestTransactionID($data['latest_transaction_id'])
       ->setRole('thread');
 
-   return $this->buildApplicationPage(
-      $layout,
-      array(
-        'title' => $title,
-        'pageObjects' => array($conpherence->getPHID()),
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setPageObjectPHIDs(array($conpherence->getPHID()))
+      ->appendChild($layout);
   }
 
   private function renderFormContent() {
 
     $conpherence = $this->getConpherence();
     $user = $this->getRequest()->getUser();
     $can_join = PhabricatorPolicyFilter::hasCapability(
       $user,
       $conpherence,
       PhabricatorPolicyCapability::CAN_JOIN);
     $participating = $conpherence->getParticipantIfExists($user->getPHID());
     if (!$can_join && !$participating && $user->isLoggedIn()) {
       return null;
     }
     $draft = PhabricatorDraft::newFromUserAndKey(
       $user,
       $conpherence->getPHID());
     if ($participating) {
       $action = ConpherenceUpdateActions::MESSAGE;
       $button_text = pht('Send');
     } else if ($user->isLoggedIn()) {
       $action = ConpherenceUpdateActions::JOIN_ROOM;
       $button_text = pht('Join');
     } else {
       // user not logged in so give them a login button.
       $login_href = id(new PhutilURI('/auth/start/'))
         ->setQueryParam('next', '/'.$conpherence->getMonogram());
       return id(new PHUIFormLayoutView())
         ->addClass('login-to-participate')
         ->appendChild(
           id(new PHUIButtonView())
           ->setTag('a')
           ->setText(pht('Login to Participate'))
           ->setHref((string)$login_href));
     }
     $update_uri = $this->getApplicationURI('update/'.$conpherence->getID().'/');
 
     $this->initBehavior('conpherence-pontificate');
 
     $form =
       id(new AphrontFormView())
       ->setUser($user)
       ->setAction($update_uri)
       ->addSigil('conpherence-pontificate')
       ->setWorkflow(true)
       ->addHiddenInput('action', $action)
       ->appendChild(
         id(new PhabricatorRemarkupControl())
         ->setUser($user)
         ->setName('text')
         ->setValue($draft->getDraft()))
       ->appendChild(
         id(new AphrontFormSubmitControl())
         ->setValue($button_text))
       ->render();
 
     return $form;
   }
 
   private function getNeededTransactions(
     ConpherenceThread $conpherence,
     $message_id) {
 
     if ($message_id) {
       $newer_transactions = $conpherence->getTransactions();
       $query = id(new ConpherenceTransactionQuery())
         ->setViewer($this->getRequest()->getUser())
         ->withObjectPHIDs(array($conpherence->getPHID()))
         ->setAfterID($message_id)
         ->needHandles(true)
         ->setLimit(self::OLDER_FETCH_LIMIT);
       $older_transactions = $query->execute();
       $handles = array();
       foreach ($older_transactions as $transaction) {
         $handles += $transaction->getHandles();
       }
       $conpherence->attachHandles($conpherence->getHandles() + $handles);
       $transactions = array_merge($newer_transactions, $older_transactions);
       $conpherence->attachTransactions($transactions);
     } else {
       $transactions = $conpherence->getTransactions();
     }
 
     return $transactions;
   }
 
   private function getMainQueryLimit() {
     $request = $this->getRequest();
     $base_limit = ConpherenceThreadQuery::TRANSACTION_LIMIT;
     if ($request->getURIData('messageID')) {
       $base_limit = $base_limit - self::OLDER_FETCH_LIMIT;
     }
     return $base_limit;
   }
 }
diff --git a/src/applications/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php
index d6c62d7e1..a446c88a8 100644
--- a/src/applications/countdown/application/PhabricatorCountdownApplication.php
+++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php
@@ -1,74 +1,72 @@
 <?php
 
 final class PhabricatorCountdownApplication extends PhabricatorApplication {
 
   public function getBaseURI() {
     return '/countdown/';
   }
 
   public function getIcon() {
     return 'fa-rocket';
   }
 
   public function getName() {
     return pht('Countdown');
   }
 
   public function getShortDescription() {
     return pht('Countdown to Events');
   }
 
   public function getTitleGlyph() {
     return "\xE2\x9A\xB2";
   }
 
   public function getFlavorText() {
     return pht('Utilize the full capabilities of your ALU.');
   }
 
   public function getApplicationGroup() {
     return self::GROUP_UTILITIES;
   }
 
   public function getRemarkupRules() {
     return array(
       new PhabricatorCountdownRemarkupRule(),
     );
   }
 
   public function getRoutes() {
     return array(
       '/C(?P<id>[1-9]\d*)' => 'PhabricatorCountdownViewController',
       '/countdown/' => array(
         '(?:query/(?P<queryKey>[^/]+)/)?'
           => 'PhabricatorCountdownListController',
         '(?P<id>[1-9]\d*)/'
           => 'PhabricatorCountdownViewController',
         'comment/(?P<id>[1-9]\d*)/'
           => 'PhabricatorCountdownCommentController',
-        'edit/(?:(?P<id>[1-9]\d*)/)?'
-          => 'PhabricatorCountdownEditController',
-        'create/'
+        $this->getEditRoutePattern('edit/')
           => 'PhabricatorCountdownEditController',
         'delete/(?P<id>[1-9]\d*)/'
           => 'PhabricatorCountdownDeleteController',
       ),
     );
   }
 
   protected function getCustomCapabilities() {
     return array(
       PhabricatorCountdownDefaultViewCapability::CAPABILITY => array(
         'caption' => pht('Default view policy for new countdowns.'),
         'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST,
         'capability' => PhabricatorPolicyCapability::CAN_VIEW,
       ),
       PhabricatorCountdownDefaultEditCapability::CAPABILITY => array(
         'caption' => pht('Default edit policy for new countdowns.'),
         'template' => PhabricatorCountdownCountdownPHIDType::TYPECONST,
         'capability' => PhabricatorPolicyCapability::CAN_EDIT,
       ),
     );
   }
 
 }
diff --git a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php b/src/applications/countdown/controller/PhabricatorCountdownCommentController.php
deleted file mode 100644
index 03b200128..000000000
--- a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-final class PhabricatorCountdownCommentController
-  extends PhabricatorCountdownController {
-
-  public function handleRequest(AphrontRequest $request) {
-    $viewer = $request->getViewer();
-    $id = $request->getURIData('id');
-
-    if (!$request->isFormPost()) {
-      return new Aphront400Response();
-    }
-
-    $countdown = id(new PhabricatorCountdownQuery())
-      ->setViewer($viewer)
-      ->withIDs(array($id))
-      ->executeOne();
-    if (!$countdown) {
-      return new Aphront404Response();
-    }
-
-    $is_preview = $request->isPreviewRequest();
-    $draft = PhabricatorDraft::buildFromRequest($request);
-
-    $view_uri = '/'.$countdown->getMonogram();
-
-    $xactions = array();
-    $xactions[] = id(new PhabricatorCountdownTransaction())
-      ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
-      ->attachComment(
-        id(new PhabricatorCountdownTransactionComment())
-          ->setContent($request->getStr('comment')));
-
-    $editor = id(new PhabricatorCountdownEditor())
-      ->setActor($viewer)
-      ->setContinueOnNoEffect($request->isContinueRequest())
-      ->setContentSourceFromRequest($request)
-      ->setIsPreview($is_preview);
-
-    try {
-      $xactions = $editor->applyTransactions($countdown, $xactions);
-    } catch (PhabricatorApplicationTransactionNoEffectException $ex) {
-      return id(new PhabricatorApplicationTransactionNoEffectResponse())
-        ->setCancelURI($view_uri)
-        ->setException($ex);
-    }
-
-    if ($draft) {
-      $draft->replaceOrDelete();
-    }
-
-    if ($request->isAjax() && $is_preview) {
-      return id(new PhabricatorApplicationTransactionResponse())
-        ->setViewer($viewer)
-        ->setTransactions($xactions)
-        ->setIsPreview($is_preview);
-    } else {
-      return id(new AphrontRedirectResponse())
-        ->setURI($view_uri);
-    }
-  }
-
-}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php
index 37b0e49a6..4d09d7ed5 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownController.php
@@ -1,22 +1,11 @@
 <?php
 
 abstract class PhabricatorCountdownController extends PhabricatorController {
 
   public function buildApplicationMenu() {
     return $this->newApplicationMenu()
       ->setSearchEngine(new PhabricatorCountdownSearchEngine());
   }
 
-  protected function buildApplicationCrumbs() {
-    $crumbs = parent::buildApplicationCrumbs();
-
-    $crumbs->addAction(
-      id(new PHUIListItemView())
-        ->setName(pht('Create Countdown'))
-        ->setHref($this->getApplicationURI('create/'))
-        ->setIcon('fa-plus-square'));
-
-    return $crumbs;
-  }
 
 }
diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
index ea89c1591..7bdd4236b 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
@@ -1,208 +1,12 @@
 <?php
 
 final class PhabricatorCountdownEditController
   extends PhabricatorCountdownController {
 
   public function handleRequest(AphrontRequest $request) {
-    $viewer = $request->getViewer();
-    $id = $request->getURIData('id');
-
-    if ($id) {
-      $countdown = id(new PhabricatorCountdownQuery())
-        ->setViewer($viewer)
-        ->withIDs(array($id))
-        ->requireCapabilities(
-          array(
-            PhabricatorPolicyCapability::CAN_VIEW,
-            PhabricatorPolicyCapability::CAN_EDIT,
-          ))
-        ->executeOne();
-      if (!$countdown) {
-        return new Aphront404Response();
-      }
-      $date_value = AphrontFormDateControlValue::newFromEpoch(
-        $viewer,
-        $countdown->getEpoch());
-      $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
-        $countdown->getPHID(),
-        PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
-      $v_projects = array_reverse($v_projects);
-      $title = pht('Edit Countdown: %s', $countdown->getTitle());
-    } else {
-      $title = pht('Create Countdown');
-      $countdown = PhabricatorCountdown::initializeNewCountdown($viewer);
-      $date_value = AphrontFormDateControlValue::newFromEpoch(
-        $viewer, PhabricatorTime::getNow());
-      $v_projects = array();
-    }
-
-    $errors = array();
-    $e_text = true;
-    $e_epoch = null;
-
-    $v_text = $countdown->getTitle();
-    $v_desc = $countdown->getDescription();
-    $v_space = $countdown->getSpacePHID();
-    $v_view = $countdown->getViewPolicy();
-    $v_edit = $countdown->getEditPolicy();
-
-    if ($request->isFormPost()) {
-      $v_text = $request->getStr('title');
-      $v_desc = $request->getStr('description');
-      $v_space = $request->getStr('spacePHID');
-      $date_value = AphrontFormDateControlValue::newFromRequest(
-        $request,
-        'epoch');
-      $v_view = $request->getStr('viewPolicy');
-      $v_edit = $request->getStr('editPolicy');
-      $v_projects = $request->getArr('projects');
-
-      $type_title = PhabricatorCountdownTransaction::TYPE_TITLE;
-      $type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH;
-      $type_description = PhabricatorCountdownTransaction::TYPE_DESCRIPTION;
-      $type_space = PhabricatorTransactions::TYPE_SPACE;
-      $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
-      $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
-
-      $xactions = array();
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_title)
-        ->setNewValue($v_text);
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_epoch)
-        ->setNewValue($date_value);
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_description)
-        ->setNewValue($v_desc);
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_space)
-        ->setNewValue($v_space);
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_view)
-        ->setNewValue($v_view);
-
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType($type_edit)
-        ->setNewValue($v_edit);
-
-      $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
-      $xactions[] = id(new PhabricatorCountdownTransaction())
-        ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
-        ->setMetadataValue('edge:type', $proj_edge_type)
-        ->setNewValue(array('=' => array_fuse($v_projects)));
-
-      $editor = id(new PhabricatorCountdownEditor())
-        ->setActor($viewer)
-        ->setContentSourceFromRequest($request)
-        ->setContinueOnNoEffect(true);
-
-      try {
-        $editor->applyTransactions($countdown, $xactions);
-
-        return id(new AphrontRedirectResponse())
-          ->setURI('/'.$countdown->getMonogram());
-      } catch (PhabricatorApplicationTransactionValidationException $ex) {
-        $validation_exception = $ex;
-
-        $e_title = $ex->getShortMessage($type_title);
-        $e_epoch = $ex->getShortMessage($type_epoch);
-      }
-
-    }
-
-    $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->setBorder(true);
-
-    $cancel_uri = '/countdown/';
-    if ($countdown->getID()) {
-      $cancel_uri = '/countdown/'.$countdown->getID().'/';
-      $crumbs->addTextCrumb('C'.$countdown->getID(), $cancel_uri);
-      $crumbs->addTextCrumb(pht('Edit'));
-      $submit_label = pht('Save Changes');
-      $header_icon = 'fa-pencil';
-    } else {
-      $crumbs->addTextCrumb(pht('Create Countdown'));
-      $submit_label = pht('Create Countdown');
-      $header_icon = 'fa-plus-square';
-    }
-
-    $policies = id(new PhabricatorPolicyQuery())
-      ->setViewer($viewer)
-      ->setObject($countdown)
-      ->execute();
-
-    $form = id(new AphrontFormView())
-      ->setUser($viewer)
-      ->setAction($request->getRequestURI()->getPath())
-      ->appendChild(
-        id(new AphrontFormTextControl())
-          ->setLabel(pht('Title'))
-          ->setValue($v_text)
-          ->setName('title')
-          ->setError($e_text))
-      ->appendControl(
-        id(new AphrontFormDateControl())
-          ->setName('epoch')
-          ->setLabel(pht('End Date'))
-          ->setError($e_epoch)
-          ->setValue($date_value))
-      ->appendControl(
-        id(new PhabricatorRemarkupControl())
-          ->setName('description')
-          ->setLabel(pht('Description'))
-          ->setValue($v_desc))
-      ->appendControl(
-        id(new AphrontFormPolicyControl())
-          ->setName('viewPolicy')
-          ->setPolicyObject($countdown)
-          ->setPolicies($policies)
-          ->setSpacePHID($v_space)
-          ->setValue($v_view)
-          ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
-      ->appendControl(
-        id(new AphrontFormPolicyControl())
-          ->setName('editPolicy')
-          ->setPolicyObject($countdown)
-          ->setPolicies($policies)
-          ->setValue($v_edit)
-          ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
-      ->appendControl(
-        id(new AphrontFormTokenizerControl())
-          ->setLabel(pht('Projects'))
-          ->setName('projects')
-          ->setValue($v_projects)
-          ->setDatasource(new PhabricatorProjectDatasource()))
-      ->appendChild(
-        id(new AphrontFormSubmitControl())
-          ->addCancelButton($cancel_uri)
-          ->setValue($submit_label));
-
-    $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Countdown'))
-      ->setFormErrors($errors)
-      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
-      ->setForm($form);
-
-    $header = id(new PHUIHeaderView())
-      ->setHeader($title)
-      ->setHeaderIcon($header_icon);
-
-    $view = id(new PHUITwoColumnView())
-      ->setHeader($header)
-      ->setFooter($form_box);
-
-    return $this->newPage()
-      ->setTitle($title)
-      ->setCrumbs($crumbs)
-      ->appendChild(
-        array(
-          $view,
-        ));
+    return id(new PhabricatorCountdownEditEngine())
+      ->setController($this)
+      ->buildResponse();
   }
 
 }
diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php
index 382f1a030..b06cfd0e7 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownListController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php
@@ -1,16 +1,26 @@
 <?php
 
 final class PhabricatorCountdownListController
   extends PhabricatorCountdownController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     return id(new PhabricatorCountdownSearchEngine())
       ->setController($this)
       ->buildResponse();
   }
 
+  protected function buildApplicationCrumbs() {
+    $crumbs = parent::buildApplicationCrumbs();
+
+    id(new PhabricatorCountdownEditEngine())
+      ->setViewer($this->getViewer())
+      ->addActionToCrumbs($crumbs);
+
+    return $crumbs;
+  }
+
 }
diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
index 6e259df55..56911bf43 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
@@ -1,159 +1,141 @@
 <?php
 
 final class PhabricatorCountdownViewController
   extends PhabricatorCountdownController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $countdown = id(new PhabricatorCountdownQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$countdown) {
       return new Aphront404Response();
     }
 
     $countdown_view = id(new PhabricatorCountdownView())
       ->setUser($viewer)
       ->setCountdown($countdown);
 
     $id = $countdown->getID();
     $title = $countdown->getTitle();
 
     $crumbs = $this
       ->buildApplicationCrumbs()
       ->addTextCrumb("C{$id}")
       ->setBorder(true);
 
     $epoch = $countdown->getEpoch();
     if ($epoch >= PhabricatorTime::getNow()) {
       $icon = 'fa-clock-o';
       $color = '';
       $status = pht('Running');
     } else {
       $icon = 'fa-check-square-o';
       $color = 'dark';
       $status = pht('Launched');
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
       ->setPolicyObject($countdown)
       ->setStatus($icon, $color, $status)
       ->setHeaderIcon('fa-rocket');
 
     $curtain = $this->buildCurtain($countdown);
     $subheader = $this->buildSubheaderView($countdown);
 
     $timeline = $this->buildTransactionTimeline(
       $countdown,
       new PhabricatorCountdownTransactionQuery());
-    $add_comment = $this->buildCommentForm($countdown);
+
+    $comment_view = id(new PhabricatorCountdownEditEngine())
+      ->setViewer($viewer)
+      ->buildEditEngineCommentView($countdown);
 
     $content = array(
       $countdown_view,
       $timeline,
-      $add_comment,
+      $comment_view,
     );
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
       ->setMainColumn($content);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(
         array(
           $countdown->getPHID(),
         ))
       ->appendChild($view);
   }
 
   private function buildCurtain(PhabricatorCountdown $countdown) {
     $viewer = $this->getViewer();
 
     $id = $countdown->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $countdown,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($countdown);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Countdown'))
         ->setHref($this->getApplicationURI("edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-times')
         ->setName(pht('Delete Countdown'))
         ->setHref($this->getApplicationURI("delete/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
     return $curtain;
   }
 
   private function buildSubheaderView(
     PhabricatorCountdown $countdown) {
     $viewer = $this->getViewer();
 
     $author = $viewer->renderHandle($countdown->getAuthorPHID())->render();
     $date = phabricator_datetime($countdown->getDateCreated(), $viewer);
     $author = phutil_tag('strong', array(), $author);
 
     $person = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($countdown->getAuthorPHID()))
       ->needProfileImage(true)
       ->executeOne();
 
     $image_uri = $person->getProfileImageURI();
     $image_href = '/p/'.$person->getUsername();
 
     $content = pht('Authored by %s on %s.', $author, $date);
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
   }
 
-  private function buildCommentForm(PhabricatorCountdown $countdown) {
-    $viewer = $this->getViewer();
-
-    $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
-
-    $add_comment_header = $is_serious
-      ? pht('Add Comment')
-      : pht('Last Words');
-
-    $draft = PhabricatorDraft::newFromUserAndKey(
-      $viewer, $countdown->getPHID());
-
-    return id(new PhabricatorApplicationTransactionCommentView())
-      ->setUser($viewer)
-      ->setObjectPHID($countdown->getPHID())
-      ->setDraft($draft)
-      ->setHeaderText($add_comment_header)
-      ->setAction($this->getApplicationURI('/comment/'.$countdown->getID().'/'))
-      ->setSubmitButtonName(pht('Add Comment'));
-  }
-
 }
diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php
new file mode 100644
index 000000000..c1d5f6753
--- /dev/null
+++ b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php
@@ -0,0 +1,108 @@
+<?php
+
+final class PhabricatorCountdownEditEngine
+  extends PhabricatorEditEngine {
+
+  const ENGINECONST = 'countdown.countdown';
+
+  public function isEngineConfigurable() {
+    return false;
+  }
+
+  public function getEngineName() {
+    return pht('Countdowns');
+  }
+
+  public function getSummaryHeader() {
+    return pht('Edit Countdowns');
+  }
+
+  public function getSummaryText() {
+    return pht('Creates and edits countdowns.');
+  }
+
+  public function getEngineApplicationClass() {
+    return 'PhabricatorCountdownApplication';
+  }
+
+  protected function newEditableObject() {
+    return PhabricatorCountdown::initializeNewCountdown(
+      $this->getViewer());
+  }
+
+  protected function newObjectQuery() {
+    return id(new PhabricatorCountdownQuery());
+  }
+
+  protected function getObjectCreateTitleText($object) {
+    return pht('Create Countdown');
+  }
+
+  protected function getObjectCreateButtonText($object) {
+    return pht('Create Countdown');
+  }
+
+  protected function getObjectEditTitleText($object) {
+    return pht('Edit Countdown: %s', $object->getTitle());
+  }
+
+  protected function getObjectEditShortText($object) {
+    return pht('Edit Countdown');
+  }
+
+  protected function getObjectCreateShortText() {
+    return pht('Create Countdown');
+  }
+
+  protected function getObjectName() {
+    return pht('Countdown');
+  }
+
+  protected function getCommentViewHeaderText($object) {
+    return pht('Last Words');
+  }
+
+  protected function getCommentViewButtonText($object) {
+    return pht('Contemplate Infinity');
+  }
+
+  protected function getObjectViewURI($object) {
+    return $object->getURI();
+  }
+
+  protected function buildCustomEditFields($object) {
+    $epoch_value = $object->getEpoch();
+    if ($epoch_value === null) {
+      $epoch_value = PhabricatorTime::getNow();
+    }
+
+    return array(
+      id(new PhabricatorTextEditField())
+        ->setKey('name')
+        ->setLabel(pht('Name'))
+        ->setIsRequired(true)
+        ->setTransactionType(PhabricatorCountdownTransaction::TYPE_TITLE)
+        ->setDescription(pht('The countdown name.'))
+        ->setConduitDescription(pht('Rename the countdown.'))
+        ->setConduitTypeDescription(pht('New countdown name.'))
+        ->setValue($object->getTitle()),
+      id(new PhabricatorEpochEditField())
+        ->setKey('epoch')
+        ->setLabel(pht('End Date'))
+        ->setTransactionType(PhabricatorCountdownTransaction::TYPE_EPOCH)
+        ->setDescription(pht('Date when the countdown ends.'))
+        ->setConduitDescription(pht('Change the end date of the countdown.'))
+        ->setConduitTypeDescription(pht('New countdown end date.'))
+        ->setValue($epoch_value),
+      id(new PhabricatorRemarkupEditField())
+        ->setKey('description')
+        ->setLabel(pht('Description'))
+        ->setTransactionType(PhabricatorCountdownTransaction::TYPE_DESCRIPTION)
+        ->setDescription(pht('Description of the countdown.'))
+        ->setConduitDescription(pht('Change the countdown description.'))
+        ->setConduitTypeDescription(pht('New description.'))
+        ->setValue($object->getDescription()),
+    );
+  }
+
+}
diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php
index e1eddf227..37f23f6bd 100644
--- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php
+++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php
@@ -1,213 +1,221 @@
 <?php
 
 final class PhabricatorCountdownEditor
   extends PhabricatorApplicationTransactionEditor {
 
   public function getEditorApplicationClass() {
     return 'PhabricatorCountdownApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('Countdown');
   }
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $types[] = PhabricatorCountdownTransaction::TYPE_TITLE;
     $types[] = PhabricatorCountdownTransaction::TYPE_EPOCH;
     $types[] = PhabricatorCountdownTransaction::TYPE_DESCRIPTION;
 
     $types[] = PhabricatorTransactions::TYPE_EDGE;
     $types[] = PhabricatorTransactions::TYPE_SPACE;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
     $types[] = PhabricatorTransactions::TYPE_COMMENT;
 
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorCountdownTransaction::TYPE_TITLE:
         return $object->getTitle();
       case PhabricatorCountdownTransaction::TYPE_DESCRIPTION:
         return $object->getDescription();
       case PhabricatorCountdownTransaction::TYPE_EPOCH:
         return $object->getEpoch();
     }
 
     return parent::getCustomTransactionOldValue($object, $xaction);
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorCountdownTransaction::TYPE_TITLE:
         return $xaction->getNewValue();
       case PhabricatorCountdownTransaction::TYPE_DESCRIPTION:
         return $xaction->getNewValue();
       case PhabricatorCountdownTransaction::TYPE_EPOCH:
         return $xaction->getNewValue()->getEpoch();
     }
 
     return parent::getCustomTransactionNewValue($object, $xaction);
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $type = $xaction->getTransactionType();
     switch ($type) {
       case PhabricatorCountdownTransaction::TYPE_TITLE:
         $object->setTitle($xaction->getNewValue());
         return;
       case PhabricatorCountdownTransaction::TYPE_DESCRIPTION:
         $object->setDescription($xaction->getNewValue());
         return;
       case PhabricatorCountdownTransaction::TYPE_EPOCH:
         $object->setEpoch($xaction->getNewValue());
         return;
     }
 
     return parent::applyCustomInternalTransaction($object, $xaction);
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $type = $xaction->getTransactionType();
     switch ($type) {
       case PhabricatorCountdownTransaction::TYPE_TITLE:
         return;
       case PhabricatorCountdownTransaction::TYPE_DESCRIPTION:
         return;
       case PhabricatorCountdownTransaction::TYPE_EPOCH:
         return;
     }
 
     return parent::applyCustomExternalTransaction($object, $xaction);
   }
 
   protected function validateTransaction(
     PhabricatorLiskDAO $object,
     $type,
     array $xactions) {
 
     $errors = parent::validateTransaction($object, $type, $xactions);
 
     switch ($type) {
       case PhabricatorCountdownTransaction::TYPE_TITLE:
         $missing = $this->validateIsEmptyTextField(
           $object->getTitle(),
           $xactions);
 
         if ($missing) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Required'),
             pht('You must give the countdown a name.'),
             nonempty(last($xactions), null));
 
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         }
       break;
       case PhabricatorCountdownTransaction::TYPE_EPOCH:
-        $date_value = AphrontFormDateControlValue::newFromEpoch(
-          $this->requireActor(),
-          $object->getEpoch());
-        if (!$date_value->isValid()) {
+        if (!$object->getEpoch() && !$xactions) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
-            pht('Invalid'),
-            pht('You must give the countdown a valid end date.'),
-            nonempty(last($xactions), null));
-
+            pht('Required'),
+            pht('You must give the countdown an end date.'),
+            null);
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         }
+
+        foreach ($xactions as $xaction) {
+          $value = $xaction->getNewValue();
+          if (!$value->isValid()) {
+            $error = new PhabricatorApplicationTransactionValidationError(
+              $type,
+              pht('Invalid'),
+              pht('You must give the countdown a valid end date.'),
+              $xaction);
+            $errors[] = $error;
+          }
+        }
       break;
     }
 
     return $errors;
   }
 
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   public function getMailTagsMap() {
     return array(
       PhabricatorCountdownTransaction::MAILTAG_DETAILS =>
         pht('Someone changes the countdown details.'),
       PhabricatorCountdownTransaction::MAILTAG_COMMENT =>
         pht('Someone comments on a countdown.'),
       PhabricatorCountdownTransaction::MAILTAG_OTHER =>
         pht('Other countdown activity not listed above occurs.'),
     );
   }
 
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     $monogram = $object->getMonogram();
     $name = $object->getTitle();
 
     return id(new PhabricatorMetaMTAMail())
       ->setSubject("{$monogram}: {$name}")
       ->addHeader('Thread-Topic', $monogram);
   }
 
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $body = parent::buildMailBody($object, $xactions);
     $description = $object->getDescription();
 
     if (strlen($description)) {
       $body->addRemarkupSection(
         pht('COUNTDOWN DESCRIPTION'),
         $object->getDescription());
     }
 
     $body->addLinkSection(
       pht('COUNTDOWN DETAIL'),
       PhabricatorEnv::getProductionURI('/'.$object->getMonogram()));
 
     return $body;
   }
 
   protected function getMailTo(PhabricatorLiskDAO $object) {
     return array(
       $object->getAuthorPHID(),
       $this->requireActor()->getPHID(),
     );
   }
   protected function getMailSubjectPrefix() {
     return '[Countdown]';
   }
 
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new PhabricatorCountdownReplyHandler())
       ->setMailReceiver($object);
   }
 
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function supportsSearch() {
     return true;
   }
 
 }
diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php
index 17fde126f..e6c410ee4 100644
--- a/src/applications/countdown/query/PhabricatorCountdownQuery.php
+++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php
@@ -1,77 +1,108 @@
 <?php
 
 final class PhabricatorCountdownQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $authorPHIDs;
   private $upcoming;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withAuthorPHIDs(array $author_phids) {
     $this->authorPHIDs = $author_phids;
     return $this;
   }
 
   public function withUpcoming() {
     $this->upcoming = true;
     return $this;
   }
 
   protected function loadPage() {
     return $this->loadStandardPage($this->newResultObject());
   }
 
   public function newResultObject() {
     return new PhabricatorCountdown();
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->authorPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'authorPHID in (%Ls)',
         $this->authorPHIDs);
     }
 
     if ($this->upcoming !== null) {
       $where[] = qsprintf(
         $conn,
         'epoch >= %d',
         PhabricatorTime::getNow());
     }
 
     return $where;
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorCountdownApplication';
   }
 
+  public function getBuiltinOrders() {
+    return array(
+      'ending' => array(
+        'vector' => array('-epoch', '-id'),
+        'name' => pht('End Date (Past to Future)'),
+      ),
+      'unending' => array(
+        'vector' => array('epoch', 'id'),
+        'name' => pht('End Date (Future to Past)'),
+      ),
+    ) + parent::getBuiltinOrders();
+  }
+
+  public function getOrderableColumns() {
+    return array(
+      'epoch' => array(
+        'table' => $this->getPrimaryTableAlias(),
+        'column' => 'epoch',
+        'type' => 'int',
+      ),
+    ) + parent::getOrderableColumns();
+  }
+
+  protected function getPagingValueMap($cursor, array $keys) {
+    $countdown = $this->loadCursorObject($cursor);
+    return array(
+      'epoch' => $countdown->getEpoch(),
+      'id' => $countdown->getID(),
+    );
+  }
+
 }
diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
index 79b329385..93aec9a03 100644
--- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
+++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php
@@ -1,168 +1,166 @@
 <?php
 
 final class PhabricatorCountdownSearchEngine
   extends PhabricatorApplicationSearchEngine {
 
   public function getResultTypeDescription() {
     return pht('Countdowns');
   }
 
   public function getApplicationClassName() {
     return 'PhabricatorCountdownApplication';
   }
 
   public function newQuery() {
     return new PhabricatorCountdownQuery();
   }
 
   protected function buildQueryFromParameters(array $map) {
     $query = $this->newQuery();
 
     if ($map['authorPHIDs']) {
       $query->withAuthorPHIDs($map['authorPHIDs']);
     }
 
     if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
       $query->withUpcoming();
     }
 
     return $query;
   }
 
   protected function buildCustomSearchFields() {
-
     return array(
-        id(new PhabricatorUsersSearchField())
-          ->setLabel(pht('Authors'))
-          ->setKey('authorPHIDs')
-          ->setAliases(array('author', 'authors')),
-
-        id(new PhabricatorSearchCheckboxesField())
-          ->setKey('upcoming')
-          ->setOptions(array(
+      id(new PhabricatorUsersSearchField())
+        ->setLabel(pht('Authors'))
+        ->setKey('authorPHIDs')
+        ->setAliases(array('author', 'authors')),
+      id(new PhabricatorSearchCheckboxesField())
+        ->setKey('upcoming')
+        ->setOptions(
+          array(
             'upcoming' => pht('Show only upcoming countdowns.'),
           )),
-      );
-
+    );
   }
 
   protected function getURI($path) {
     return '/countdown/'.$path;
   }
 
   protected function getBuiltinQueryNames() {
     $names = array(
       'upcoming' => pht('Upcoming'),
       'all' => pht('All'),
     );
 
     if ($this->requireViewer()->getPHID()) {
       $names['authored'] = pht('Authored');
     }
 
     return $names;
   }
 
   public function buildSavedQueryFromBuiltin($query_key) {
     $query = $this->newSavedQuery();
     $query->setQueryKey($query_key);
 
     switch ($query_key) {
       case 'all':
         return $query;
       case 'authored':
         return $query->setParameter(
           'authorPHIDs',
           array($this->requireViewer()->getPHID()));
       case 'upcoming':
         return $query->setParameter('upcoming', array('upcoming'));
     }
 
     return parent::buildSavedQueryFromBuiltin($query_key);
   }
 
   protected function getRequiredHandlePHIDsForResultList(
     array $countdowns,
     PhabricatorSavedQuery $query) {
 
     return mpull($countdowns, 'getAuthorPHID');
   }
 
   protected function renderResultList(
     array $countdowns,
     PhabricatorSavedQuery $query,
     array $handles) {
 
     assert_instances_of($countdowns, 'PhabricatorCountdown');
 
     $viewer = $this->requireViewer();
 
     $list = new PHUIObjectItemListView();
     $list->setUser($viewer);
     foreach ($countdowns as $countdown) {
       $id = $countdown->getID();
       $ended = false;
       $epoch = $countdown->getEpoch();
       if ($epoch <= PhabricatorTime::getNow()) {
         $ended = true;
       }
 
       $item = id(new PHUIObjectItemView())
         ->setUser($viewer)
         ->setObject($countdown)
         ->setObjectName("C{$id}")
         ->setHeader($countdown->getTitle())
         ->setHref($this->getApplicationURI("{$id}/"))
         ->addByline(
           pht(
             'Created by %s',
             $handles[$countdown->getAuthorPHID()]->renderLink()));
 
       if ($ended) {
         $item->addAttribute(
           pht('Launched on %s', phabricator_datetime($epoch, $viewer)));
         $item->setDisabled(true);
       } else {
         $time_left = ($epoch - PhabricatorTime::getNow());
         $num = round($time_left / (60 * 60 * 24));
         $noun = pht('Days');
         if ($num < 1) {
           $num = round($time_left / (60 * 60), 1);
           $noun = pht('Hours');
         }
         $item->setCountdown($num, $noun);
         $item->addAttribute(
           phabricator_datetime($epoch, $viewer));
       }
 
       $list->addItem($item);
     }
 
     $result = new PhabricatorApplicationSearchResultView();
     $result->setObjectList($list);
     $result->setNoDataString(pht('No countdowns found.'));
 
     return $result;
   }
 
   protected function getNewUserBody() {
     $create_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Create a Countdown'))
       ->setHref('/countdown/create/')
       ->setColor(PHUIButtonView::GREEN);
 
     $icon = $this->getApplication()->getIcon();
     $app_name =  $this->getApplication()->getName();
     $view = id(new PHUIBigInfoView())
       ->setIcon($icon)
       ->setTitle(pht('Welcome to %s', $app_name))
       ->setDescription(
         pht('Keep track of upcoming launch dates with '.
             'embeddable counters.'))
       ->addAction($create_button);
 
       return $view;
   }
 
 }
diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php
index 52a395f0c..ad9b1b33a 100644
--- a/src/applications/countdown/storage/PhabricatorCountdown.php
+++ b/src/applications/countdown/storage/PhabricatorCountdown.php
@@ -1,137 +1,152 @@
 <?php
 
 final class PhabricatorCountdown extends PhabricatorCountdownDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorFlaggableInterface,
     PhabricatorSubscribableInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorTokenReceiverInterface,
     PhabricatorSpacesInterface,
     PhabricatorProjectInterface {
 
   protected $title;
   protected $authorPHID;
   protected $epoch;
   protected $description;
   protected $viewPolicy;
   protected $editPolicy;
   protected $mailKey;
   protected $spacePHID;
 
   public static function initializeNewCountdown(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorCountdownApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(
       PhabricatorCountdownDefaultViewCapability::CAPABILITY);
 
+    $edit_policy = $app->getPolicy(
+      PhabricatorCountdownDefaultEditCapability::CAPABILITY);
+
     return id(new PhabricatorCountdown())
       ->setAuthorPHID($actor->getPHID())
       ->setViewPolicy($view_policy)
-      ->setEpoch(PhabricatorTime::getNow())
+      ->setEditPolicy($edit_policy)
       ->setSpacePHID($actor->getDefaultSpacePHID());
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_COLUMN_SCHEMA => array(
         'title' => 'text255',
         'description' => 'text',
         'mailKey' => 'bytes20',
       ),
+      self::CONFIG_KEY_SCHEMA => array(
+        'key_epoch' => array(
+          'columns' => array('epoch'),
+        ),
+        'key_author' => array(
+          'columns' => array('authorPHID', 'epoch'),
+        ),
+      ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorCountdownCountdownPHIDType::TYPECONST);
   }
 
   public function getMonogram() {
     return 'C'.$this->getID();
   }
 
+  public function getURI() {
+    return '/'.$this->getMonogram();
+  }
+
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
 
 /* -(  PhabricatorSubscribableInterface  )----------------------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($phid == $this->getAuthorPHID());
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorCountdownEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorCountdownTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     return array($this->getAuthorPHID());
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return false;
   }
 
 /* -( PhabricatorSpacesInterface )------------------------------------------- */
 
   public function getSpacePHID() {
     return $this->spacePHID;
   }
 
 }
diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php
index 72303a369..247466ffe 100644
--- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php
+++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php
@@ -1,178 +1,120 @@
 <?php
 
 final class PhabricatorCountdownTransaction
   extends PhabricatorApplicationTransaction {
 
   const TYPE_TITLE = 'countdown:title';
   const TYPE_EPOCH = 'countdown:epoch';
   const TYPE_DESCRIPTION = 'countdown:description';
 
   const MAILTAG_DETAILS = 'countdown:details';
   const MAILTAG_COMMENT = 'countdown:comment';
   const MAILTAG_OTHER  = 'countdown:other';
 
   public function getApplicationName() {
     return 'countdown';
   }
 
   public function getApplicationTransactionType() {
     return PhabricatorCountdownCountdownPHIDType::TYPECONST;
   }
 
   public function getApplicationTransactionCommentObject() {
     return new PhabricatorCountdownTransactionComment();
   }
 
   public function getTitle() {
     $author_phid = $this->getAuthorPHID();
     $object_phid = $this->getObjectPHID();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     $type = $this->getTransactionType();
     switch ($type) {
       case self::TYPE_TITLE:
-        if ($old === null) {
-          return pht(
-            '%s created this countdown.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s renamed this countdown from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $old,
-            $new);
-        }
-      break;
+        return pht(
+          '%s renamed this countdown from "%s" to "%s".',
+          $this->renderHandleLink($author_phid),
+          $old,
+          $new);
       case self::TYPE_DESCRIPTION:
-        if ($old === null) {
-          return pht(
-            '%s set the description of this countdown.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s edited the description of this countdown.',
-            $this->renderHandleLink($author_phid));
-        }
-      break;
+        return pht(
+          '%s edited the description of this countdown.',
+          $this->renderHandleLink($author_phid));
       case self::TYPE_EPOCH:
-        if ($old === null) {
-          return pht(
-            '%s set this countdown to end on %s.',
-            $this->renderHandleLink($author_phid),
-            phabricator_datetime($new, $this->getViewer()));
-        } else if ($old != $new) {
-          return pht(
-            '%s updated this countdown to end on %s.',
-            $this->renderHandleLink($author_phid),
-            phabricator_datetime($new, $this->getViewer()));
-        }
-        break;
+        return pht(
+          '%s updated this countdown to end on %s.',
+          $this->renderHandleLink($author_phid),
+          phabricator_datetime($new, $this->getViewer()));
     }
 
     return parent::getTitle();
   }
 
   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_TITLE:
-        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;
+        return pht(
+          '%s renamed %s.',
+          $this->renderHandleLink($author_phid),
+          $this->renderHandleLink($object_phid));
       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;
+        return pht(
+          '%s edited the description of %s.',
+          $this->renderHandleLink($author_phid),
+          $this->renderHandleLink($object_phid));
       case self::TYPE_EPOCH:
-        if ($old === null) {
-          return pht(
-            '%s set the end date of %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-
-        } else {
-          return pht(
-            '%s edited the end date of %s.',
-            $this->renderHandleLink($author_phid),
-            $this->renderHandleLink($object_phid));
-        }
-      break;
+        return pht(
+          '%s edited the end date of %s.',
+          $this->renderHandleLink($author_phid),
+          $this->renderHandleLink($object_phid));
     }
 
     return parent::getTitleForFeed();
   }
 
   public function getMailTags() {
     $tags = parent::getMailTags();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         $tags[] = self::MAILTAG_COMMENT;
         break;
       case self::TYPE_TITLE:
       case self::TYPE_EPOCH:
       case self::TYPE_DESCRIPTION:
         $tags[] = self::MAILTAG_DETAILS;
         break;
       default:
         $tags[] = self::MAILTAG_OTHER;
         break;
     }
 
     return $tags;
   }
 
-  public function shouldHide() {
-    $old = $this->getOldValue();
-    switch ($this->getTransactionType()) {
-      case self::TYPE_DESCRIPTION:
-        return ($old === null);
-    }
-    return parent::shouldHide();
-  }
-
   public function hasChangeDetails() {
     switch ($this->getTransactionType()) {
       case self::TYPE_DESCRIPTION:
         return ($this->getOldValue() !== null);
     }
 
     return parent::hasChangeDetails();
   }
 
   public function renderChangeDetails(PhabricatorUser $viewer) {
     return $this->renderTextCorpusChangeDetails(
       $viewer,
       $this->getOldValue(),
       $this->getNewValue());
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php b/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php
index f32892cf4..f79402459 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php
@@ -1,89 +1,94 @@
 <?php
 
 final class PhabricatorDaemonBulkJobViewController
   extends PhabricatorDaemonController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $job = id(new PhabricatorWorkerBulkJobQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$job) {
       return new Aphront404Response();
     }
 
     $title = pht('Bulk Job %d', $job->getID());
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Bulk Jobs'), '/daemon/bulk/');
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
 
     $properties = $this->renderProperties($job);
-    $actions = $this->renderActions($job);
-    $properties->setActionList($actions);
+    $curtain = $this->buildCurtainView($job);
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Details'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($properties);
 
     $timeline = $this->buildTransactionTimeline(
       $job,
       new PhabricatorWorkerBulkJobTransactionQuery());
     $timeline->setShouldTerminate(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-hourglass');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
         $box,
         $timeline,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function renderProperties(PhabricatorWorkerBulkJob $job) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($job);
 
     $view->addProperty(
       pht('Author'),
       $viewer->renderHandle($job->getAuthorPHID()));
 
     $view->addProperty(pht('Status'), $job->getStatusName());
 
     return $view;
   }
 
-  private function renderActions(PhabricatorWorkerBulkJob $job) {
+  private function buildCurtainView(PhabricatorWorkerBulkJob $job) {
     $viewer = $this->getViewer();
-
-    $actions = id(new PhabricatorActionListView())
-      ->setUser($viewer)
-      ->setObject($job);
+    $curtain = $this->newCurtainView($job);
 
     if ($job->isConfirming()) {
       $continue_uri = $job->getMonitorURI();
     } else {
       $continue_uri = $job->getDoneURI();
     }
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setHref($continue_uri)
         ->setIcon('fa-arrow-circle-o-right')
         ->setName(pht('Continue')));
 
-    return $actions;
+    return $curtain;
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
index 9f54726bc..a488ae3a6 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
@@ -1,271 +1,269 @@
 <?php
 
 final class PhabricatorDaemonConsoleController
   extends PhabricatorDaemonController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $window_start = (time() - (60 * 15));
 
     // Assume daemons spend about 250ms second in overhead per task acquiring
     // leases and doing other bookkeeping. This is probably an over-estimation,
     // but we'd rather show that utilization is too high than too low.
     $lease_overhead = 0.250;
 
     $completed = id(new PhabricatorWorkerArchiveTaskQuery())
       ->withDateModifiedSince($window_start)
       ->execute();
 
     $failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
       'failureTime > %d',
       $window_start);
 
     $usage_total = 0;
     $usage_start = PHP_INT_MAX;
 
     $completed_info = array();
     foreach ($completed as $completed_task) {
       $class = $completed_task->getTaskClass();
       if (empty($completed_info[$class])) {
         $completed_info[$class] = array(
           'n' => 0,
           'duration' => 0,
         );
       }
       $completed_info[$class]['n']++;
       $duration = $completed_task->getDuration();
       $completed_info[$class]['duration'] += $duration;
 
       // NOTE: Duration is in microseconds, but we're just using seconds to
       // compute utilization.
       $usage_total += $lease_overhead + ($duration / 1000000);
       $usage_start = min($usage_start, $completed_task->getDateModified());
     }
 
     $completed_info = isort($completed_info, 'n');
 
     $rows = array();
     foreach ($completed_info as $class => $info) {
       $rows[] = array(
         $class,
         number_format($info['n']),
         pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))),
       );
     }
 
     if ($failed) {
       // Add the time it takes to restart the daemons. This includes a guess
       // about other overhead of 2X.
       $restart_delay = PhutilDaemonHandle::getWaitBeforeRestart();
       $usage_total += $restart_delay * count($failed) * 2;
       foreach ($failed as $failed_task) {
         $usage_start = min($usage_start, $failed_task->getFailureTime());
       }
 
       $rows[] = array(
         phutil_tag('em', array(), pht('Temporary Failures')),
         count($failed),
         null,
       );
     }
 
     $logs = id(new PhabricatorDaemonLogQuery())
       ->setViewer($viewer)
       ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
       ->setAllowStatusWrites(true)
       ->execute();
 
     $taskmasters = 0;
     foreach ($logs as $log) {
       if ($log->getDaemon() == 'PhabricatorTaskmasterDaemon') {
         $taskmasters++;
       }
     }
 
     if ($taskmasters && $usage_total) {
       // Total number of wall-time seconds the daemons have been running since
       // the oldest event. For very short times round up to 15s so we don't
       // render any ridiculous numbers if you reload the page immediately after
       // restarting the daemons.
       $available_time = $taskmasters * max(15, (time() - $usage_start));
 
       // Percentage of those wall-time seconds we can account for, which the
       // daemons spent doing work:
       $used_time = ($usage_total / $available_time);
 
       $rows[] = array(
         phutil_tag('em', array(), pht('Queue Utilization (Approximate)')),
         sprintf('%.1f%%', 100 * $used_time),
         null,
       );
     }
 
     $completed_table = new AphrontTableView($rows);
     $completed_table->setNoDataString(
       pht('No tasks have completed in the last 15 minutes.'));
     $completed_table->setHeaders(
       array(
         pht('Class'),
         pht('Count'),
         pht('Avg'),
       ));
     $completed_table->setColumnClasses(
       array(
         'wide',
         'n',
         'n',
       ));
 
-    $completed_panel = new PHUIObjectBoxView();
-    $completed_panel->setHeaderText(
-      pht('Recently Completed Tasks (Last 15m)'));
-    $completed_panel->setTable($completed_table);
+    $completed_panel = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Recently Completed Tasks (Last 15m)'))
+      ->setTable($completed_table);
 
     $daemon_table = new PhabricatorDaemonLogListView();
     $daemon_table->setUser($viewer);
     $daemon_table->setDaemonLogs($logs);
 
-    $daemon_panel = new PHUIObjectBoxView();
+    $daemon_panel = id(new PHUIObjectBoxView());
     $daemon_panel->setHeaderText(pht('Active Daemons'));
     $daemon_panel->setObjectList($daemon_table);
 
 
     $tasks = id(new PhabricatorWorkerLeaseQuery())
       ->setSkipLease(true)
       ->withLeasedTasks(true)
       ->setLimit(100)
       ->execute();
 
     $tasks_table = id(new PhabricatorDaemonTasksTableView())
       ->setTasks($tasks)
       ->setNoDataString(pht('No tasks are leased by workers.'));
 
     $leased_panel = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Leased Tasks'))
       ->setTable($tasks_table);
 
     $task_table = new PhabricatorWorkerActiveTask();
     $queued = queryfx_all(
       $task_table->establishConnection('r'),
       'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass
         ORDER BY N DESC',
       $task_table->getTableName());
 
     $rows = array();
     foreach ($queued as $row) {
       $rows[] = array(
         $row['taskClass'],
         number_format($row['N']),
       );
     }
 
     $queued_table = new AphrontTableView($rows);
     $queued_table->setHeaders(
       array(
         pht('Class'),
         pht('Count'),
       ));
     $queued_table->setColumnClasses(
       array(
         'wide',
         'n',
       ));
     $queued_table->setNoDataString(pht('Task queue is empty.'));
 
     $queued_panel = new PHUIObjectBoxView();
     $queued_panel->setHeaderText(pht('Queued Tasks'));
     $queued_panel->setTable($queued_table);
 
     $upcoming = id(new PhabricatorWorkerLeaseQuery())
       ->setLimit(10)
       ->setSkipLease(true)
       ->execute();
 
     $upcoming_panel = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Next In Queue'))
       ->setTable(
         id(new PhabricatorDaemonTasksTableView())
           ->setTasks($upcoming)
           ->setNoDataString(pht('Task queue is empty.')));
 
     $triggers = id(new PhabricatorWorkerTriggerQuery())
       ->setViewer($viewer)
       ->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)
       ->needEvents(true)
       ->setLimit(10)
       ->execute();
 
     $triggers_table = $this->buildTriggersTable($triggers);
 
     $triggers_panel = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Upcoming Triggers'))
       ->setTable($triggers_table);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Console'));
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('/');
     $nav->appendChild(
       array(
         $crumbs,
         $completed_panel,
         $daemon_panel,
         $queued_panel,
         $leased_panel,
         $upcoming_panel,
         $triggers_panel,
       ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => pht('Console'),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('Console'))
+      ->appendChild($nav);
+
   }
 
   private function buildTriggersTable(array $triggers) {
     $viewer = $this->getViewer();
 
     $rows = array();
     foreach ($triggers as $trigger) {
       $event = $trigger->getEvent();
       if ($event) {
         $last_epoch = $event->getLastEventEpoch();
         $next_epoch = $event->getNextEventEpoch();
       } else {
         $last_epoch = null;
         $next_epoch = null;
       }
 
       $rows[] = array(
         $trigger->getID(),
         $trigger->getClockClass(),
         $trigger->getActionClass(),
         $last_epoch ? phabricator_datetime($last_epoch, $viewer) : null,
         $next_epoch ? phabricator_datetime($next_epoch, $viewer) : null,
       );
     }
 
     return id(new AphrontTableView($rows))
       ->setNoDataString(pht('There are no upcoming event triggers.'))
       ->setHeaders(
         array(
           pht('ID'),
           pht('Clock'),
           pht('Action'),
           pht('Last'),
           pht('Next'),
         ))
       ->setColumnClasses(
         array(
           '',
           '',
           'wide',
           'date',
           'date',
         ));
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php
index 772ee87fd..208a20b9a 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php
@@ -1,43 +1,47 @@
 <?php
 
 final class PhabricatorDaemonLogEventViewController
   extends PhabricatorDaemonController {
 
   public function handleRequest(AphrontRequest $request) {
     $id = $request->getURIData('id');
 
     $event = id(new PhabricatorDaemonLogEvent())->load($id);
     if (!$event) {
       return new Aphront404Response();
     }
 
     $event_view = id(new PhabricatorDaemonLogEventsView())
       ->setEvents(array($event))
       ->setUser($request->getUser())
       ->setCombinedLog(true)
       ->setShowFullMessage(true);
 
-    $log_panel = new PHUIObjectBoxView();
-    $log_panel->setHeaderText(pht('Combined Log'));
-    $log_panel->appendChild($event_view);
+    $log_panel = id(new PHUIObjectBoxView())
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->appendChild($event_view);
 
     $daemon_id = $event->getLogID();
 
     $crumbs = $this->buildApplicationCrumbs()
       ->addTextCrumb(
         pht('Daemon %s', $daemon_id),
         $this->getApplicationURI("log/{$daemon_id}/"))
-      ->addTextCrumb(pht('Event %s', $event->getID()));
+      ->addTextCrumb(pht('Event %s', $event->getID()))
+      ->setBorder(true);
 
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Combined Log'))
+      ->setHeaderIcon('fa-file-text');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($log_panel);
+
+    return $this->newPage()
+      ->setTitle(pht('Combined Daemon Log'))
+      ->appendChild($view);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $log_panel,
-      ),
-      array(
-        'title' => pht('Combined Daemon Log'),
-      ));
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
index c1de0b892..e5ef050d9 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
@@ -1,41 +1,40 @@
 <?php
 
 final class PhabricatorDaemonLogListController
   extends PhabricatorDaemonController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $pager = new AphrontCursorPagerView();
     $pager->readFromRequest($request);
 
     $logs = id(new PhabricatorDaemonLogQuery())
       ->setViewer($viewer)
       ->setAllowStatusWrites(true)
       ->executeWithCursorPager($pager);
 
     $daemon_table = new PhabricatorDaemonLogListView();
     $daemon_table->setUser($request->getUser());
     $daemon_table->setDaemonLogs($logs);
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('All Daemons'))
       ->appendChild($daemon_table);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('All Daemons'));
 
     $nav = $this->buildSideNavView();
     $nav->selectFilter('log');
     $nav->setCrumbs($crumbs);
     $nav->appendChild($box);
     $nav->appendChild($pager);
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => pht('All Daemons'),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('All Daemons'))
+      ->appendChild($nav);
+
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
index 32af8f6f1..f2f512189 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
@@ -1,183 +1,194 @@
 <?php
 
 final class PhabricatorDaemonLogViewController
   extends PhabricatorDaemonController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $log = id(new PhabricatorDaemonLogQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->setAllowStatusWrites(true)
       ->executeOne();
     if (!$log) {
       return new Aphront404Response();
     }
 
     $events = id(new PhabricatorDaemonLogEvent())->loadAllWhere(
       'logID = %d ORDER BY id DESC LIMIT 1000',
       $log->getID());
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Daemon %s', $log->getID()));
+    $crumbs->setBorder(true);
 
     $header = id(new PHUIHeaderView())
-      ->setHeader($log->getDaemon());
+      ->setHeader($log->getDaemon())
+      ->setHeaderIcon('fa-pied-piper-alt');
 
     $tag = id(new PHUITagView())
       ->setType(PHUITagView::TYPE_STATE);
 
     $status = $log->getStatus();
     switch ($status) {
       case PhabricatorDaemonLog::STATUS_UNKNOWN:
-        $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE);
-        $tag->setName(pht('Unknown'));
+        $color = 'orange';
+        $name = pht('Unknown');
+        $icon = 'fa-warning';
         break;
       case PhabricatorDaemonLog::STATUS_RUNNING:
-        $tag->setBackgroundColor(PHUITagView::COLOR_GREEN);
-        $tag->setName(pht('Running'));
+        $color = 'green';
+        $name = pht('Running');
+        $icon = 'fa-rocket';
         break;
       case PhabricatorDaemonLog::STATUS_DEAD:
-        $tag->setBackgroundColor(PHUITagView::COLOR_RED);
-        $tag->setName(pht('Dead'));
+        $color = 'red';
+        $name = pht('Dead');
+        $icon = 'fa-times';
         break;
       case PhabricatorDaemonLog::STATUS_WAIT:
-        $tag->setBackgroundColor(PHUITagView::COLOR_BLUE);
-        $tag->setName(pht('Waiting'));
+        $color = 'blue';
+        $name = pht('Waiting');
+        $icon = 'fa-clock-o';
         break;
       case PhabricatorDaemonLog::STATUS_EXITING:
-        $tag->setBackgroundColor(PHUITagView::COLOR_YELLOW);
-        $tag->setName(pht('Exiting'));
+        $color = 'yellow';
+        $name = pht('Exiting');
+        $icon = 'fa-check';
         break;
       case PhabricatorDaemonLog::STATUS_EXITED:
-        $tag->setBackgroundColor(PHUITagView::COLOR_GREY);
-        $tag->setName(pht('Exited'));
+        $color = 'bluegrey';
+        $name = pht('Exited');
+        $icon = 'fa-check';
         break;
     }
 
-    $header->addTag($tag);
+    $header->setStatus($icon, $color, $name);
 
     $properties = $this->buildPropertyListView($log);
 
     $event_view = id(new PhabricatorDaemonLogEventsView())
       ->setUser($viewer)
       ->setEvents($events);
 
-    $event_panel = new PHUIObjectBoxView();
-    $event_panel->setHeaderText(pht('Events'));
-    $event_panel->appendChild($event_view);
+    $event_panel = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Events'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->appendChild($event_view);
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
       ->addPropertyList($properties);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $object_box,
         $event_panel,
-      ),
-      array(
-        'title' => pht('Daemon Log'),
       ));
+
+    return $this->newPage()
+      ->setTitle(pht('Daemon Log'))
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function buildPropertyListView(PhabricatorDaemonLog $daemon) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $id = $daemon->getID();
     $c_epoch = $daemon->getDateCreated();
     $u_epoch = $daemon->getDateModified();
 
     $unknown_time = PhabricatorDaemonLogQuery::getTimeUntilUnknown();
     $dead_time = PhabricatorDaemonLogQuery::getTimeUntilDead();
     $wait_time = PhutilDaemonHandle::getWaitBeforeRestart();
 
     $details = null;
     $status = $daemon->getStatus();
     switch ($status) {
       case PhabricatorDaemonLog::STATUS_RUNNING:
         $details = pht(
           'This daemon is running normally and reported a status update '.
           'recently (within %s).',
           phutil_format_relative_time($unknown_time));
         break;
       case PhabricatorDaemonLog::STATUS_UNKNOWN:
         $details = pht(
           'This daemon has not reported a status update recently (within %s). '.
           'It may have exited abruptly. After %s, it will be presumed dead.',
           phutil_format_relative_time($unknown_time),
           phutil_format_relative_time($dead_time));
         break;
       case PhabricatorDaemonLog::STATUS_DEAD:
         $details = pht(
           'This daemon did not report a status update for %s. It is '.
           'presumed dead. Usually, this indicates that the daemon was '.
           'killed or otherwise exited abruptly with an error. You may '.
           'need to restart it.',
           phutil_format_relative_time($dead_time));
         break;
       case PhabricatorDaemonLog::STATUS_WAIT:
         $details = pht(
           'This daemon is running normally and reported a status update '.
           'recently (within %s). However, it encountered an error while '.
           'doing work and is waiting a little while (%s) to resume '.
           'processing. After encountering an error, daemons wait before '.
           'resuming work to avoid overloading services.',
           phutil_format_relative_time($unknown_time),
           phutil_format_relative_time($wait_time));
         break;
       case PhabricatorDaemonLog::STATUS_EXITING:
         $details = pht('This daemon is shutting down gracefully.');
         break;
       case PhabricatorDaemonLog::STATUS_EXITED:
         $details = pht('This daemon exited normally and is no longer running.');
         break;
     }
 
     $view->addProperty(pht('Status Details'), $details);
 
     $view->addProperty(pht('Daemon Class'), $daemon->getDaemon());
     $view->addProperty(pht('Host'), $daemon->getHost());
     $view->addProperty(pht('PID'), $daemon->getPID());
     $view->addProperty(pht('Running as'), $daemon->getRunningAsUser());
     $view->addProperty(pht('Started'), phabricator_datetime($c_epoch, $viewer));
     $view->addProperty(
       pht('Seen'),
       pht(
         '%s ago (%s)',
         phutil_format_relative_time(time() - $u_epoch),
         phabricator_datetime($u_epoch, $viewer)));
 
     $argv = $daemon->getArgv();
     if (is_array($argv)) {
       $argv = implode("\n", $argv);
     }
 
     $view->addProperty(
       pht('Argv'),
       phutil_tag(
         'textarea',
         array(
           'style'   => 'width: 100%; height: 12em;',
         ),
         $argv));
 
     $view->addProperty(
       pht('View Full Logs'),
       phutil_tag(
         'tt',
         array(),
         "phabricator/ $ ./bin/phd log --id {$id}"));
 
 
     return $view;
   }
 
 }
diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
index ad8f673fd..ad15d41b9 100644
--- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
+++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
@@ -1,218 +1,221 @@
 <?php
 
 final class PhabricatorWorkerTaskDetailController
   extends PhabricatorDaemonController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $task = id(new PhabricatorWorkerActiveTask())->load($id);
     if (!$task) {
       $tasks = id(new PhabricatorWorkerArchiveTaskQuery())
         ->withIDs(array($id))
         ->execute();
       $task = reset($tasks);
     }
 
     if (!$task) {
       $title = pht('Task Does Not Exist');
 
       $error_view = new PHUIInfoView();
       $error_view->setTitle(pht('No Such Task'));
       $error_view->appendChild(phutil_tag(
         'p',
         array(),
         pht('This task may have recently been garbage collected.')));
       $error_view->setSeverity(PHUIInfoView::SEVERITY_NODATA);
 
       $content = $error_view;
     } else {
       $title = pht('Task %d', $task->getID());
 
       $header = id(new PHUIHeaderView())
-        ->setHeader(pht('Task %d (%s)',
+        ->setHeader(pht('Task %d: %s',
           $task->getID(),
-          $task->getTaskClass()));
+          $task->getTaskClass()))
+        ->setHeaderIcon('fa-sort');
 
       $properties = $this->buildPropertyListView($task);
 
       $object_box = id(new PHUIObjectBoxView())
-        ->setHeader($header)
+        ->setHeaderText($title)
+        ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->addPropertyList($properties);
 
-
       $retry_head = id(new PHUIHeaderView())
         ->setHeader(pht('Retries'));
 
       $retry_info = $this->buildRetryListView($task);
 
       $retry_box = id(new PHUIObjectBoxView())
         ->setHeader($retry_head)
+        ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->addPropertyList($retry_info);
 
       $content = array(
         $object_box,
         $retry_box,
       );
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($content);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $content,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildPropertyListView(
     PhabricatorWorkerTask $task) {
 
     $viewer = $this->getRequest()->getUser();
 
     $view = new PHUIPropertyListView();
 
     if ($task->isArchived()) {
       switch ($task->getResult()) {
         case PhabricatorWorkerArchiveTask::RESULT_SUCCESS:
           $status = pht('Complete');
           break;
         case PhabricatorWorkerArchiveTask::RESULT_FAILURE:
           $status = pht('Failed');
           break;
         case PhabricatorWorkerArchiveTask::RESULT_CANCELLED:
           $status = pht('Cancelled');
           break;
         default:
           throw new Exception(pht('Unknown task status!'));
       }
     } else {
       $status = pht('Queued');
     }
 
     $view->addProperty(
       pht('Task Status'),
       $status);
 
     $view->addProperty(
       pht('Task Class'),
       $task->getTaskClass());
 
     if ($task->getLeaseExpires()) {
       if ($task->getLeaseExpires() > time()) {
         $lease_status = pht('Leased');
       } else {
         $lease_status = pht('Lease Expired');
       }
     } else {
       $lease_status = phutil_tag('em', array(), pht('Not Leased'));
     }
 
     $view->addProperty(
       pht('Lease Status'),
       $lease_status);
 
     $view->addProperty(
       pht('Lease Owner'),
       $task->getLeaseOwner()
         ? $task->getLeaseOwner()
         : phutil_tag('em', array(), pht('None')));
 
     if ($task->getLeaseExpires() && $task->getLeaseOwner()) {
       $expires = ($task->getLeaseExpires() - time());
       $expires = phutil_format_relative_time_detailed($expires);
     } else {
       $expires = phutil_tag('em', array(), pht('None'));
     }
 
     $view->addProperty(
       pht('Lease Expires'),
       $expires);
 
     if ($task->isArchived()) {
       $duration = pht('%s us', new PhutilNumber($task->getDuration()));
     } else {
       $duration = phutil_tag('em', array(), pht('Not Completed'));
     }
 
     $view->addProperty(
       pht('Duration'),
       $duration);
 
     $data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
     $task->setData($data->getData());
     $worker = $task->getWorkerInstance();
     $data = $worker->renderForDisplay($viewer);
 
     if ($data !== null) {
       $view->addProperty(pht('Data'), $data);
     }
 
     return $view;
   }
 
   private function buildRetryListView(PhabricatorWorkerTask $task) {
     $view = new PHUIPropertyListView();
 
     $data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
     $task->setData($data->getData());
     $worker = $task->getWorkerInstance();
 
     $view->addProperty(
       pht('Failure Count'),
       $task->getFailureCount());
 
     $retry_count = $worker->getMaximumRetryCount();
     if ($retry_count === null) {
       $max_retries = phutil_tag('em', array(), pht('Retries Forever'));
       $retry_count = INF;
     } else {
       $max_retries = $retry_count;
     }
 
     $view->addProperty(
       pht('Maximum Retries'),
       $max_retries);
 
     $projection = clone $task;
     $projection->makeEphemeral();
 
     $next = array();
     for ($ii = $task->getFailureCount(); $ii < $retry_count; $ii++) {
       $projection->setFailureCount($ii);
       $next[] = $worker->getWaitBeforeRetry($projection);
       if (count($next) > 10) {
         break;
       }
     }
 
     if ($next) {
       $cumulative = 0;
       foreach ($next as $key => $duration) {
         if ($duration === null) {
           $duration = 60;
         }
         $cumulative += $duration;
         $next[$key] = phutil_format_relative_time($cumulative);
       }
       if ($ii != $retry_count) {
         $next[] = '...';
       }
       $retries_in = implode(', ', $next);
     } else {
       $retries_in = pht('No More Retries');
     }
 
     $view->addProperty(
       pht('Retries After'),
       $retries_in);
 
     return $view;
   }
 
 }
diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php
index 965325663..3e67f76f0 100644
--- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php
+++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php
@@ -1,59 +1,58 @@
 <?php
 
 final class PhabricatorDashboardApplication extends PhabricatorApplication {
 
   public function getName() {
     return pht('Dashboards');
   }
 
   public function getBaseURI() {
     return '/dashboard/';
   }
 
   public function getShortDescription() {
     return pht('Create Custom Pages');
   }
 
   public function getIcon() {
     return 'fa-dashboard';
   }
 
   public function getRoutes() {
     return array(
       '/W(?P<id>\d+)' => 'PhabricatorDashboardPanelViewController',
       '/dashboard/' => array(
         '(?:query/(?P<queryKey>[^/]+)/)?'
           => 'PhabricatorDashboardListController',
         'view/(?P<id>\d+)/' => 'PhabricatorDashboardViewController',
         'archive/(?P<id>\d+)/' => 'PhabricatorDashboardArchiveController',
         'manage/(?P<id>\d+)/' => 'PhabricatorDashboardManageController',
-        'history/(?P<id>\d+)/' => 'PhabricatorDashboardHistoryController',
         'create/' => 'PhabricatorDashboardEditController',
         'copy/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardCopyController',
         'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
         'install/(?P<id>\d+)/' => 'PhabricatorDashboardInstallController',
         'uninstall/(?P<id>\d+)/' => 'PhabricatorDashboardUninstallController',
         'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
         'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
         'removepanel/(?P<id>\d+)/'
           => 'PhabricatorDashboardRemovePanelController',
         'panel/' => array(
           '(?:query/(?P<queryKey>[^/]+)/)?'
             => 'PhabricatorDashboardPanelListController',
           'create/' => 'PhabricatorDashboardPanelEditController',
           'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardPanelEditController',
           'render/(?P<id>\d+)/' => 'PhabricatorDashboardPanelRenderController',
           'archive/(?P<id>\d+)/'
             => 'PhabricatorDashboardPanelArchiveController',
         ),
       ),
     );
   }
 
   public function getRemarkupRules() {
     return array(
       new PhabricatorDashboardRemarkupRule(),
     );
   }
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
index 16fdac557..6dc4a6b8b 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
@@ -1,355 +1,362 @@
 <?php
 
 final class PhabricatorDashboardEditController
   extends PhabricatorDashboardController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if ($id) {
       $dashboard = id(new PhabricatorDashboardQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->needPanels(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$dashboard) {
         return new Aphront404Response();
       }
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $dashboard->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
       $is_new = false;
     } else {
       if (!$request->getStr('edit')) {
         if ($request->isFormPost()) {
           switch ($request->getStr('template')) {
             case 'empty':
               break;
             default:
               return $this->processBuildTemplateRequest($request);
           }
         } else {
           return $this->processTemplateRequest($request);
         }
       }
 
       $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer);
       $v_projects = array();
       $is_new = true;
     }
 
     $crumbs = $this->buildApplicationCrumbs();
 
     if ($is_new) {
       $title = pht('Create Dashboard');
-      $header = pht('Create Dashboard');
+      $header_icon = 'fa-plus-square';
       $button = pht('Create Dashboard');
       $cancel_uri = $this->getApplicationURI();
 
       $crumbs->addTextCrumb(pht('Create Dashboard'));
     } else {
       $id = $dashboard->getID();
       $cancel_uri = $this->getApplicationURI('manage/'.$id.'/');
 
-      $title = pht('Edit Dashboard %d', $dashboard->getID());
-      $header = pht('Edit Dashboard "%s"', $dashboard->getName());
+      $title = pht('Edit Dashboard: %s', $dashboard->getName());
+      $header_icon = 'fa-pencil';
       $button = pht('Save Changes');
 
-      $crumbs->addTextCrumb(pht('Dashboard %d', $id), $cancel_uri);
+      $crumbs->addTextCrumb($dashboard->getName(), $cancel_uri);
       $crumbs->addTextCrumb(pht('Edit'));
     }
 
     $v_name = $dashboard->getName();
     $v_layout_mode = $dashboard->getLayoutConfigObject()->getLayoutMode();
     $e_name = true;
 
     $validation_exception = null;
     if ($request->isFormPost() && $request->getStr('edit')) {
       $v_name = $request->getStr('name');
       $v_layout_mode = $request->getStr('layout_mode');
       $v_view_policy = $request->getStr('viewPolicy');
       $v_edit_policy = $request->getStr('editPolicy');
       $v_projects = $request->getArr('projects');
 
       $xactions = array();
 
       $type_name = PhabricatorDashboardTransaction::TYPE_NAME;
       $type_layout_mode = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE;
       $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
       $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY;
 
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType($type_name)
         ->setNewValue($v_name);
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType($type_layout_mode)
         ->setNewValue($v_layout_mode);
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType($type_view_policy)
         ->setNewValue($v_view_policy);
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType($type_edit_policy)
         ->setNewValue($v_edit_policy);
 
       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $proj_edge_type)
         ->setNewValue(array('=' => array_fuse($v_projects)));
 
       try {
         $editor = id(new PhabricatorDashboardTransactionEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request)
           ->applyTransactions($dashboard, $xactions);
 
         $uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/');
 
         return id(new AphrontRedirectResponse())->setURI($uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
         $e_name = $validation_exception->getShortMessage($type_name);
 
         $dashboard->setViewPolicy($v_view_policy);
         $dashboard->setEditPolicy($v_edit_policy);
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($dashboard)
       ->execute();
 
     $layout_mode_options =
       PhabricatorDashboardLayoutConfig::getLayoutModeSelectOptions();
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('edit', true)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Name'))
           ->setName('name')
           ->setValue($v_name)
           ->setError($e_name))
+      ->appendChild(
+        id(new AphrontFormSelectControl())
+          ->setLabel(pht('Layout Mode'))
+          ->setName('layout_mode')
+          ->setValue($v_layout_mode)
+          ->setOptions($layout_mode_options))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($dashboard)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($dashboard)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
-          ->setPolicies($policies))
-      ->appendChild(
-        id(new AphrontFormSelectControl())
-          ->setLabel(pht('Layout Mode'))
-          ->setName('layout_mode')
-          ->setValue($v_layout_mode)
-          ->setOptions($layout_mode_options));
+          ->setPolicies($policies));
 
     $form->appendControl(
       id(new AphrontFormTokenizerControl())
         ->setLabel(pht('Projects'))
         ->setName('projects')
         ->setValue($v_projects)
         ->setDatasource(new PhabricatorProjectDatasource()));
 
     $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue($button)
           ->addCancelButton($cancel_uri));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($header)
+      ->setHeaderText(pht('Dashboard'))
       ->setForm($form)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setValidationException($validation_exception);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function processTemplateRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $template_control = id(new AphrontFormRadioButtonControl())
       ->setName(pht('template'))
       ->setValue($request->getStr('template', 'empty'))
       ->addButton(
         'empty',
         pht('Empty'),
         pht('Start with a blank canvas.'))
       ->addButton(
         'simple',
         pht('Simple Template'),
         pht(
           'Start with a simple dashboard with a welcome message, a feed of '.
           'recent events, and a few starter panels.'));
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendRemarkupInstructions(
         pht('Choose a dashboard template to start with.'))
       ->appendChild($template_control);
 
     return $this->newDialog()
       ->setTitle(pht('Create Dashboard'))
       ->setWidth(AphrontDialogView::WIDTH_FORM)
       ->appendChild($form->buildLayoutView())
       ->addCancelButton('/dashboard/')
       ->addSubmitButton(pht('Continue'));
   }
 
   private function processBuildTemplateRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
     $template = $request->getStr('template');
 
     $bare_panel = PhabricatorDashboardPanel::initializeNewPanel($viewer);
     $panel_phids = array();
 
     switch ($template) {
       case 'simple':
         $v_name = pht('New Simple Dashboard');
 
         $welcome_panel = $this->newPanel(
           $request,
           $viewer,
           'text',
           pht('Welcome'),
           array(
             'text' => pht(
               "This is a simple template dashboard. You can edit this panel ".
               "to change this text and replace it with a welcome message, or ".
               "leave this placeholder text as-is to give your dashboard a ".
               "rustic, authentic feel.\n\n".
               "You can drag, remove, add, and edit panels to customize the ".
               "rest of this dashboard to show the information you want.\n\n".
               "To install this dashboard on the home page, use the ".
               "**Install Dashboard** action link above."),
           ));
         $panel_phids[] = $welcome_panel->getPHID();
 
         $feed_panel = $this->newPanel(
           $request,
           $viewer,
           'query',
           pht('Recent Activity'),
           array(
             'class' => 'PhabricatorFeedSearchEngine',
             'key' => 'all',
           ));
         $panel_phids[] = $feed_panel->getPHID();
 
         $task_panel = $this->newPanel(
           $request,
           $viewer,
           'query',
           pht('Recent Tasks'),
           array(
             'class' => 'ManiphestTaskSearchEngine',
             'key' => 'all',
           ));
         $panel_phids[] = $task_panel->getPHID();
 
         $commit_panel = $this->newPanel(
           $request,
           $viewer,
           'query',
           pht('Recent Commits'),
           array(
             'class' => 'PhabricatorCommitSearchEngine',
             'key' => 'all',
           ));
         $panel_phids[] = $commit_panel->getPHID();
 
         $mode_2_and_1 = PhabricatorDashboardLayoutConfig::MODE_THIRDS_AND_THIRD;
         $layout = id(new PhabricatorDashboardLayoutConfig())
           ->setLayoutMode($mode_2_and_1)
           ->setPanelLocation(0, $welcome_panel->getPHID())
           ->setPanelLocation(0, $task_panel->getPHID())
           ->setPanelLocation(0, $commit_panel->getPHID())
           ->setPanelLocation(1, $feed_panel->getPHID());
 
         break;
       default:
         throw new Exception(pht('Unknown dashboard template %s!', $template));
     }
 
     // Create the dashboard.
 
     $dashboard = PhabricatorDashboard::initializeNewDashboard($viewer)
       ->setLayoutConfigFromObject($layout);
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorDashboardTransaction())
       ->setTransactionType(PhabricatorDashboardTransaction::TYPE_NAME)
       ->setNewValue($v_name);
 
     $xactions[] = id(new PhabricatorDashboardTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
       ->setMetadataValue(
         'edge:type',
         PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST)
       ->setNewValue(
         array(
           '+' => array_fuse($panel_phids),
         ));
 
     $editor = id(new PhabricatorDashboardTransactionEditor())
       ->setActor($viewer)
       ->setContinueOnNoEffect(true)
       ->setContentSourceFromRequest($request)
       ->applyTransactions($dashboard, $xactions);
 
     $manage_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/');
 
     return id(new AphrontRedirectResponse())
       ->setURI($manage_uri);
   }
 
   private function newPanel(
     AphrontRequest $request,
     PhabricatorUser $viewer,
     $type,
     $name,
     array $properties) {
 
     $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer)
       ->setPanelType($type)
       ->setProperties($properties);
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorDashboardPanelTransaction())
       ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME)
       ->setNewValue($name);
 
     $editor = id(new PhabricatorDashboardPanelTransactionEditor())
       ->setActor($viewer)
       ->setContinueOnNoEffect(true)
       ->setContentSourceFromRequest($request)
       ->applyTransactions($panel, $xactions);
 
     return $panel;
   }
 
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php b/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php
deleted file mode 100644
index ab303b23e..000000000
--- a/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-final class PhabricatorDashboardHistoryController
-  extends PhabricatorDashboardController {
-
-  public function handleRequest(AphrontRequest $request) {
-    $viewer = $request->getViewer();
-    $id = $request->getURIData('id');
-
-    $dashboard_view_uri = $this->getApplicationURI('view/'.$id.'/');
-    $dashboard_manage_uri = $this->getApplicationURI('manage/'.$id.'/');
-
-    $dashboard = id(new PhabricatorDashboardQuery())
-      ->setViewer($viewer)
-      ->withIDs(array($id))
-      ->executeOne();
-    if (!$dashboard) {
-      return new Aphront404Response();
-    }
-
-    $title = $dashboard->getName();
-
-    $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->setBorder(true);
-    $crumbs->addTextCrumb(
-      pht('Dashboard %d', $dashboard->getID()),
-      $dashboard_view_uri);
-    $crumbs->addTextCrumb(
-      pht('Manage'),
-      $dashboard_manage_uri);
-    $crumbs->addTextCrumb(pht('History'));
-
-    $timeline = $this->buildTransactionTimeline(
-      $dashboard,
-      new PhabricatorDashboardTransactionQuery());
-    $timeline->setShouldTerminate(true);
-
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $timeline,
-      ),
-      array(
-        'title' => $title,
-      ));
-  }
-
-}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php
index 6600ad0d3..4e31b2d2a 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php
@@ -1,201 +1,206 @@
 <?php
 
 final class PhabricatorDashboardManageController
   extends PhabricatorDashboardController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $dashboard_uri = $this->getApplicationURI('view/'.$id.'/');
 
     // TODO: This UI should drop a lot of capabilities if the user can't
     // edit the dashboard, but we should still let them in for "Install" and
     // "View History".
 
     $dashboard = id(new PhabricatorDashboardQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needPanels(true)
       ->executeOne();
     if (!$dashboard) {
       return new Aphront404Response();
     }
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $dashboard,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $title = $dashboard->getName();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Dashboard %d', $dashboard->getID()),
       $dashboard_uri);
     $crumbs->addTextCrumb(pht('Manage'));
+    $crumbs->setBorder(true);
 
     $header = $this->buildHeaderView($dashboard);
-    $actions = $this->buildActionView($dashboard);
+    $curtain = $this->buildCurtainview($dashboard);
     $properties = $this->buildPropertyView($dashboard);
 
-    $properties->setActionList($actions);
-    $box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
-      ->addPropertyList($properties);
+    $timeline = $this->buildTransactionTimeline(
+      $dashboard,
+      new PhabricatorDashboardTransactionQuery());
 
+    $info_view = null;
     if (!$can_edit) {
       $no_edit = pht(
         'You do not have permission to edit this dashboard. If you want to '.
         'make changes, make a copy first.');
 
-      $box->setInfoView(
-        id(new PHUIInfoView())
-          ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
-          ->setErrors(array($no_edit)));
+      $info_view = id(new PHUIInfoView())
+        ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
+        ->setErrors(array($no_edit));
     }
 
     $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine())
       ->setViewer($viewer)
       ->setDashboard($dashboard)
       ->setArrangeMode($can_edit)
       ->renderDashboard();
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-        $rendered_dashboard,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $dashboard_box = id(new PHUIBoxView())
+      ->addClass('dashboard-preview-box')
+      ->appendChild($rendered_dashboard);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
+        $info_view,
+        $properties,
+        $timeline,
+      ))
+      ->setFooter($dashboard_box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function buildHeaderView(PhabricatorDashboard $dashboard) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
+    $id = $dashboard->getID();
 
     if ($dashboard->isArchived()) {
       $status_icon = 'fa-ban';
       $status_color = 'dark';
     } else {
       $status_icon = 'fa-check';
       $status_color = 'bluegrey';
     }
 
     $status_name = idx(
       PhabricatorDashboard::getStatusNameMap(),
       $dashboard->getStatus());
 
+    $button = id(new PHUIButtonView())
+      ->setTag('a')
+      ->setText(pht('View Dashboard'))
+      ->setIcon('fa-columns')
+      ->setHref($this->getApplicationURI("view/{$id}/"));
+
     return id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($dashboard->getName())
       ->setPolicyObject($dashboard)
-      ->setStatus($status_icon, $status_color, $status_name);
+      ->setStatus($status_icon, $status_color, $status_name)
+      ->setHeaderIcon('fa-dashboard')
+      ->addActionLink($button);
   }
 
-  private function buildActionView(PhabricatorDashboard $dashboard) {
-    $viewer = $this->getRequest()->getUser();
+  private function buildCurtainView(PhabricatorDashboard $dashboard) {
+    $viewer = $this->getViewer();
     $id = $dashboard->getID();
 
-    $actions = id(new PhabricatorActionListView())
-      ->setObject($dashboard)
-      ->setUser($viewer);
+    $curtain = $this->newCurtainView($dashboard);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $dashboard,
       PhabricatorPolicyCapability::CAN_EDIT);
 
-    $actions->addAction(
-      id(new PhabricatorActionView())
-        ->setName(pht('View Dashboard'))
-        ->setIcon('fa-columns')
-        ->setHref($this->getApplicationURI("view/{$id}/")));
-
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Dashboard'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("edit/{$id}/"))
         ->setDisabled(!$can_edit));
 
     if ($dashboard->isArchived()) {
-      $actions->addAction(
+      $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Activate Dashboard'))
           ->setIcon('fa-check')
           ->setHref($this->getApplicationURI("archive/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow($can_edit));
     } else {
-      $actions->addAction(
+      $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Archive Dashboard'))
           ->setIcon('fa-ban')
           ->setHref($this->getApplicationURI("archive/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow($can_edit));
     }
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Copy Dashboard'))
         ->setIcon('fa-files-o')
         ->setHref($this->getApplicationURI("copy/{$id}/"))
         ->setWorkflow(true));
 
     $installed_dashboard = id(new PhabricatorDashboardInstall())
       ->loadOneWhere(
         'objectPHID = %s AND applicationClass = %s',
         $viewer->getPHID(),
         'PhabricatorHomeApplication');
     if ($installed_dashboard &&
         $installed_dashboard->getDashboardPHID() == $dashboard->getPHID()) {
       $title_install = pht('Uninstall Dashboard');
       $href_install = "uninstall/{$id}/";
     } else {
       $title_install = pht('Install Dashboard');
       $href_install = "install/{$id}/";
     }
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
       ->setName($title_install)
       ->setIcon('fa-wrench')
       ->setHref($this->getApplicationURI($href_install))
       ->setWorkflow(true));
 
-    $actions->addAction(
-      id(new PhabricatorActionView())
-        ->setName(pht('View History'))
-        ->setIcon('fa-history')
-        ->setHref($this->getApplicationURI("history/{$id}/")));
-
-    return $actions;
+    return $curtain;
   }
 
   private function buildPropertyView(PhabricatorDashboard $dashboard) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
-      ->setUser($viewer)
-      ->setObject($dashboard);
+      ->setUser($viewer);
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $dashboard);
 
     $properties->addProperty(
       pht('Editable By'),
       $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
 
     $properties->addProperty(
       pht('Panels'),
       $viewer->renderHandleList($dashboard->getPanelPHIDs()));
 
-    $properties->invokeWillRenderEvent();
-
-    return $properties;
+    return id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Details'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->addPropertyList($properties);
   }
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php
index beccd3551..790b8d11d 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php
@@ -1,449 +1,462 @@
 <?php
 
 final class PhabricatorDashboardPanelEditController
   extends PhabricatorDashboardController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     // If the user is trying to create a panel directly on a dashboard, make
     // sure they have permission to see and edit the dashboard.
 
     $dashboard_id = $request->getInt('dashboardID');
     $dashboard = null;
     if ($dashboard_id) {
       $dashboard = id(new PhabricatorDashboardQuery())
         ->setViewer($viewer)
         ->withIDs(array($dashboard_id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$dashboard) {
         return new Aphront404Response();
       }
 
       $manage_uri = $this->getApplicationURI('manage/'.$dashboard_id.'/');
     }
 
     if ($id) {
       $is_create = false;
 
       if ($dashboard) {
         $capabilities = array(
           PhabricatorPolicyCapability::CAN_VIEW,
         );
       } else {
         $capabilities = array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         );
       }
 
       $panel = id(new PhabricatorDashboardPanelQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities($capabilities)
         ->executeOne();
       if (!$panel) {
         return new Aphront404Response();
       }
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $panel->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
 
       if ($dashboard) {
         $can_edit = PhabricatorPolicyFilter::hasCapability(
           $viewer,
           $panel,
           PhabricatorPolicyCapability::CAN_EDIT);
         if (!$can_edit) {
           if ($request->isFormPost() && $request->getBool('copy')) {
             $panel = $this->copyPanel(
               $request,
               $dashboard,
               $panel);
           } else {
             return $this->processPanelCloneRequest(
               $request,
               $dashboard,
               $panel);
           }
         }
       }
     } else {
       $is_create = true;
 
       $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer);
       $types = PhabricatorDashboardPanelType::getAllPanelTypes();
       $type = $request->getStr('type');
       if (empty($types[$type])) {
         return $this->processPanelTypeRequest($request);
       }
       $v_projects = array();
 
       $panel->setPanelType($type);
     }
 
     if ($is_create) {
-      $title = pht('New Panel');
-      $header = pht('Create New Panel');
+      $title = pht('Create New Panel');
       $button = pht('Create Panel');
+      $header_icon = 'fa-plus-square';
       if ($dashboard) {
         $cancel_uri = $manage_uri;
       } else {
         $cancel_uri = $this->getApplicationURI('panel/');
       }
     } else {
-      $title = pht('Edit %s', $panel->getMonogram());
-      $header = pht('Edit %s %s', $panel->getMonogram(), $panel->getName());
+      $title = pht('Edit Panel: %s', $panel->getName());
       $button = pht('Save Panel');
+      $header_icon = 'fa-pencil';
       if ($dashboard) {
         $cancel_uri = $manage_uri;
       } else {
         $cancel_uri = '/'.$panel->getMonogram();
       }
     }
 
     $v_name = $panel->getName();
     $e_name = true;
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $panel,
       PhabricatorCustomField::ROLE_EDIT);
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($panel);
 
     if ($is_create && !$request->isFormPost()) {
       $panel->requireImplementation()->initializeFieldsFromRequest(
         $panel,
         $field_list,
         $request);
     }
 
     $validation_exception = null;
 
     // NOTE: We require 'edit' to distinguish between the "Choose a Type"
     // and "Create a Panel" dialogs.
 
     if ($request->isFormPost() && $request->getBool('edit')) {
       $v_name = $request->getStr('name');
       $v_view_policy = $request->getStr('viewPolicy');
       $v_edit_policy = $request->getStr('editPolicy');
       $v_projects = $request->getArr('projects');
 
       $type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME;
       $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
       $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY;
 
       $xactions = array();
 
       $xactions[] = id(new PhabricatorDashboardPanelTransaction())
         ->setTransactionType($type_name)
         ->setNewValue($v_name);
 
       $xactions[] = id(new PhabricatorDashboardPanelTransaction())
         ->setTransactionType($type_view_policy)
         ->setNewValue($v_view_policy);
 
       $xactions[] = id(new PhabricatorDashboardPanelTransaction())
         ->setTransactionType($type_edit_policy)
         ->setNewValue($v_edit_policy);
 
       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
       $xactions[] = id(new PhabricatorDashboardPanelTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $proj_edge_type)
         ->setNewValue(array('=' => array_fuse($v_projects)));
 
       $field_xactions = $field_list->buildFieldTransactionsFromRequest(
         new PhabricatorDashboardPanelTransaction(),
         $request);
       $xactions = array_merge($xactions, $field_xactions);
 
       try {
         $editor = id(new PhabricatorDashboardPanelTransactionEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request)
           ->applyTransactions($panel, $xactions);
 
         // If we're creating a panel directly on a dashboard, add it now.
         if ($dashboard) {
           PhabricatorDashboardTransactionEditor::addPanelToDashboard(
             $viewer,
             PhabricatorContentSource::newFromRequest($request),
             $panel,
             $dashboard,
             $request->getInt('column', 0));
         }
 
         if ($dashboard) {
           $done_uri = $manage_uri;
         } else {
           $done_uri = '/'.$panel->getMonogram();
         }
 
         return id(new AphrontRedirectResponse())->setURI($done_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
         $e_name = $validation_exception->getShortMessage($type_name);
 
         $panel->setViewPolicy($v_view_policy);
         $panel->setEditPolicy($v_edit_policy);
       }
     }
 
     // NOTE: We're setting the submit URI explicitly because we need to edit
     // a different panel if we just cloned the original panel.
     if ($is_create) {
       $submit_uri = $this->getApplicationURI('panel/edit/');
     } else {
       $submit_uri = $this->getApplicationURI('panel/edit/'.$panel->getID().'/');
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($panel)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setAction($submit_uri)
       ->addHiddenInput('edit', true)
       ->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
       ->addHiddenInput('column', $request->getInt('column'))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Name'))
           ->setName('name')
           ->setValue($v_name)
           ->setError($e_name))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($panel)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($panel)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicies($policies));
 
     $form->appendControl(
       id(new AphrontFormTokenizerControl())
         ->setLabel(pht('Projects'))
         ->setName('projects')
         ->setValue($v_projects)
         ->setDatasource(new PhabricatorProjectDatasource()));
 
     $field_list->appendFieldsToForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Panels'),
       $this->getApplicationURI('panel/'));
     if ($is_create) {
       $crumbs->addTextCrumb(pht('New Panel'));
       $form->addHiddenInput('type', $panel->getPanelType());
     } else {
       $crumbs->addTextCrumb(
         $panel->getMonogram(),
         '/'.$panel->getMonogram());
       $crumbs->addTextCrumb(pht('Edit'));
     }
+    $crumbs->setBorder(true);
 
     if ($request->isAjax()) {
       return $this->newDialog()
-        ->setTitle($header)
+        ->setTitle($title)
         ->setSubmitURI($submit_uri)
         ->setWidth(AphrontDialogView::WIDTH_FORM)
         ->setValidationException($validation_exception)
         ->appendChild($form->buildLayoutView())
         ->addCancelButton($cancel_uri)
         ->addSubmitButton($button);
     } else {
       $form
         ->appendChild(
           id(new AphrontFormSubmitControl())
             ->setValue($button)
             ->addCancelButton($cancel_uri));
     }
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($header)
+      ->setHeaderText(pht('Panel'))
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function processPanelTypeRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $types = PhabricatorDashboardPanelType::getAllPanelTypes();
 
     $v_type = null;
     $errors = array();
     if ($request->isFormPost()) {
       $v_type = $request->getStr('type');
       if (!isset($types[$v_type])) {
         $errors[] = pht('You must select a type of panel to create.');
       }
     }
 
     $cancel_uri = $this->getApplicationURI('panel/');
 
     if (!$v_type) {
       $v_type = key($types);
     }
 
     $panel_types = id(new AphrontFormRadioButtonControl())
       ->setName('type')
       ->setValue($v_type);
 
     foreach ($types as $key => $type) {
       $panel_types->addButton(
         $key,
         $type->getPanelTypeName(),
         $type->getPanelTypeDescription());
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
       ->addHiddenInput('column', $request->getInt('column'))
       ->appendRemarkupInstructions(
         pht(
           'Choose the type of dashboard panel to create:'))
       ->appendChild($panel_types);
 
     if ($request->isAjax()) {
       return $this->newDialog()
         ->setTitle(pht('Add New Panel'))
         ->setWidth(AphrontDialogView::WIDTH_FORM)
         ->setErrors($errors)
         ->appendChild($form->buildLayoutView())
         ->addCancelbutton($cancel_uri)
         ->addSubmitButton(pht('Continue'));
     } else {
       $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Continue'))
           ->addCancelButton($cancel_uri));
     }
 
     $title = pht('Create Dashboard Panel');
+    $header_icon = 'fa-plus-square';
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Panels'),
       $this->getApplicationURI('panel/'));
     $crumbs->addTextCrumb(pht('New Panel'));
+    $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Panel'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function processPanelCloneRequest(
     AphrontRequest $request,
     PhabricatorDashboard $dashboard,
     PhabricatorDashboardPanel $panel) {
 
     $viewer = $request->getUser();
 
     $manage_uri = $this->getApplicationURI('manage/'.$dashboard->getID().'/');
 
     return $this->newDialog()
       ->setTitle(pht('Copy Panel?'))
       ->addHiddenInput('copy', true)
       ->addHiddenInput('dashboardID', $request->getInt('dashboardID'))
       ->addHiddenInput('column', $request->getInt('column'))
       ->appendParagraph(
         pht(
           'You do not have permission to edit this dashboard panel, but you '.
           'can make a copy and edit that instead. If you choose to copy the '.
           'panel, the original will be replaced with the new copy on this '.
           'dashboard.'))
       ->appendParagraph(
         pht(
           'Do you want to make a copy of this panel?'))
       ->addCancelButton($manage_uri)
       ->addSubmitButton(pht('Copy Panel'));
   }
 
   private function copyPanel(
     AphrontRequest $request,
     PhabricatorDashboard $dashboard,
     PhabricatorDashboardPanel $panel) {
 
     $viewer = $request->getUser();
 
     $copy = PhabricatorDashboardPanel::initializeNewPanel($viewer);
     $copy = PhabricatorDashboardPanel::copyPanel($copy, $panel);
 
     $copy->openTransaction();
       $copy->save();
 
       // TODO: This should record a transaction on the panel copy, too.
 
       $xactions = array();
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue(
           'edge:type',
           PhabricatorDashboardDashboardHasPanelEdgeType::EDGECONST)
         ->setNewValue(
           array(
             '+' => array(
               $copy->getPHID() => $copy->getPHID(),
             ),
             '-' => array(
               $panel->getPHID() => $panel->getPHID(),
             ),
           ));
 
       $layout_config = $dashboard->getLayoutConfigObject();
       $layout_config->replacePanel($panel->getPHID(), $copy->getPHID());
       $dashboard->setLayoutConfigFromObject($layout_config);
       $dashboard->save();
 
       $editor = id(new PhabricatorDashboardTransactionEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnMissingFields(true)
         ->setContinueOnNoEffect(true)
         ->applyTransactions($dashboard, $xactions);
     $copy->saveTransaction();
 
     return $copy;
   }
 
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php
index b1718421e..0a0e7e30c 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php
@@ -1,69 +1,67 @@
 <?php
 
 final class PhabricatorDashboardPanelRenderController
   extends PhabricatorDashboardController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $panel = id(new PhabricatorDashboardPanelQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$panel) {
       return new Aphront404Response();
     }
 
     if ($request->isAjax()) {
       $parent_phids = $request->getStrList('parentPanelPHIDs', null);
       if ($parent_phids === null) {
         throw new Exception(
           pht(
             'Required parameter `parentPanelPHIDs` is not present in '.
             'request.'));
       }
     } else {
       $parent_phids = array();
     }
 
     $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine())
       ->setViewer($viewer)
       ->setPanel($panel)
       ->setParentPanelPHIDs($parent_phids)
       ->setHeaderMode($request->getStr('headerMode'))
       ->setDashboardID($request->getInt('dashboardID'))
       ->renderPanel();
 
     if ($request->isAjax()) {
       return id(new AphrontAjaxResponse())
         ->setContent(
           array(
             'panelMarkup' => hsprintf('%s', $rendered_panel),
           ));
     }
 
     $crumbs = $this->buildApplicationCrumbs()
       ->addTextCrumb(pht('Panels'), $this->getApplicationURI('panel/'))
       ->addTextCrumb($panel->getMonogram(), '/'.$panel->getMonogram())
-      ->addTextCrumb(pht('Standalone View'));
+      ->addTextCrumb(pht('Standalone View'))
+      ->setBorder(true);
 
     $view = id(new PHUIBoxView())
       ->addClass('dashboard-view')
       ->appendChild($rendered_panel);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $view,
-      ),
-      array(
-        'title' => array(pht('Panel'), $panel->getName()),
-      ));
+    return $this->newPage()
+      ->setTitle(array(pht('Panel'), $panel->getName()))
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php
index bc1337459..9fd9e1840 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php
@@ -1,173 +1,173 @@
 <?php
 
 final class PhabricatorDashboardPanelViewController
   extends PhabricatorDashboardController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $panel = id(new PhabricatorDashboardPanelQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$panel) {
       return new Aphront404Response();
     }
 
     $title = $panel->getMonogram().' '.$panel->getName();
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Panels'),
       $this->getApplicationURI('panel/'));
     $crumbs->addTextCrumb($panel->getMonogram());
+    $crumbs->setBorder(true);
 
     $header = $this->buildHeaderView($panel);
-    $actions = $this->buildActionView($panel);
+    $curtain = $this->buildCurtainView($panel);
     $properties = $this->buildPropertyView($panel);
+
     $timeline = $this->buildTransactionTimeline(
       $panel,
       new PhabricatorDashboardPanelTransactionQuery());
-    $timeline->setShouldTerminate(true);
-
-    $properties->setActionList($actions);
-    $box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
-      ->addPropertyList($properties);
 
     $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine())
       ->setViewer($viewer)
       ->setPanel($panel)
       ->setParentPanelPHIDs(array())
       ->renderPanel();
 
-    $view = id(new PHUIBoxView())
-      ->addMargin(PHUI::MARGIN_LARGE_LEFT)
-      ->addMargin(PHUI::MARGIN_LARGE_RIGHT)
-      ->addMargin(PHUI::MARGIN_LARGE_TOP)
+    $preview = id(new PHUIBoxView())
+      ->addClass('dashboard-preview-box')
       ->appendChild($rendered_panel);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-        $view,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
+        $properties,
         $timeline,
-      ),
-      array(
-        'title' => $title,
-      ));
+      ))
+      ->setFooter($rendered_panel);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function buildHeaderView(PhabricatorDashboardPanel $panel) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
+    $id = $panel->getID();
+
+    $button = id(new PHUIButtonView())
+      ->setTag('a')
+      ->setText(pht('View Panel'))
+      ->setIcon('fa-columns')
+      ->setHref($this->getApplicationURI("panel/render/{$id}/"));
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($panel->getName())
-      ->setPolicyObject($panel);
+      ->setPolicyObject($panel)
+      ->setHeaderIcon('fa-columns')
+      ->addActionLink($button);
 
     if (!$panel->getIsArchived()) {
       $header->setStatus('fa-check', 'bluegrey', pht('Active'));
     } else {
       $header->setStatus('fa-ban', 'red', pht('Archived'));
     }
     return $header;
   }
 
-  private function buildActionView(PhabricatorDashboardPanel $panel) {
-    $viewer = $this->getRequest()->getUser();
+  private function buildCurtainView(PhabricatorDashboardPanel $panel) {
+    $viewer = $this->getViewer();
     $id = $panel->getID();
 
-    $actions = id(new PhabricatorActionListView())
-      ->setObject($panel)
-      ->setUser($viewer);
+    $curtain = $this->newCurtainView($panel);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $panel,
       PhabricatorPolicyCapability::CAN_EDIT);
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Panel'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("panel/edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     if (!$panel->getIsArchived()) {
       $archive_text = pht('Archive Panel');
       $archive_icon = 'fa-ban';
     } else {
       $archive_text = pht('Activate Panel');
       $archive_icon = 'fa-check';
     }
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($archive_text)
         ->setIcon($archive_icon)
         ->setHref($this->getApplicationURI("panel/archive/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
-    $actions->addAction(
-      id(new PhabricatorActionView())
-        ->setName(pht('View Standalone'))
-        ->setIcon('fa-eye')
-        ->setHref($this->getApplicationURI("panel/render/{$id}/")));
-
-    return $actions;
+    return $curtain;
   }
 
   private function buildPropertyView(PhabricatorDashboardPanel $panel) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
-      ->setUser($viewer)
-      ->setObject($panel);
+      ->setUser($viewer);
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $panel);
 
     $panel_type = $panel->getImplementation();
     if ($panel_type) {
       $type_name = $panel_type->getPanelTypeName();
     } else {
       $type_name = phutil_tag(
         'em',
         array(),
         nonempty($panel->getPanelType(), pht('null')));
     }
 
     $properties->addProperty(
       pht('Panel Type'),
       $type_name);
 
     $properties->addProperty(
       pht('Editable By'),
       $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
 
     $dashboard_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
       $panel->getPHID(),
       PhabricatorDashboardPanelHasDashboardEdgeType::EDGECONST);
 
     $does_not_appear = pht(
       'This panel does not appear on any dashboards.');
 
     $properties->addProperty(
       pht('Appears On'),
       $dashboard_phids
         ? $viewer->renderHandleList($dashboard_phids)
         : phutil_tag('em', array(), $does_not_appear));
 
-    return $properties;
+    return id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Details'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->addPropertyList($properties);
   }
 
 }
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
index 4caa68d2e..dd2214df2 100644
--- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
@@ -1,77 +1,73 @@
 <?php
 
 final class PhabricatorDashboardViewController
   extends PhabricatorDashboardController {
 
   private $id;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $this->id = $request->getURIData('id');
 
     $dashboard = id(new PhabricatorDashboardQuery())
       ->setViewer($viewer)
       ->withIDs(array($this->id))
       ->needPanels(true)
       ->executeOne();
     if (!$dashboard) {
       return new Aphront404Response();
     }
 
     $title = $dashboard->getName();
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumbs->addTextCrumb(pht('Dashboard %d', $dashboard->getID()));
 
     if ($dashboard->getPanelPHIDs()) {
       $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine())
         ->setViewer($viewer)
         ->setDashboard($dashboard)
         ->renderDashboard();
     } else {
       $rendered_dashboard = $this->buildEmptyView();
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $rendered_dashboard,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($rendered_dashboard);
   }
 
   protected function buildApplicationCrumbs() {
     $crumbs = parent::buildApplicationCrumbs();
     $id = $this->id;
 
     $crumbs->addAction(
       id(new PHUIListItemView())
         ->setIcon('fa-th')
         ->setName(pht('Manage Dashboard'))
         ->setHref($this->getApplicationURI("manage/{$id}/")));
 
     return $crumbs;
   }
 
   public function buildEmptyView() {
     $id = $this->id;
     $manage_uri = $this->getApplicationURI("manage/{$id}/");
 
     return id(new PHUIInfoView())
       ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
       ->appendChild(
         pht('This dashboard has no panels '.
           'yet. Use %s to add panels.',
           phutil_tag(
             'a',
             array('href' => $manage_uri),
             pht('Manage Dashboard'))));
   }
 
 }
diff --git a/src/applications/differential/controller/DifferentialRevisionEditController.php b/src/applications/differential/controller/DifferentialRevisionEditController.php
index a21e1b259..eb3aa0852 100644
--- a/src/applications/differential/controller/DifferentialRevisionEditController.php
+++ b/src/applications/differential/controller/DifferentialRevisionEditController.php
@@ -1,214 +1,214 @@
 <?php
 
 final class DifferentialRevisionEditController
   extends DifferentialController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     if (!$id) {
       $id = $request->getInt('revisionID');
     }
 
     if ($id) {
       $revision = id(new DifferentialRevisionQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->needRelationships(true)
         ->needReviewerStatus(true)
         ->needActiveDiffs(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$revision) {
         return new Aphront404Response();
       }
     } else {
       $revision = DifferentialRevision::initializeNewRevision($viewer);
       $revision->attachReviewerStatus(array());
     }
 
     $diff_id = $request->getInt('diffID');
     if ($diff_id) {
       $diff = id(new DifferentialDiffQuery())
         ->setViewer($viewer)
         ->withIDs(array($diff_id))
         ->executeOne();
       if (!$diff) {
         return new Aphront404Response();
       }
       if ($diff->getRevisionID()) {
         // TODO: Redirect?
         throw new Exception(
           pht('This diff is already attached to a revision!'));
       }
     } else {
       $diff = null;
     }
 
     if (!$diff) {
       if (!$revision->getID()) {
         throw new Exception(
           pht('You can not create a new revision without a diff!'));
       }
     } else {
       // TODO: It would be nice to show the diff being attached in the UI.
     }
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $revision,
       PhabricatorCustomField::ROLE_EDIT);
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($revision);
 
     if ($request->getStr('viaDiffView') && $diff) {
       $repo_key = id(new DifferentialRepositoryField())->getFieldKey();
       $repository_field = idx(
         $field_list->getFields(),
         $repo_key);
       if ($repository_field) {
         $repository_field->setValue($request->getStr($repo_key));
       }
       $view_policy_key = id(new DifferentialViewPolicyField())->getFieldKey();
       $view_policy_field = idx(
         $field_list->getFields(),
         $view_policy_key);
       if ($view_policy_field) {
         $view_policy_field->setValue($diff->getViewPolicy());
       }
     }
 
     $validation_exception = null;
     if ($request->isFormPost() && !$request->getStr('viaDiffView')) {
 
       $editor = id(new DifferentialTransactionEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       $xactions = $field_list->buildFieldTransactionsFromRequest(
         new DifferentialTransaction(),
         $request);
 
       if ($diff) {
         $repository_phid = null;
         $repository_tokenizer = $request->getArr(
           id(new DifferentialRepositoryField())->getFieldKey());
         if ($repository_tokenizer) {
           $repository_phid = reset($repository_tokenizer);
         }
 
         $xactions[] = id(new DifferentialTransaction())
           ->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
           ->setNewValue($diff->getPHID());
 
         $editor->setRepositoryPHIDOverride($repository_phid);
       }
 
       $comments = $request->getStr('comments');
       if (strlen($comments)) {
         $xactions[] = id(new DifferentialTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
           ->attachComment(
             id(new DifferentialTransactionComment())
               ->setContent($comments));
       }
 
       try {
         $editor->applyTransactions($revision, $xactions);
         $revision_uri = '/D'.$revision->getID();
         return id(new AphrontRedirectResponse())->setURI($revision_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
       }
     }
 
 
     $form = new AphrontFormView();
     $form->setUser($request->getUser());
     if ($diff) {
       $form->addHiddenInput('diffID', $diff->getID());
     }
 
     if ($revision->getID()) {
       $form->setAction('/differential/revision/edit/'.$revision->getID().'/');
     } else {
       $form->setAction('/differential/revision/edit/');
     }
 
     if ($diff && $revision->getID()) {
       $form
         ->appendChild(
           id(new AphrontFormTextAreaControl())
             ->setLabel(pht('Comments'))
             ->setName('comments')
             ->setCaption(pht("Explain what's new in this diff."))
             ->setValue($request->getStr('comments')))
         ->appendChild(
           id(new AphrontFormSubmitControl())
             ->setValue(pht('Save')))
         ->appendChild(
           id(new AphrontFormDividerControl()));
     }
 
     $field_list->appendFieldsToForm($form);
 
     $submit = id(new AphrontFormSubmitControl())
       ->setValue('Save');
     if ($diff) {
       $submit->addCancelButton('/differential/diff/'.$diff->getID().'/');
     } else {
       $submit->addCancelButton('/D'.$revision->getID());
     }
 
     $form->appendChild($submit);
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($revision->getID()) {
       if ($diff) {
         $header_icon = 'fa-upload';
-        $title = pht('Update Differential Revision');
+        $title = pht('Update Revision');
         $crumbs->addTextCrumb(
           'D'.$revision->getID(),
           '/differential/diff/'.$diff->getID().'/');
       } else {
         $header_icon = 'fa-pencil';
-        $title = pht('Edit Differential Revision');
+        $title = pht('Edit Revision: %s', $revision->getTitle());
         $crumbs->addTextCrumb(
           'D'.$revision->getID(),
           '/D'.$revision->getID());
       }
     } else {
       $header_icon = 'fa-plus-square';
       $title = pht('Create New Differential Revision');
     }
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText('Revision')
       ->setValidationException($validation_exception)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setHeaderIcon($header_icon);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setFooter($form_box);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
 }
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 61fffd9b2..ef333a419 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,1173 +1,1173 @@
 <?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(
       $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');
     }
 
     $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().'/');
     }
 
     $diff_history = id(new DifferentialRevisionUpdateHistoryView())
       ->setUser($viewer)
       ->setDiffs($diffs)
       ->setSelectedVersusDiffID($diff_vs)
       ->setSelectedDiffID($target->getID())
       ->setSelectedWhitespace($whitespace)
       ->setCommitsForLinks($commits_for_links);
 
     $local_view = 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));
 
     $comment_form = null;
     if (!$viewer_is_anonymous) {
       $comment_form = $this->buildCommentForm($revision, $field_list);
     }
 
     $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 {
       $footer[] =
         array(
           $diff_history,
           $warning,
           $local_view,
           $toc_view,
           $other_view,
           $changeset_view,
         );
     }
 
     if ($comment_form) {
       $footer[] = $comment_form;
     } else {
       // TODO: For now, just use this to get "Login to Comment".
       $footer[] = id(new PhabricatorApplicationTransactionCommentView())
         ->setUser($viewer)
         ->setRequestURI($request->getRequestURI());
     }
 
     $object_id = 'D'.$revision->getID();
     $operations_box = $this->buildOperationsBox($revision);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($object_id, '/'.$object_id);
     $crumbs->setBorder(true);
 
     $prefs = $viewer->loadPreferences();
     $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
     $nav = null;
     if ($prefs->getPreference($pref_filetree)) {
       $collapsed = $prefs->getPreference(
         PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED,
         false);
 
       $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
         ->setTitle('D'.$revision->getID())
         ->setBaseURI(new PhutilURI('/D'.$revision->getID()))
         ->setCollapsed((bool)$collapsed)
         ->build($changesets);
     }
 
     // Haunt Mode
     $pane_id = celerity_generate_unique_node_id();
     Javelin::initBehavior(
       'differential-keyboard-navigation',
       array(
         'haunt' => $pane_id,
       ));
     Javelin::initBehavior('differential-user-select');
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
       ->setID($pane_id)
       ->setMainColumn(array(
         $operations_box,
         $info_view,
         $details,
         $diff_detail_box,
         $unit_box,
         $comment_view,
         $signature_message,
       ))
       ->setFooter($footer);
 
     $page =  $this->newPage()
       ->setTitle($object_id.' '.$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'));
+      ->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));
 
     $this->requireResource('phabricator-object-selector-css');
     $this->requireResource('javelin-behavior-phabricator-object-selector');
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-link')
         ->setName(pht('Edit Dependencies'))
         ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/")
         ->setWorkflow(true)
         ->setDisabled(!$can_edit));
 
     $maniphest = 'PhabricatorManiphestApplication';
     if (PhabricatorApplication::isClassInstalled($maniphest)) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setIcon('fa-anchor')
           ->setName(pht('Edit Maniphest Tasks'))
           ->setHref("/search/attach/{$revision_phid}/TASK/")
           ->setWorkflow(true)
           ->setDisabled(!$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')));
 
     return $curtain;
   }
 
   private function buildCommentForm(
     DifferentialRevision $revision,
     $field_list) {
 
     $viewer = $this->getViewer();
 
     $draft = id(new PhabricatorDraft())->loadOneWhere(
       'authorPHID = %s AND draftKey = %s',
       $viewer->getPHID(),
       'differential-comment-'.$revision->getID());
 
     $reviewers = array();
     $ccs = array();
     if ($draft) {
       $reviewers = idx($draft->getMetadata(), 'reviewers', array());
       $ccs = idx($draft->getMetadata(), 'ccs', array());
       if ($reviewers || $ccs) {
         $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
         $reviewers = array_select_keys($handles, $reviewers);
         $ccs = array_select_keys($handles, $ccs);
       }
     }
 
     $comment_form = id(new DifferentialAddCommentView())
       ->setRevision($revision);
 
     $review_warnings = array();
     foreach ($field_list->getFields() as $field) {
       $review_warnings[] = $field->getWarningsForDetailView();
     }
     $review_warnings = array_mergev($review_warnings);
 
     if ($review_warnings) {
       $review_warnings_panel = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
         ->setErrors($review_warnings);
       $comment_form->setInfoView($review_warnings_panel);
     }
 
     $action_uri = $this->getApplicationURI(
       'comment/save/'.$revision->getID().'/');
 
     $comment_form->setActions($this->getRevisionCommentActions($revision))
       ->setActionURI($action_uri)
       ->setUser($viewer)
       ->setDraft($draft)
       ->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'))
       ->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
 
     // TODO: This just makes the "Z" key work. Generalize this and remove
     // it at some point.
     $comment_form = phutil_tag(
       'div',
       array(
         'class' => 'differential-add-comment-panel',
       ),
       $comment_form);
     return $comment_form;
   }
 
   private function getRevisionCommentActions(DifferentialRevision $revision) {
     $actions = array(
       DifferentialAction::ACTION_COMMENT => true,
     );
 
     $viewer = $this->getViewer();
     $viewer_phid = $viewer->getPHID();
     $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
     $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
     $status = $revision->getStatus();
 
     $viewer_has_accepted = false;
     $viewer_has_rejected = false;
     $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
     $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
     foreach ($revision->getReviewerStatus() as $reviewer) {
       if ($reviewer->getReviewerPHID() == $viewer_phid) {
         if ($reviewer->getStatus() == $status_accepted) {
           $viewer_has_accepted = true;
         }
         if ($reviewer->getStatus() == $status_rejected) {
           $viewer_has_rejected = true;
         }
         break;
       }
     }
 
     $allow_self_accept = PhabricatorEnv::getEnvConfig(
       'differential.allow-self-accept');
     $always_allow_abandon = PhabricatorEnv::getEnvConfig(
       'differential.always-allow-abandon');
     $always_allow_close = PhabricatorEnv::getEnvConfig(
       'differential.always-allow-close');
     $allow_reopen = PhabricatorEnv::getEnvConfig(
       'differential.allow-reopen');
 
     if ($viewer_is_owner) {
       switch ($status) {
         case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
           $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
           $actions[DifferentialAction::ACTION_ABANDON] = true;
           $actions[DifferentialAction::ACTION_RETHINK] = true;
           break;
         case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
         case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
           $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
           $actions[DifferentialAction::ACTION_ABANDON] = true;
           $actions[DifferentialAction::ACTION_REQUEST] = true;
           break;
         case ArcanistDifferentialRevisionStatus::ACCEPTED:
           $actions[DifferentialAction::ACTION_ABANDON] = true;
           $actions[DifferentialAction::ACTION_REQUEST] = true;
           $actions[DifferentialAction::ACTION_RETHINK] = true;
           $actions[DifferentialAction::ACTION_CLOSE] = true;
           break;
         case ArcanistDifferentialRevisionStatus::CLOSED:
           break;
         case ArcanistDifferentialRevisionStatus::ABANDONED:
           $actions[DifferentialAction::ACTION_RECLAIM] = true;
           break;
       }
     } else {
       switch ($status) {
         case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
           $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
           $actions[DifferentialAction::ACTION_ACCEPT] = true;
           $actions[DifferentialAction::ACTION_REJECT] = true;
           $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
           break;
         case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
         case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
           $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
           $actions[DifferentialAction::ACTION_ACCEPT] = true;
           $actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
           $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
           break;
         case ArcanistDifferentialRevisionStatus::ACCEPTED:
           $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
           $actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
           $actions[DifferentialAction::ACTION_REJECT] = true;
           $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
           break;
         case ArcanistDifferentialRevisionStatus::CLOSED:
         case ArcanistDifferentialRevisionStatus::ABANDONED:
           break;
       }
       if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
         $actions[DifferentialAction::ACTION_CLAIM] = true;
         $actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
       }
     }
 
     $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
     $actions[DifferentialAction::ACTION_ADDCCS] = true;
     $actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
       ($status == ArcanistDifferentialRevisionStatus::CLOSED);
 
     $actions = array_keys(array_filter($actions));
     $actions_dict = array();
     foreach ($actions as $action) {
       $actions_dict[$action] = DifferentialAction::getActionVerb($action);
     }
 
     return $actions_dict;
   }
 
   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())
       ->setHeader($header)
       ->setRevisions($revisions)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->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),
       );
     }
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DIFF DETAIL'))
+      ->setHeaderText(pht('Diff Detail'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setUser($viewer);
 
     $last_tab = null;
     foreach ($property_lists as $key => $property_list) {
       list($tab_name, $list_view) = $property_list;
 
       $tab = id(new PHUIListItemView())
         ->setKey($key)
         ->setName($tab_name);
 
       $box->addPropertyList($list_view, $tab);
       $last_tab = $tab;
     }
 
     if ($last_tab) {
       $last_tab->setSelected(true);
     }
 
     return $box;
   }
 
   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'))
+      ->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/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
index a79613cfa..7616922aa 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -1,2017 +1,2017 @@
 <?php
 
 final class DiffusionBrowseController extends DiffusionController {
 
   private $lintCommit;
   private $lintMessages;
   private $coverage;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $response = $this->loadDiffusionContext();
     if ($response) {
       return $response;
     }
 
     $drequest = $this->getDiffusionRequest();
 
     // Figure out if we're browsing a directory, a file, or a search result
     // list.
 
     $grep = $request->getStr('grep');
     $find = $request->getStr('find');
     if (strlen($grep) || strlen($find)) {
       return $this->browseSearch();
     }
 
     $pager = id(new PHUIPagerView())
       ->readFromRequest($request);
 
     $results = DiffusionBrowseResultSet::newFromConduit(
       $this->callConduitWithDiffusionRequest(
         'diffusion.browsequery',
         array(
           'path' => $drequest->getPath(),
           'commit' => $drequest->getStableCommit(),
           'offset' => $pager->getOffset(),
           'limit' => $pager->getPageSize() + 1,
         )));
 
     $reason = $results->getReasonForEmptyResultSet();
     $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE);
 
     if ($is_file) {
       return $this->browseFile($results);
     } else {
       $paths = $results->getPaths();
       $paths = $pager->sliceResults($paths);
       $results->setPaths($paths);
 
       return $this->browseDirectory($results, $pager);
     }
   }
 
   private function browseSearch() {
 
     $drequest = $this->getDiffusionRequest();
     $header = $this->buildHeaderView($drequest);
 
     $search_form = $this->renderSearchForm();
     $search_results = $this->renderSearchResults();
 
     $search_form = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Search'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($search_form);
 
     $crumbs = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'browse',
       ));
     $crumbs->setBorder(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setFooter(array(
           $search_form,
           $search_results,
         ));
 
     return $this->newPage()
       ->setTitle(
         array(
           nonempty(basename($drequest->getPath()), '/'),
           $drequest->getRepository()->getDisplayName(),
         ))
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function browseFile() {
     $viewer = $this->getViewer();
     $request = $this->getRequest();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $before = $request->getStr('before');
     if ($before) {
       return $this->buildBeforeResponse($before);
     }
 
     $path = $drequest->getPath();
 
     $preferences = $viewer->loadPreferences();
 
     $show_blame = $request->getBool(
       'blame',
       $preferences->getPreference(
         PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME,
         false));
     $show_color = $request->getBool(
       'color',
       $preferences->getPreference(
         PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR,
         true));
 
     $view = $request->getStr('view');
     if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) {
       $preferences->setPreference(
         PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME,
         $show_blame);
       $preferences->setPreference(
         PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR,
         $show_color);
       $preferences->save();
 
       $uri = $request->getRequestURI()
         ->alter('blame', null)
         ->alter('color', null);
 
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     // We need the blame information if blame is on and we're building plain
     // text, or blame is on and this is an Ajax request. If blame is on and
     // this is a colorized request, we don't show blame at first (we ajax it
     // in afterward) so we don't need to query for it.
     $needs_blame = ($show_blame && !$show_color) ||
                    ($show_blame && $request->isAjax());
 
     $params = array(
       'commit' => $drequest->getCommit(),
       'path' => $drequest->getPath(),
     );
 
     $byte_limit = null;
     if ($view !== 'raw') {
       $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold();
       $time_limit = 10;
 
       $params += array(
         'timeout' => $time_limit,
         'byteLimit' => $byte_limit,
       );
     }
 
     $response = $this->callConduitWithDiffusionRequest(
       'diffusion.filecontentquery',
       $params);
 
     $hit_byte_limit = $response['tooHuge'];
     $hit_time_limit = $response['tooSlow'];
 
     $file_phid = $response['filePHID'];
     if ($hit_byte_limit) {
       $corpus = $this->buildErrorCorpus(
         pht(
           'This file is larger than %s byte(s), and too large to display '.
           'in the web UI.',
           phutil_format_bytes($byte_limit)));
     } else if ($hit_time_limit) {
       $corpus = $this->buildErrorCorpus(
         pht(
           'This file took too long to load from the repository (more than '.
           '%s second(s)).',
           new PhutilNumber($time_limit)));
     } else {
       $file = id(new PhabricatorFileQuery())
         ->setViewer($viewer)
         ->withPHIDs(array($file_phid))
         ->executeOne();
       if (!$file) {
         throw new Exception(pht('Failed to load content file!'));
       }
 
       if ($view === 'raw') {
         return $file->getRedirectResponse();
       }
 
       $data = $file->loadFileData();
 
       $ref = $this->getGitLFSRef($repository, $data);
       if ($ref) {
         if ($view == 'git-lfs') {
           $file = $this->loadGitLFSFile($ref);
 
           // Rename the file locally so we generate a better vanity URI for
           // it. In storage, it just has a name like "lfs-13f9a94c0923...",
           // since we don't get any hints about possible human-readable names
           // at upload time.
           $basename = basename($drequest->getPath());
           $file->makeEphemeral();
           $file->setName($basename);
 
           return $file->getRedirectResponse();
         } else {
           $corpus = $this->buildGitLFSCorpus($ref);
         }
       } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
         $file_uri = $file->getBestURI();
 
         if ($file->isViewableImage()) {
           $corpus = $this->buildImageCorpus($file_uri);
         } else {
           $corpus = $this->buildBinaryCorpus($file_uri, $data);
         }
       } else {
         $this->loadLintMessages();
         $this->coverage = $drequest->loadCoverage();
 
         // Build the content of the file.
         $corpus = $this->buildCorpus(
           $show_blame,
           $show_color,
           $data,
           $needs_blame,
           $drequest,
           $path,
           $data);
       }
     }
 
     if ($request->isAjax()) {
       return id(new AphrontAjaxResponse())->setContent($corpus);
     }
 
     require_celerity_resource('diffusion-source-css');
 
     // Render the page.
     $view = $this->buildCurtain($drequest);
     $curtain = $this->enrichCurtain(
       $view,
       $drequest,
       $show_blame,
       $show_color);
 
     $properties = $this->buildPropertyView($drequest);
     $header = $this->buildHeaderView($drequest);
     $header->setHeaderIcon('fa-file-code-o');
 
     $content = array();
 
     $follow  = $request->getStr('follow');
     if ($follow) {
       $notice = new PHUIInfoView();
       $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING);
       $notice->setTitle(pht('Unable to Continue'));
       switch ($follow) {
         case 'first':
           $notice->appendChild(
             pht(
               'Unable to continue tracing the history of this file because '.
               'this commit is the first commit in the repository.'));
           break;
         case 'created':
           $notice->appendChild(
             pht(
               'Unable to continue tracing the history of this file because '.
               'this commit created the file.'));
           break;
       }
       $content[] = $notice;
     }
 
     $renamed = $request->getStr('renamed');
     if ($renamed) {
       $notice = new PHUIInfoView();
       $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
       $notice->setTitle(pht('File Renamed'));
       $notice->appendChild(
         pht(
           'File history passes through a rename from "%s" to "%s".',
           $drequest->getPath(),
           $renamed));
       $content[] = $notice;
     }
 
     $content[] = $corpus;
     $content[] = $this->buildOpenRevisions();
 
     $crumbs = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'browse',
       ));
     $crumbs->setBorder(true);
 
     $basename = basename($this->getDiffusionRequest()->getPath());
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $content,
         ));
 
     if ($properties) {
-      $view->addPropertySection(pht('DETAILS'), $properties);
+      $view->addPropertySection(pht('Details'), $properties);
     }
 
     $title = array($basename, $repository->getDisplayName());
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $view,
       ));
 
   }
 
   public function browseDirectory(
     DiffusionBrowseResultSet $results,
     PHUIPagerView $pager) {
 
     $request = $this->getRequest();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $reason = $results->getReasonForEmptyResultSet();
 
     $curtain = $this->buildCurtain($drequest);
     $details = $this->buildPropertyView($drequest);
 
     $header = $this->buildHeaderView($drequest);
     $header->setHeaderIcon('fa-folder-open');
 
     $search_form = $this->renderSearchForm();
 
     $empty_result = null;
     $browse_panel = null;
     if (!$results->isValidResults()) {
       $empty_result = new DiffusionEmptyResultView();
       $empty_result->setDiffusionRequest($drequest);
       $empty_result->setDiffusionBrowseResultSet($results);
       $empty_result->setView($request->getStr('view'));
     } else {
       $phids = array();
       foreach ($results->getPaths() as $result) {
         $data = $result->getLastCommitData();
         if ($data) {
           if ($data->getCommitDetail('authorPHID')) {
             $phids[$data->getCommitDetail('authorPHID')] = true;
           }
         }
       }
 
       $phids = array_keys($phids);
       $handles = $this->loadViewerHandles($phids);
 
       $browse_table = id(new DiffusionBrowseTableView())
         ->setDiffusionRequest($drequest)
         ->setHandles($handles)
         ->setPaths($results->getPaths())
         ->setUser($request->getUser());
 
       $browse_header = id(new PHUIHeaderView())
         ->setHeader(nonempty(basename($drequest->getPath()), '/'))
         ->setHeaderIcon('fa-folder-open');
 
       $browse_panel = id(new PHUIObjectBoxView())
         ->setHeader($browse_header)
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->setTable($browse_table);
 
       $browse_panel->setShowHide(
         array(pht('Show Search')),
         pht('Hide Search'),
         $search_form,
         '#');
     }
 
     $open_revisions = $this->buildOpenRevisions();
     $readme = $this->renderDirectoryReadme($results);
 
     $crumbs = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'browse',
       ));
 
     $pager_box = $this->renderTablePagerBox($pager);
     $crumbs->setBorder(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $empty_result,
         $browse_panel,
       ))
       ->setFooter(array(
           $open_revisions,
           $readme,
           $pager_box,
       ));
 
     if ($details) {
-      $view->addPropertySection(pht('DETAILS'), $details);
+      $view->addPropertySection(pht('Details'), $details);
     }
 
     return $this->newPage()
       ->setTitle(array(
           nonempty(basename($drequest->getPath()), '/'),
           $repository->getDisplayName(),
         ))
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $view,
         ));
   }
 
   private function renderSearchResults() {
     $request = $this->getRequest();
 
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $results = array();
 
     $pager = id(new PHUIPagerView())
       ->readFromRequest($request);
 
     $search_mode = null;
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $results = array();
         break;
       default:
         if (strlen($this->getRequest()->getStr('grep'))) {
           $search_mode = 'grep';
           $query_string = $request->getStr('grep');
           $results = $this->callConduitWithDiffusionRequest(
             'diffusion.searchquery',
             array(
               'grep' => $query_string,
               'commit' => $drequest->getStableCommit(),
               'path' => $drequest->getPath(),
               'limit' => $pager->getPageSize() + 1,
               'offset' => $pager->getOffset(),
             ));
         } else { // Filename search.
           $search_mode = 'find';
           $query_string = $request->getStr('find');
           $results = $this->callConduitWithDiffusionRequest(
             'diffusion.querypaths',
             array(
               'pattern' => $query_string,
               'commit' => $drequest->getStableCommit(),
               'path' => $drequest->getPath(),
               'limit' => $pager->getPageSize() + 1,
               'offset' => $pager->getOffset(),
             ));
         }
         break;
     }
     $results = $pager->sliceResults($results);
 
     if ($search_mode == 'grep') {
       $table = $this->renderGrepResults($results, $query_string);
       $header = pht(
         'File content matching "%s" under "%s"',
         $query_string,
         nonempty($drequest->getPath(), '/'));
     } else {
       $table = $this->renderFindResults($results);
       $header = pht(
         'Paths matching "%s" under "%s"',
         $query_string,
         nonempty($drequest->getPath(), '/'));
     }
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($table);
 
     $pager_box = $this->renderTablePagerBox($pager);
 
     return array($box, $pager_box);
   }
 
   private function renderGrepResults(array $results, $pattern) {
     $drequest = $this->getDiffusionRequest();
 
     require_celerity_resource('phabricator-search-results-css');
 
     $rows = array();
     foreach ($results as $result) {
       list($path, $line, $string) = $result;
 
       $href = $drequest->generateURI(array(
         'action' => 'browse',
         'path' => $path,
         'line' => $line,
       ));
 
       $matches = null;
       $count = @preg_match_all(
         '('.$pattern.')u',
         $string,
         $matches,
         PREG_OFFSET_CAPTURE);
 
       if (!$count) {
         $output = ltrim($string);
       } else {
         $output = array();
         $cursor = 0;
         $length = strlen($string);
         foreach ($matches[0] as $match) {
           $offset = $match[1];
           if ($cursor != $offset) {
             $output[] = array(
               'text' => substr($string, $cursor, $offset),
               'highlight' => false,
             );
           }
           $output[] = array(
             'text' => $match[0],
             'highlight' => true,
           );
           $cursor = $offset + strlen($match[0]);
         }
         if ($cursor != $length) {
           $output[] = array(
             'text' => substr($string, $cursor),
             'highlight' => false,
           );
         }
 
         if ($output) {
           $output[0]['text'] =  ltrim($output[0]['text']);
         }
 
         foreach ($output as $key => $segment) {
           if ($segment['highlight']) {
             $output[$key] = phutil_tag('strong', array(), $segment['text']);
           } else {
             $output[$key] = $segment['text'];
           }
         }
       }
 
       $string = phutil_tag(
         'pre',
         array('class' => 'PhabricatorMonospaced phui-source-fragment'),
         $output);
 
       $path = Filesystem::readablePath($path, $drequest->getPath());
 
       $rows[] = array(
         phutil_tag('a', array('href' => $href), $path),
         $line,
         $string,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setClassName('remarkup-code')
       ->setHeaders(array(pht('Path'), pht('Line'), pht('String')))
       ->setColumnClasses(array('', 'n', 'wide'))
       ->setNoDataString(
         pht(
           'The pattern you searched for was not found in the content of any '.
           'files.'));
 
     return $table;
   }
 
   private function renderFindResults(array $results) {
     $drequest = $this->getDiffusionRequest();
 
     $rows = array();
     foreach ($results as $result) {
       $href = $drequest->generateURI(array(
         'action' => 'browse',
         'path' => $result,
       ));
 
       $readable = Filesystem::readablePath($result, $drequest->getPath());
 
       $rows[] = array(
         phutil_tag('a', array('href' => $href), $readable),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(array(pht('Path')))
       ->setColumnClasses(array('wide'))
       ->setNoDataString(
         pht(
           'The pattern you searched for did not match the names of any '.
           'files.'));
 
     return $table;
   }
 
   private function loadLintMessages() {
     $drequest = $this->getDiffusionRequest();
     $branch = $drequest->loadBranch();
 
     if (!$branch || !$branch->getLintCommit()) {
       return;
     }
 
     $this->lintCommit = $branch->getLintCommit();
 
     $conn = id(new PhabricatorRepository())->establishConnection('r');
 
     $where = '';
     if ($drequest->getLint()) {
       $where = qsprintf(
         $conn,
         'AND code = %s',
         $drequest->getLint());
     }
 
     $this->lintMessages = queryfx_all(
       $conn,
       'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s',
       PhabricatorRepository::TABLE_LINTMESSAGE,
       $branch->getID(),
       $where,
       '/'.$drequest->getPath());
   }
 
   private function buildCorpus(
     $show_blame,
     $show_color,
     $file_corpus,
     $needs_blame,
     DiffusionRequest $drequest,
     $path,
     $data) {
 
     $viewer = $this->getViewer();
     $blame_timeout = 15;
     $blame_failed = false;
 
     $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;
     $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;
     $can_highlight = (strlen($file_corpus) <= $highlight_limit);
     $can_blame = (strlen($file_corpus) <= $blame_limit);
 
     if ($needs_blame && $can_blame) {
       $blame = $this->loadBlame($path, $drequest->getCommit(), $blame_timeout);
       list($blame_list, $blame_commits) = $blame;
       if ($blame_list === null) {
         $blame_failed = true;
         $blame_list = array();
       }
     } else {
       $blame_list = array();
       $blame_commits = array();
     }
 
     if (!$show_color) {
       $corpus = $this->renderPlaintextCorpus(
         $file_corpus,
         $blame_list,
         $blame_commits,
         $show_blame);
     } else {
       if ($can_highlight) {
         require_celerity_resource('syntax-highlighting-css');
 
         $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename(
           $path,
           $file_corpus);
         $lines = phutil_split_lines($highlighted);
       } else {
         $lines = phutil_split_lines($file_corpus);
       }
 
       $rows = $this->buildDisplayRows(
         $lines,
         $blame_list,
         $blame_commits,
         $show_blame,
         $show_color);
 
       $corpus_table = javelin_tag(
         'table',
         array(
           'class' => 'diffusion-source remarkup-code PhabricatorMonospaced',
           'sigil' => 'phabricator-source',
         ),
         $rows);
 
       if ($this->getRequest()->isAjax()) {
         return $corpus_table;
       }
 
       $id = celerity_generate_unique_node_id();
 
       $repo = $drequest->getRepository();
       $symbol_repos = nonempty($repo->getSymbolSources(), array());
       $symbol_repos[] = $repo->getPHID();
 
       $lang = last(explode('.', $drequest->getPath()));
       $repo_languages = $repo->getSymbolLanguages();
       $repo_languages = nonempty($repo_languages, array());
       $repo_languages = array_fill_keys($repo_languages, true);
 
       $needs_symbols = true;
       if ($repo_languages && $symbol_repos) {
         $have_symbols = id(new DiffusionSymbolQuery())
             ->existsSymbolsInRepository($repo->getPHID());
         if (!$have_symbols) {
           $needs_symbols = false;
         }
       }
 
       if ($needs_symbols && $repo_languages) {
         $needs_symbols = isset($repo_languages[$lang]);
       }
 
       if ($needs_symbols) {
         Javelin::initBehavior(
           'repository-crossreference',
           array(
             'container' => $id,
             'lang' => $lang,
             'repositories' => $symbol_repos,
           ));
       }
 
       $corpus = phutil_tag(
         'div',
         array(
           'id' => $id,
         ),
         $corpus_table);
 
       Javelin::initBehavior('load-blame', array('id' => $id));
     }
 
     $edit = $this->renderEditButton();
     $file = $this->renderFileButton();
     $header = id(new PHUIHeaderView())
       ->setHeader(basename($this->getDiffusionRequest()->getPath()))
       ->setHeaderIcon('fa-file-code-o')
       ->addActionLink($edit)
       ->addActionLink($file);
 
     $corpus = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($corpus)
       ->setCollapsed(true);
 
     $messages = array();
 
     if (!$can_highlight) {
       $messages[] = pht(
         'This file is larger than %s, so syntax highlighting is disabled '.
         'by default.',
         phutil_format_bytes($highlight_limit));
     }
 
     if ($show_blame && !$can_blame) {
       $messages[] = pht(
         'This file is larger than %s, so blame is disabled.',
         phutil_format_bytes($blame_limit));
     }
 
     if ($blame_failed) {
       $messages[] = pht(
         'Failed to load blame information for this file in %s second(s).',
         new PhutilNumber($blame_timeout));
     }
 
     if ($messages) {
       $corpus->setInfoView(
         id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
           ->setErrors($messages));
     }
 
     return $corpus;
   }
 
   private function enrichCurtain(
     PHUICurtainView $curtain,
     DiffusionRequest $drequest,
     $show_blame,
     $show_color) {
 
     $viewer = $this->getViewer();
     $base_uri = $this->getRequest()->getRequestURI();
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Show Last Change'))
         ->setHref(
           $drequest->generateURI(
             array(
               'action' => 'change',
             )))
         ->setIcon('fa-backward'));
 
     if ($show_blame) {
       $blame_text = pht('Disable Blame');
       $blame_icon = 'fa-exclamation-circle lightgreytext';
       $blame_value = 0;
     } else {
       $blame_text = pht('Enable Blame');
       $blame_icon = 'fa-exclamation-circle';
       $blame_value = 1;
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($blame_text)
         ->setHref($base_uri->alter('blame', $blame_value))
         ->setIcon($blame_icon)
         ->setUser($viewer)
         ->setRenderAsForm($viewer->isLoggedIn()));
 
     if ($show_color) {
       $highlight_text = pht('Disable Highlighting');
       $highlight_icon = 'fa-star-o grey';
       $highlight_value = 0;
     } else {
       $highlight_text = pht('Enable Highlighting');
       $highlight_icon = 'fa-star';
       $highlight_value = 1;
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($highlight_text)
         ->setHref($base_uri->alter('color', $highlight_value))
         ->setIcon($highlight_icon)
         ->setUser($viewer)
         ->setRenderAsForm($viewer->isLoggedIn()));
 
     $href = null;
     if ($this->getRequest()->getStr('lint') !== null) {
       $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages));
       $href = $base_uri->alter('lint', null);
 
     } else if ($this->lintCommit === null) {
       $lint_text = pht('Lint not Available');
     } else {
       $lint_text = pht(
         'Show %d Lint Message(s)',
         count($this->lintMessages));
       $href = $this->getDiffusionRequest()->generateURI(array(
         'action' => 'browse',
         'commit' => $this->lintCommit,
       ))->alter('lint', '');
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($lint_text)
         ->setHref($href)
         ->setIcon('fa-exclamation-triangle')
         ->setDisabled(!$href));
 
 
     $repository = $drequest->getRepository();
 
     $owners = 'PhabricatorOwnersApplication';
     if (PhabricatorApplication::isClassInstalled($owners)) {
       $package_query = id(new PhabricatorOwnersPackageQuery())
         ->setViewer($viewer)
         ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
         ->withControl(
           $repository->getPHID(),
           array(
             $drequest->getPath(),
           ));
 
       $package_query->execute();
 
       $packages = $package_query->getControllingPackagesForPath(
         $repository->getPHID(),
         $drequest->getPath());
 
       if ($packages) {
         $ownership = id(new PHUIStatusListView())
           ->setUser($viewer);
 
         foreach ($packages as $package) {
           $icon = 'fa-list-alt';
           $color = 'grey';
 
           $item = id(new PHUIStatusItemView())
             ->setIcon($icon, $color)
             ->setTarget($viewer->renderHandle($package->getPHID()));
 
           $ownership->addItem($item);
         }
       } else {
         $ownership = phutil_tag('em', array(), pht('None'));
       }
 
       $curtain->newPanel()
         ->setHeaderText(pht('Owners'))
         ->appendChild($ownership);
     }
 
     return $curtain;
   }
 
   private function renderEditButton() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $drequest = $this->getDiffusionRequest();
 
     $repository = $drequest->getRepository();
     $path = $drequest->getPath();
     $line = nonempty((int)$drequest->getLine(), 1);
 
     $editor_link = $user->loadEditorLink($path, $line, $repository);
     $template = $user->loadEditorLink($path, '%l', $repository);
 
     $button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Open in Editor'))
       ->setHref($editor_link)
       ->setIcon('fa-pencil')
       ->setID('editor_link')
       ->setMetadata(array('link_template' => $template))
       ->setDisabled(!$editor_link);
 
     return $button;
   }
 
   private function renderFileButton($file_uri = null, $label = null) {
 
     $base_uri = $this->getRequest()->getRequestURI();
 
     if ($file_uri) {
       $text = pht('Download Raw File');
       $href = $file_uri;
       $icon = 'fa-download';
     } else {
       $text = pht('View Raw File');
       $href = $base_uri->alter('view', 'raw');
       $icon = 'fa-file-text';
     }
 
     if ($label !== null) {
       $text = $label;
     }
 
     $button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText($text)
       ->setHref($href)
       ->setIcon($icon);
 
     return $button;
   }
 
   private function renderGitLFSButton() {
     $viewer = $this->getViewer();
 
     $uri = $this->getRequest()->getRequestURI();
     $href = $uri->alter('view', 'git-lfs');
 
     $text = pht('Download from Git LFS');
     $icon = 'fa-download';
 
     return id(new PHUIButtonView())
       ->setTag('a')
       ->setText($text)
       ->setHref($href)
       ->setIcon($icon);
   }
 
   private function buildDisplayRows(
     array $lines,
     array $blame_list,
     array $blame_commits,
     $show_blame,
     $show_color) {
 
     $request = $this->getRequest();
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $revision_map = array();
     $revisions = array();
     if ($blame_commits) {
       $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID');
 
       $revision_ids = id(new DifferentialRevision())
         ->loadIDsByCommitPHIDs(array_keys($commit_map));
       if ($revision_ids) {
         $revisions = id(new DifferentialRevisionQuery())
           ->setViewer($viewer)
           ->withIDs($revision_ids)
           ->execute();
         $revisions = mpull($revisions, null, 'getID');
       }
 
       foreach ($revision_ids as $commit_phid => $revision_id) {
         $revision_map[$commit_map[$commit_phid]] = $revision_id;
       }
     }
 
     $phids = array();
     foreach ($blame_commits as $commit) {
       $author_phid = $commit->getAuthorPHID();
       if ($author_phid === null) {
         continue;
       }
       $phids[$author_phid] = $author_phid;
     }
 
     foreach ($revisions as $revision) {
       $author_phid = $revision->getAuthorPHID();
       if ($author_phid === null) {
         continue;
       }
       $phids[$author_phid] = $author_phid;
     }
 
     $handles = $viewer->loadHandles($phids);
 
     $colors = array();
     if ($blame_commits) {
       $epochs = array();
 
       foreach ($blame_commits as $identifier => $commit) {
         $epochs[$identifier] = $commit->getEpoch();
       }
 
       $epoch_list = array_filter($epochs);
       $epoch_list = array_unique($epoch_list);
       $epoch_list = array_values($epoch_list);
 
       $epoch_min   = min($epoch_list);
       $epoch_max   = max($epoch_list);
       $epoch_range = ($epoch_max - $epoch_min) + 1;
 
       foreach ($blame_commits as $identifier => $commit) {
         $epoch = $epochs[$identifier];
         if (!$epoch) {
           $color = '#ffffdd'; // Warning color, missing data.
         } else {
           $color_ratio = ($epoch - $epoch_min) / $epoch_range;
           $color_value = 0xE6 * (1.0 - $color_ratio);
           $color = sprintf(
             '#%02x%02x%02x',
             $color_value,
             0xF6,
             $color_value);
         }
 
         $colors[$identifier] = $color;
       }
     }
 
     $display = array();
     $last_identifier = null;
     $last_color = null;
     foreach ($lines as $line_index => $line) {
       $color = '#f6f6f6';
       $duplicate = false;
       if (isset($blame_list[$line_index])) {
         $identifier = $blame_list[$line_index];
         if (isset($colors[$identifier])) {
           $color = $colors[$identifier];
         }
 
         if ($identifier === $last_identifier) {
           $duplicate = true;
         } else {
           $last_identifier = $identifier;
         }
       }
 
       $display[$line_index] = array(
         'data' => $line,
         'target' => false,
         'highlighted' => false,
         'color' => $color,
         'duplicate' => $duplicate,
       );
     }
 
     $line_arr = array();
     $line_str = $drequest->getLine();
     $ranges = explode(',', $line_str);
     foreach ($ranges as $range) {
       if (strpos($range, '-') !== false) {
         list($min, $max) = explode('-', $range, 2);
         $line_arr[] = array(
           'min' => min($min, $max),
           'max' => max($min, $max),
         );
       } else if (strlen($range)) {
         $line_arr[] = array(
           'min' => $range,
           'max' => $range,
         );
       }
     }
 
     // Mark the first highlighted line as the target line.
     if ($line_arr) {
       $target_line = $line_arr[0]['min'];
       if (isset($display[$target_line - 1])) {
         $display[$target_line - 1]['target'] = true;
       }
     }
 
     // Mark all other highlighted lines as highlighted.
     foreach ($line_arr as $range) {
       for ($ii = $range['min']; $ii <= $range['max']; $ii++) {
         if (isset($display[$ii - 1])) {
           $display[$ii - 1]['highlighted'] = true;
         }
       }
     }
 
     $engine = null;
     $inlines = array();
     if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
       $engine = new PhabricatorMarkupEngine();
       $engine->setViewer($viewer);
 
       foreach ($this->lintMessages as $message) {
         $inline = id(new PhabricatorAuditInlineComment())
           ->setSyntheticAuthor(
             ArcanistLintSeverity::getStringForSeverity($message['severity']).
             ' '.$message['code'].' ('.$message['name'].')')
           ->setLineNumber($message['line'])
           ->setContent($message['description']);
         $inlines[$message['line']][] = $inline;
 
         $engine->addObject(
           $inline,
           PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
       }
 
       $engine->process();
       require_celerity_resource('differential-changeset-view-css');
     }
 
     $rows = $this->renderInlines(
       idx($inlines, 0, array()),
       $show_blame,
       (bool)$this->coverage,
       $engine);
 
     // NOTE: We're doing this manually because rendering is otherwise
     // dominated by URI generation for very large files.
     $line_base = (string)$drequest->generateURI(
       array(
         'action'  => 'browse',
         'stable'  => true,
       ));
 
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-oncopy');
     Javelin::initBehavior('phabricator-tooltips');
     Javelin::initBehavior('phabricator-line-linker');
 
     // Render these once, since they tend to get repeated many times in large
     // blame outputs.
     $commit_links = $this->renderCommitLinks($blame_commits, $handles);
     $revision_links = $this->renderRevisionLinks($revisions, $handles);
 
     $skip_text = pht('Skip Past This Commit');
     foreach ($display as $line_index => $line) {
       $row = array();
 
       $line_number = $line_index + 1;
       $line_href = $line_base.'$'.$line_number;
 
       if (isset($blame_list[$line_index])) {
         $identifier = $blame_list[$line_index];
       } else {
         $identifier = null;
       }
 
       $revision_link = null;
       $commit_link = null;
       $before_link = null;
 
       $style = 'background: '.$line['color'].';';
 
       if ($identifier && !$line['duplicate']) {
         if (isset($commit_links[$identifier])) {
           $commit_link = $commit_links[$identifier];
         }
 
         if (isset($revision_map[$identifier])) {
           $revision_id = $revision_map[$identifier];
           if (isset($revision_links[$revision_id])) {
             $revision_link = $revision_links[$revision_id];
           }
         }
 
         $skip_href = $line_href.'?before='.$identifier.'&view=blame';
         $before_link = javelin_tag(
           'a',
           array(
             'href'  => $skip_href,
             'sigil' => 'has-tooltip',
             'meta'  => array(
               'tip'     => $skip_text,
               'align'   => 'E',
               'size'    => 300,
             ),
           ),
           "\xC2\xAB");
       }
 
       if ($show_blame) {
         $row[] = phutil_tag(
           'th',
           array(
             'class' => 'diffusion-blame-link',
           ),
           $before_link);
 
         $object_links = array();
         $object_links[] = $commit_link;
         if ($revision_link) {
           $object_links[] = phutil_tag('span', array(), '/');
           $object_links[] = $revision_link;
         }
 
         $row[] = phutil_tag(
           'th',
           array(
             'class' => 'diffusion-rev-link',
           ),
           $object_links);
       }
 
       $line_link = phutil_tag(
         'a',
         array(
           'href' => $line_href,
           'style' => $style,
         ),
         $line_number);
 
       $row[] = javelin_tag(
         'th',
         array(
           'class' => 'diffusion-line-link',
           'sigil' => 'phabricator-source-line',
           'style' => $style,
         ),
         $line_link);
 
       if ($line['target']) {
         Javelin::initBehavior(
           'diffusion-jump-to',
           array(
             'target' => 'scroll_target',
           ));
         $anchor_text = phutil_tag(
           'a',
           array(
             'id' => 'scroll_target',
           ),
           '');
       } else {
         $anchor_text = null;
       }
 
       $row[] = phutil_tag(
         'td',
         array(
         ),
         array(
           $anchor_text,
 
           // NOTE: See phabricator-oncopy behavior.
           "\xE2\x80\x8B",
 
           // TODO: [HTML] Not ideal.
           phutil_safe_html(str_replace("\t", '  ', $line['data'])),
         ));
 
       if ($this->coverage) {
         require_celerity_resource('differential-changeset-view-css');
         $cov_index = $line_index;
 
         if (isset($this->coverage[$cov_index])) {
           $cov_class = $this->coverage[$cov_index];
         } else {
           $cov_class = 'N';
         }
 
         $row[] = phutil_tag(
           'td',
           array(
             'class' => 'cov cov-'.$cov_class,
           ),
           '');
       }
 
       $rows[] = phutil_tag(
         'tr',
         array(
           'class' => ($line['highlighted'] ?
                       'phabricator-source-highlight' :
                       null),
         ),
         $row);
 
       $cur_inlines = $this->renderInlines(
         idx($inlines, $line_number, array()),
         $show_blame,
         $this->coverage,
         $engine);
       foreach ($cur_inlines as $cur_inline) {
         $rows[] = $cur_inline;
       }
     }
 
     return $rows;
   }
 
   private function renderInlines(
     array $inlines,
     $show_blame,
     $has_coverage,
     $engine) {
 
     $rows = array();
     foreach ($inlines as $inline) {
 
       // TODO: This should use modern scaffolding code.
 
       $inline_view = id(new PHUIDiffInlineCommentDetailView())
         ->setUser($this->getViewer())
         ->setMarkupEngine($engine)
         ->setInlineComment($inline)
         ->render();
 
       $row = array_fill(0, ($show_blame ? 3 : 1), phutil_tag('th'));
 
       $row[] = phutil_tag('td', array(), $inline_view);
 
       if ($has_coverage) {
         $row[] = phutil_tag(
           'td',
           array(
             'class' => 'cov cov-I',
           ));
       }
 
       $rows[] = phutil_tag('tr', array('class' => 'inline'), $row);
     }
 
     return $rows;
   }
 
   private function buildImageCorpus($file_uri) {
     $properties = new PHUIPropertyListView();
 
     $properties->addImageContent(
       phutil_tag(
         'img',
         array(
           'src' => $file_uri,
         )));
 
     $file = $this->renderFileButton($file_uri);
     $header = id(new PHUIHeaderView())
       ->setHeader(basename($this->getDiffusionRequest()->getPath()))
       ->addActionLink($file)
       ->setHeaderIcon('fa-file-image-o');
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($properties);
   }
 
   private function buildBinaryCorpus($file_uri, $data) {
     $size = new PhutilNumber(strlen($data));
     $text = pht('This is a binary file. It is %s byte(s) in length.', $size);
     $text = id(new PHUIBoxView())
       ->addPadding(PHUI::PADDING_LARGE)
       ->appendChild($text);
 
     $file = $this->renderFileButton($file_uri);
     $header = id(new PHUIHeaderView())
-      ->setHeader(pht('DETAILS'))
+      ->setHeader(pht('Details'))
       ->addActionLink($file);
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($text);
 
     return $box;
   }
 
   private function buildErrorCorpus($message) {
     $text = id(new PHUIBoxView())
       ->addPadding(PHUI::PADDING_LARGE)
       ->appendChild($message);
 
     $header = id(new PHUIHeaderView())
-      ->setHeader(pht('DETAILS'));
+      ->setHeader(pht('Details'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild($text);
 
     return $box;
   }
 
   private function buildBeforeResponse($before) {
     $request = $this->getRequest();
     $drequest = $this->getDiffusionRequest();
 
     // NOTE: We need to get the grandparent so we can capture filename changes
     // in the parent.
 
     $parent = $this->loadParentCommitOf($before);
     $old_filename = null;
     $was_created = false;
     if ($parent) {
       $grandparent = $this->loadParentCommitOf($parent);
 
       if ($grandparent) {
         $rename_query = new DiffusionRenameHistoryQuery();
         $rename_query->setRequest($drequest);
         $rename_query->setOldCommit($grandparent);
         $rename_query->setViewer($request->getUser());
         $old_filename = $rename_query->loadOldFilename();
         $was_created = $rename_query->getWasCreated();
       }
     }
 
     $follow = null;
     if ($was_created) {
       // If the file was created in history, that means older commits won't
       // have it. Since we know it existed at 'before', it must have been
       // created then; jump there.
       $target_commit = $before;
       $follow = 'created';
     } else if ($parent) {
       // If we found a parent, jump to it. This is the normal case.
       $target_commit = $parent;
     } else {
       // If there's no parent, this was probably created in the initial commit?
       // And the "was_created" check will fail because we can't identify the
       // grandparent. Keep the user at 'before'.
       $target_commit = $before;
       $follow = 'first';
     }
 
     $path = $drequest->getPath();
     $renamed = null;
     if ($old_filename !== null &&
         $old_filename !== '/'.$path) {
       $renamed = $path;
       $path = $old_filename;
     }
 
     $line = null;
     // If there's a follow error, drop the line so the user sees the message.
     if (!$follow) {
       $line = $this->getBeforeLineNumber($target_commit);
     }
 
     $before_uri = $drequest->generateURI(
       array(
         'action'    => 'browse',
         'commit'    => $target_commit,
         'line'      => $line,
         'path'      => $path,
       ));
 
     $before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
     $before_uri = $before_uri->alter('before', null);
     $before_uri = $before_uri->alter('renamed', $renamed);
     $before_uri = $before_uri->alter('follow', $follow);
 
     return id(new AphrontRedirectResponse())->setURI($before_uri);
   }
 
   private function getBeforeLineNumber($target_commit) {
     $drequest = $this->getDiffusionRequest();
 
     $line = $drequest->getLine();
     if (!$line) {
       return null;
     }
 
     $raw_diff = $this->callConduitWithDiffusionRequest(
       'diffusion.rawdiffquery',
       array(
         'commit' => $drequest->getCommit(),
         'path' => $drequest->getPath(),
         'againstCommit' => $target_commit,
       ));
     $old_line = 0;
     $new_line = 0;
 
     foreach (explode("\n", $raw_diff) as $text) {
       if ($text[0] == '-' || $text[0] == ' ') {
         $old_line++;
       }
       if ($text[0] == '+' || $text[0] == ' ') {
         $new_line++;
       }
       if ($new_line == $line) {
         return $old_line;
       }
     }
 
     // We didn't find the target line.
     return $line;
   }
 
   private function loadParentCommitOf($commit) {
     $drequest = $this->getDiffusionRequest();
     $user = $this->getRequest()->getUser();
 
     $before_req = DiffusionRequest::newFromDictionary(
       array(
         'user' => $user,
         'repository' => $drequest->getRepository(),
         'commit' => $commit,
       ));
 
     $parents = DiffusionQuery::callConduitWithDiffusionRequest(
       $user,
       $before_req,
       'diffusion.commitparentsquery',
       array(
         'commit' => $commit,
       ));
 
     return head($parents);
   }
 
   private function renderRevisionTooltip(
     DifferentialRevision $revision,
     $handles) {
     $viewer = $this->getRequest()->getUser();
 
     $date = phabricator_date($revision->getDateModified(), $viewer);
     $id = $revision->getID();
     $title = $revision->getTitle();
     $header = "D{$id} {$title}";
 
     $author = $handles[$revision->getAuthorPHID()]->getName();
 
     return "{$header}\n{$date} \xC2\xB7 {$author}";
   }
 
   private function renderCommitTooltip(
     PhabricatorRepositoryCommit $commit,
     $author) {
 
     $viewer = $this->getRequest()->getUser();
 
     $date = phabricator_date($commit->getEpoch(), $viewer);
     $summary = trim($commit->getSummary());
 
     return "{$summary}\n{$date} \xC2\xB7 {$author}";
   }
 
   protected function renderSearchForm() {
     $drequest = $this->getDiffusionRequest();
 
     $forms = array();
     $form = id(new AphrontFormView())
       ->setUser($this->getViewer())
       ->setMethod('GET');
 
     switch ($drequest->getRepository()->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $forms[] = id(clone $form)
           ->appendChild(pht('Search is not available in Subversion.'));
         break;
       default:
         $forms[] = id(clone $form)
           ->appendChild(
             id(new AphrontFormTextWithSubmitControl())
               ->setLabel(pht('File Name'))
               ->setSubmitLabel(pht('Search File Names'))
               ->setName('find')
               ->setValue($this->getRequest()->getStr('find')));
         $forms[] = id(clone $form)
           ->appendChild(
             id(new AphrontFormTextWithSubmitControl())
               ->setLabel(pht('Pattern'))
               ->setSubmitLabel(pht('Grep File Content'))
               ->setName('grep')
               ->setValue($this->getRequest()->getStr('grep')));
         break;
     }
 
     require_celerity_resource('diffusion-icons-css');
     $form_box = phutil_tag_div('diffusion-search-boxen', $forms);
 
     return $form_box;
   }
 
   protected function markupText($text) {
     $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
     $engine->setConfig('viewer', $this->getRequest()->getUser());
     $text = $engine->markupText($text);
 
     $text = phutil_tag(
       'div',
       array(
         'class' => 'phabricator-remarkup',
       ),
       $text);
 
     return $text;
   }
 
   protected function buildHeaderView(DiffusionRequest $drequest) {
     $viewer = $this->getViewer();
 
     $tag = $this->renderCommitHashTag($drequest);
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($this->renderPathLinks($drequest, $mode = 'browse'))
       ->addTag($tag);
 
     return $header;
   }
 
   protected function buildCurtain(DiffusionRequest $drequest) {
     $viewer = $this->getViewer();
 
     $curtain = $this->newCurtainView($drequest);
 
     $history_uri = $drequest->generateURI(
       array(
         'action' => 'history',
       ));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View History'))
         ->setHref($history_uri)
         ->setIcon('fa-list'));
 
     $behind_head = $drequest->getSymbolicCommit();
     $head_uri = $drequest->generateURI(
       array(
         'commit' => '',
         'action' => 'browse',
       ));
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Jump to HEAD'))
         ->setHref($head_uri)
         ->setIcon('fa-home')
         ->setDisabled(!$behind_head));
 
     return $curtain;
   }
 
   protected function buildPropertyView(
     DiffusionRequest $drequest) {
 
     $viewer = $this->getViewer();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     if ($drequest->getSymbolicType() == 'tag') {
       $symbolic = $drequest->getSymbolicCommit();
       $view->addProperty(pht('Tag'), $symbolic);
 
       $tags = $this->callConduitWithDiffusionRequest(
         'diffusion.tagsquery',
         array(
           'names' => array($symbolic),
           'needMessages' => true,
         ));
       $tags = DiffusionRepositoryTag::newFromConduit($tags);
 
       $tags = mpull($tags, null, 'getName');
       $tag = idx($tags, $symbolic);
 
       if ($tag && strlen($tag->getMessage())) {
         $view->addSectionHeader(
           pht('Tag Content'), 'fa-tag');
         $view->addTextContent($this->markupText($tag->getMessage()));
       }
     }
 
     if ($view->hasAnyProperties()) {
       return $view;
     }
 
     return null;
   }
 
   private function buildOpenRevisions() {
     $viewer = $this->getViewer();
 
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $path = $drequest->getPath();
 
     $path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
     $path_id = idx($path_map, $path);
     if (!$path_id) {
       return null;
     }
 
     $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
 
     $revisions = id(new DifferentialRevisionQuery())
       ->setViewer($viewer)
       ->withPath($repository->getID(), $path_id)
       ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
       ->withUpdatedEpochBetween($recent, null)
       ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
       ->setLimit(10)
       ->needRelationships(true)
       ->needFlags(true)
       ->needDrafts(true)
       ->execute();
 
     if (!$revisions) {
       return null;
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Open Revisions'))
       ->setSubheader(
         pht('Recently updated open revisions affecting this file.'));
 
     $view = id(new DifferentialRevisionListView())
       ->setHeader($header)
       ->setRevisions($revisions)
       ->setUser($viewer);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     return $view;
   }
 
   private function loadBlame($path, $commit, $timeout) {
     $blame = $this->callConduitWithDiffusionRequest(
       'diffusion.blame',
       array(
         'commit' => $commit,
         'paths' => array($path),
         'timeout' => $timeout,
       ));
 
     $identifiers = idx($blame, $path, null);
 
     if ($identifiers) {
       $viewer = $this->getViewer();
       $drequest = $this->getDiffusionRequest();
       $repository = $drequest->getRepository();
 
       $commits = id(new DiffusionCommitQuery())
         ->setViewer($viewer)
         ->withRepository($repository)
         ->withIdentifiers($identifiers)
         // TODO: We only fetch this to improve author display behavior, but
         // shouldn't really need to?
         ->needCommitData(true)
         ->execute();
       $commits = mpull($commits, null, 'getCommitIdentifier');
     } else {
       $commits = array();
     }
 
     return array($identifiers, $commits);
   }
 
   private function renderCommitLinks(array $commits, $handles) {
     $links = array();
     foreach ($commits as $identifier => $commit) {
       $tooltip = $this->renderCommitTooltip(
         $commit,
         $commit->renderAuthorShortName($handles));
 
       $commit_link = javelin_tag(
         'a',
         array(
           'href' => $commit->getURI(),
           'sigil' => 'has-tooltip',
           'meta'  => array(
             'tip'   => $tooltip,
             'align' => 'E',
             'size'  => 600,
           ),
         ),
         $commit->getLocalName());
 
       $links[$identifier] = $commit_link;
     }
 
     return $links;
   }
 
   private function renderRevisionLinks(array $revisions, $handles) {
     $links = array();
 
     foreach ($revisions as $revision) {
       $revision_id = $revision->getID();
 
       $tooltip = $this->renderRevisionTooltip($revision, $handles);
 
       $revision_link = javelin_tag(
         'a',
         array(
           'href' => '/'.$revision->getMonogram(),
           'sigil' => 'has-tooltip',
           'meta'  => array(
             'tip'   => $tooltip,
             'align' => 'E',
             'size'  => 600,
           ),
         ),
         $revision->getMonogram());
 
       $links[$revision_id] = $revision_link;
     }
 
     return $links;
   }
 
   private function renderPlaintextCorpus(
     $file_corpus,
     array $blame_list,
     array $blame_commits,
     $show_blame) {
 
     $viewer = $this->getViewer();
 
     if (!$show_blame) {
       $corpus = $file_corpus;
     } else {
       $author_phids = array();
       foreach ($blame_commits as $commit) {
         $author_phid = $commit->getAuthorPHID();
         if ($author_phid === null) {
           continue;
         }
         $author_phids[$author_phid] = $author_phid;
       }
 
       if ($author_phids) {
         $handles = $viewer->loadHandles($author_phids);
       } else {
         $handles = array();
       }
 
       $authors = array();
       $names = array();
       foreach ($blame_commits as $identifier => $commit) {
         $author = $commit->renderAuthorShortName($handles);
         $name = $commit->getLocalName();
 
         $authors[$identifier] = $author;
         $names[$identifier] = $name;
       }
 
       $lines = phutil_split_lines($file_corpus);
 
       $rows = array();
       foreach ($lines as $line_number => $line) {
         $commit_name = null;
         $author = null;
 
         if (isset($blame_list[$line_number])) {
           $identifier = $blame_list[$line_number];
 
           if (isset($names[$identifier])) {
             $commit_name = $names[$identifier];
           }
 
           if (isset($authors[$identifier])) {
             $author = $authors[$identifier];
           }
         }
 
         $rows[] = sprintf(
           '%-10s %-20s %s',
           $commit_name,
           $author,
           $line);
       }
       $corpus = implode('', $rows);
     }
 
     return phutil_tag(
       'textarea',
       array(
         'style' => 'border: none; width: 100%; height: 80em; '.
                    'font-family: monospace',
       ),
       $corpus);
   }
 
   private function getGitLFSRef(PhabricatorRepository $repository, $data) {
     if (!$repository->canUseGitLFS()) {
       return null;
     }
 
     $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])';
     if (!preg_match($lfs_pattern, $data)) {
       return null;
     }
 
     $matches = null;
     if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) {
       return null;
     }
 
     $hash = $matches[1];
     $hash = trim($hash);
 
     return id(new PhabricatorRepositoryGitLFSRefQuery())
       ->setViewer($this->getViewer())
       ->withRepositoryPHIDs(array($repository->getPHID()))
       ->withObjectHashes(array($hash))
       ->executeOne();
   }
 
   private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) {
     // TODO: We should probably test if we can load the file PHID here and
     // show the user an error if we can't, rather than making them click
     // through to hit an error.
 
     $header = id(new PHUIHeaderView())
       ->setHeader(basename($this->getDiffusionRequest()->getPath()))
       ->setHeaderIcon('fa-archive');
 
     $severity = PHUIInfoView::SEVERITY_NOTICE;
 
     $messages = array();
     $messages[] = pht(
       'This %s file is stored in Git Large File Storage.',
       phutil_format_bytes($ref->getByteSize()));
 
     try {
       $file = $this->loadGitLFSFile($ref);
       $data = $this->renderGitLFSButton();
       $header->addActionLink($data);
     } catch (Exception $ex) {
       $severity = PHUIInfoView::SEVERITY_ERROR;
       $messages[] = pht('The data for this file could not be loaded.');
     }
 
     $raw = $this->renderFileButton(null, pht('View Raw LFS Pointer'));
     $header->addActionLink($raw);
 
     $corpus = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setCollapsed(true);
 
     if ($messages) {
       $corpus->setInfoView(
         id(new PHUIInfoView())
           ->setSeverity($severity)
           ->setErrors($messages));
     }
 
     return $corpus;
   }
 
   private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) {
     $viewer = $this->getViewer();
 
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($ref->getFilePHID()))
       ->executeOne();
     if (!$file) {
       throw new Exception(
         pht(
           'Failed to load file object for Git LFS ref "%s"!',
           $ref->getObjectHash()));
     }
 
     return $file;
   }
 
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index 890c51287..e2f0d51f4 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,1287 +1,1286 @@
 <?php
 
 final class DiffusionCommitController extends DiffusionController {
 
   const CHANGES_LIMIT = 100;
 
   private $auditAuthorityPHIDs;
   private $highlightedAudits;
 
   private $commitParents;
   private $commitRefs;
   private $commitMerges;
   private $commitErrors;
   private $commitExists;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $response = $this->loadDiffusionContext();
     if ($response) {
       return $response;
     }
 
     $drequest = $this->getDiffusionRequest();
     $viewer = $request->getUser();
 
     if ($request->getStr('diff')) {
       return $this->buildRawDiffResponse($drequest);
     }
 
     $repository = $drequest->getRepository();
 
     $commit = id(new DiffusionCommitQuery())
       ->setViewer($viewer)
       ->withRepository($repository)
       ->withIdentifiers(array($drequest->getCommit()))
       ->needCommitData(true)
       ->needAuditRequests(true)
       ->executeOne();
 
     $crumbs = $this->buildCrumbs(array(
       'commit' => true,
     ));
     $crumbs->setBorder(true);
 
     if (!$commit) {
       if (!$this->getCommitExists()) {
         return new Aphront404Response();
       }
 
       $error = id(new PHUIInfoView())
         ->setTitle(pht('Commit Still Parsing'))
         ->appendChild(
           pht(
             'Failed to load the commit because the commit has not been '.
             'parsed yet.'));
 
-      return $this->buildApplicationPage(
-        array(
-          $crumbs,
-          $error,
-        ),
-        array(
-          'title' => pht('Commit Still Parsing'),
-        ));
+      $title = pht('Commit Still Parsing');
+
+      return $this->newPage()
+        ->setTitle($title)
+        ->setCrumbs($crumbs)
+        ->appendChild($error);
+
     }
 
     $audit_requests = $commit->getAudits();
     $this->auditAuthorityPHIDs =
       PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer);
 
     $commit_data = $commit->getCommitData();
     $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
     $error_panel = null;
     if ($is_foreign) {
       $subpath = $commit_data->getCommitDetail('svn-subpath');
 
       $error_panel = new PHUIInfoView();
       $error_panel->setTitle(pht('Commit Not Tracked'));
       $error_panel->setSeverity(PHUIInfoView::SEVERITY_WARNING);
       $error_panel->appendChild(
         pht(
           "This Diffusion repository is configured to track only one ".
           "subdirectory of the entire Subversion repository, and this commit ".
           "didn't affect the tracked subdirectory ('%s'), so no ".
           "information is available.",
           $subpath));
     } else {
       $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
       $engine->setConfig('viewer', $viewer);
 
       $commit_tag = $this->renderCommitHashTag($drequest);
       $header = id(new PHUIHeaderView())
         ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')))
         ->setHeaderIcon('fa-code-fork')
         ->addTag($commit_tag);
 
       if ($commit->getAuditStatus()) {
         $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon(
           $commit->getAuditStatus());
         $color = PhabricatorAuditCommitStatusConstants::getStatusColor(
           $commit->getAuditStatus());
         $status = PhabricatorAuditCommitStatusConstants::getStatusName(
           $commit->getAuditStatus());
 
         $header->setStatus($icon, $color, $status);
       }
 
       $curtain = $this->buildCurtain($commit, $repository);
       $subheader = $this->buildSubheaderView($commit, $commit_data);
       $details = $this->buildPropertyListView(
         $commit,
         $commit_data,
         $audit_requests);
 
       $message = $commit_data->getCommitMessage();
 
       $revision = $commit->getCommitIdentifier();
       $message = $this->linkBugtraq($message);
       $message = $engine->markupText($message);
 
       $detail_list = new PHUIPropertyListView();
       $detail_list->addTextContent(
         phutil_tag(
           'div',
           array(
             'class' => 'diffusion-commit-message phabricator-remarkup',
           ),
           $message));
 
       if ($this->getCommitErrors()) {
         $error_panel = id(new PHUIInfoView())
           ->appendChild($this->getCommitErrors())
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING);
       }
     }
 
     $timeline = $this->buildComments($commit);
     $hard_limit = 1000;
 
     if ($commit->isImported()) {
       $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
         $drequest);
       $change_query->setLimit($hard_limit + 1);
       $changes = $change_query->loadChanges();
     } else {
       $changes = array();
     }
 
     $was_limited = (count($changes) > $hard_limit);
     if ($was_limited) {
       $changes = array_slice($changes, 0, $hard_limit);
     }
 
     $merge_table = $this->buildMergesTable($commit);
 
     $highlighted_audits = $commit->getAuthorityAudits(
       $viewer,
       $this->auditAuthorityPHIDs);
 
     $count = count($changes);
 
     $bad_commit = null;
     if ($count == 0) {
       $bad_commit = queryfx_one(
         id(new PhabricatorRepository())->establishConnection('r'),
         'SELECT * FROM %T WHERE fullCommitName = %s',
         PhabricatorRepository::TABLE_BADCOMMIT,
         $commit->getMonogram());
     }
 
     $show_changesets = false;
     $info_panel = null;
     $change_list = null;
     $change_table = null;
     if ($bad_commit) {
       $info_panel = $this->renderStatusMessage(
         pht('Bad Commit'),
         $bad_commit['description']);
     } else if ($is_foreign) {
       // Don't render anything else.
     } else if (!$commit->isImported()) {
       $info_panel = $this->renderStatusMessage(
         pht('Still Importing...'),
         pht(
           'This commit is still importing. Changes will be visible once '.
           'the import finishes.'));
     } else if (!count($changes)) {
       $info_panel = $this->renderStatusMessage(
         pht('Empty Commit'),
         pht(
           'This commit is empty and does not affect any paths.'));
     } else if ($was_limited) {
       $info_panel = $this->renderStatusMessage(
         pht('Enormous Commit'),
         pht(
           'This commit is enormous, and affects more than %d files. '.
           'Changes are not shown.',
           $hard_limit));
     } else if (!$this->getCommitExists()) {
       $info_panel = $this->renderStatusMessage(
         pht('Commit No Longer Exists'),
         pht('This commit no longer exists in the repository.'));
     } else {
       $show_changesets = true;
 
       // The user has clicked "Show All Changes", and we should show all the
       // changes inline even if there are more than the soft limit.
       $show_all_details = $request->getBool('show_all');
 
       $change_header = id(new PHUIHeaderView())
         ->setHeader(pht('Changes (%s)', new PhutilNumber($count)));
 
       $warning_view = null;
       if ($count > self::CHANGES_LIMIT && !$show_all_details) {
         $button = id(new PHUIButtonView())
           ->setText(pht('Show All Changes'))
           ->setHref('?show_all=true')
           ->setTag('a')
           ->setIcon('fa-files-o');
 
         $warning_view = id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
           ->setTitle(pht('Very Large Commit'))
           ->appendChild(
             pht('This commit is very large. Load each file individually.'));
 
         $change_header->addActionLink($button);
       }
 
       $changesets = DiffusionPathChange::convertToDifferentialChangesets(
         $viewer,
         $changes);
 
       // TODO: This table and panel shouldn't really be separate, but we need
       // to clean up the "Load All Files" interaction first.
       $change_table = $this->buildTableOfContents(
         $changesets,
         $change_header,
         $warning_view);
 
       $vcs = $repository->getVersionControlSystem();
       switch ($vcs) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $vcs_supports_directory_changes = true;
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $vcs_supports_directory_changes = false;
           break;
         default:
           throw new Exception(pht('Unknown VCS.'));
       }
 
       $references = array();
       foreach ($changesets as $key => $changeset) {
         $file_type = $changeset->getFileType();
         if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
           if (!$vcs_supports_directory_changes) {
             unset($changesets[$key]);
             continue;
           }
         }
 
         $references[$key] = $drequest->generateURI(
           array(
             'action' => 'rendering-ref',
             'path'   => $changeset->getFilename(),
           ));
       }
 
       // TODO: Some parts of the views still rely on properties of the
       // DifferentialChangeset. Make the objects ephemeral to make sure we don't
       // accidentally save them, and then set their ID to the appropriate ID for
       // this application (the path IDs).
       $path_ids = array_flip(mpull($changes, 'getPath'));
       foreach ($changesets as $changeset) {
         $changeset->makeEphemeral();
         $changeset->setID($path_ids[$changeset->getFilename()]);
       }
 
       if ($count <= self::CHANGES_LIMIT || $show_all_details) {
         $visible_changesets = $changesets;
       } else {
         $visible_changesets = array();
         $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments(
           $viewer,
           $commit->getPHID());
         $path_ids = mpull($inlines, null, 'getPathID');
         foreach ($changesets as $key => $changeset) {
           if (array_key_exists($changeset->getID(), $path_ids)) {
             $visible_changesets[$key] = $changeset;
           }
         }
       }
 
       $change_list_title = $commit->getDisplayName();
 
       $change_list = new DifferentialChangesetListView();
       $change_list->setTitle($change_list_title);
       $change_list->setChangesets($changesets);
       $change_list->setVisibleChangesets($visible_changesets);
       $change_list->setRenderingReferences($references);
       $change_list->setRenderURI($repository->getPathURI('diff/'));
       $change_list->setRepository($repository);
       $change_list->setUser($viewer);
       $change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
       // TODO: Try to setBranch() to something reasonable here?
 
       $change_list->setStandaloneURI(
         $repository->getPathURI('diff/'));
 
       $change_list->setRawFileURIs(
         // TODO: Implement this, somewhat tricky if there's an octopus merge
         // or whatever?
         null,
         $repository->getPathURI('diff/?view=r'));
 
       $change_list->setInlineCommentControllerURI(
         '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
 
     }
 
     $add_comment = $this->renderAddCommentPanel($commit, $audit_requests);
 
     $prefs = $viewer->loadPreferences();
     $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
     $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
     $show_filetree = $prefs->getPreference($pref_filetree);
     $collapsed = $prefs->getPreference($pref_collapse);
 
     $nav = null;
     if ($show_changesets && $show_filetree) {
       $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
         ->setTitle($commit->getDisplayName())
         ->setBaseURI(new PhutilURI($commit->getURI()))
         ->build($changesets)
         ->setCrumbs($crumbs)
         ->setCollapsed((bool)$collapsed);
     }
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setMainColumn(array(
         $error_panel,
         $timeline,
         $merge_table,
         $info_panel,
       ))
       ->setFooter(array(
         $change_table,
         $change_list,
         $add_comment,
       ))
-      ->addPropertySection(pht('DESCRIPTION'), $detail_list)
-      ->addPropertySection(pht('DETAILS'), $details)
+      ->addPropertySection(pht('Description'), $detail_list)
+      ->addPropertySection(pht('Details'), $details)
       ->setCurtain($curtain);
 
     $page = $this->newPage()
       ->setTitle($commit->getDisplayName())
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDS(array($commit->getPHID()))
       ->appendChild(
         array(
           $view,
       ));
 
     if ($nav) {
       $page->setNavigation($nav);
     }
 
     return $page;
 
   }
 
   private function buildPropertyListView(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepositoryCommitData $data,
     array $audit_requests) {
 
     $viewer = $this->getViewer();
     $commit_phid = $commit->getPHID();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($this->getRequest()->getUser());
 
     $edge_query = id(new PhabricatorEdgeQuery())
       ->withSourcePHIDs(array($commit_phid))
       ->withEdgeTypes(array(
         DiffusionCommitHasTaskEdgeType::EDGECONST,
         DiffusionCommitHasRevisionEdgeType::EDGECONST,
         DiffusionCommitRevertsCommitEdgeType::EDGECONST,
         DiffusionCommitRevertedByCommitEdgeType::EDGECONST,
       ));
 
     $edges = $edge_query->execute();
 
     $task_phids = array_keys(
       $edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]);
     $revision_phid = key(
       $edges[$commit_phid][DiffusionCommitHasRevisionEdgeType::EDGECONST]);
 
     $reverts_phids = array_keys(
       $edges[$commit_phid][DiffusionCommitRevertsCommitEdgeType::EDGECONST]);
     $reverted_by_phids = array_keys(
       $edges[$commit_phid][DiffusionCommitRevertedByCommitEdgeType::EDGECONST]);
 
     $phids = $edge_query->getDestinationPHIDs(array($commit_phid));
 
     if ($data->getCommitDetail('authorPHID')) {
       $phids[] = $data->getCommitDetail('authorPHID');
     }
     if ($data->getCommitDetail('reviewerPHID')) {
       $phids[] = $data->getCommitDetail('reviewerPHID');
     }
     if ($data->getCommitDetail('committerPHID')) {
       $phids[] = $data->getCommitDetail('committerPHID');
     }
 
     // NOTE: We should never normally have more than a single push log, but
     // it can occur naturally if a commit is pushed, then the branch it was
     // on is deleted, then the commit is pushed again (or through other similar
     // chains of events). This should be rare, but does not indicate a bug
     // or data issue.
 
     // NOTE: We never query push logs in SVN because the commiter is always
     // the pusher and the commit time is always the push time; the push log
     // is redundant and we save a query by skipping it.
 
     $push_logs = array();
     if ($repository->isHosted() && !$repository->isSVN()) {
       $push_logs = id(new PhabricatorRepositoryPushLogQuery())
         ->setViewer($viewer)
         ->withRepositoryPHIDs(array($repository->getPHID()))
         ->withNewRefs(array($commit->getCommitIdentifier()))
         ->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))
         ->execute();
       foreach ($push_logs as $log) {
         $phids[] = $log->getPusherPHID();
       }
     }
 
     $handles = array();
     if ($phids) {
       $handles = $this->loadViewerHandles($phids);
     }
 
     $props = array();
 
     if ($audit_requests) {
       $user_requests = array();
       $other_requests = array();
       foreach ($audit_requests as $audit_request) {
         if ($audit_request->isUser()) {
           $user_requests[] = $audit_request;
         } else {
           $other_requests[] = $audit_request;
         }
       }
 
       if ($user_requests) {
         $view->addProperty(
           pht('Auditors'),
           $this->renderAuditStatusView($user_requests));
       }
 
       if ($other_requests) {
         $view->addProperty(
           pht('Project/Package Auditors'),
           $this->renderAuditStatusView($other_requests));
       }
     }
 
     $author_phid = $data->getCommitDetail('authorPHID');
     $author_name = $data->getAuthorName();
     $author_epoch = $data->getCommitDetail('authorEpoch');
 
     $committed_info = id(new PHUIStatusItemView())
       ->setNote(phabricator_datetime($commit->getEpoch(), $viewer));
 
     $committer_phid = $data->getCommitDetail('committerPHID');
     $committer_name = $data->getCommitDetail('committer');
     if ($committer_phid) {
       $committed_info->setTarget($handles[$committer_phid]->renderLink());
     } else if (strlen($committer_name)) {
       $committed_info->setTarget($committer_name);
     } else if ($author_phid) {
       $committed_info->setTarget($handles[$author_phid]->renderLink());
     } else if (strlen($author_name)) {
       $committed_info->setTarget($author_name);
     }
 
     $committed_list = new PHUIStatusListView();
     $committed_list->addItem($committed_info);
     $view->addProperty(
       pht('Committed'),
       $committed_list);
 
     if ($push_logs) {
       $pushed_list = new PHUIStatusListView();
 
       foreach ($push_logs as $push_log) {
         $pushed_item = id(new PHUIStatusItemView())
           ->setTarget($handles[$push_log->getPusherPHID()]->renderLink())
           ->setNote(phabricator_datetime($push_log->getEpoch(), $viewer));
         $pushed_list->addItem($pushed_item);
       }
 
       $view->addProperty(
         pht('Pushed'),
         $pushed_list);
     }
 
     $reviewer_phid = $data->getCommitDetail('reviewerPHID');
     if ($reviewer_phid) {
       $view->addProperty(
         pht('Reviewer'),
         $handles[$reviewer_phid]->renderLink());
     }
 
     if ($revision_phid) {
       $view->addProperty(
         pht('Differential Revision'),
         $handles[$revision_phid]->renderLink());
     }
 
     $parents = $this->getCommitParents();
     if ($parents) {
       $view->addProperty(
         pht('Parents'),
         $viewer->renderHandleList(mpull($parents, 'getPHID')));
     }
 
     if ($this->getCommitExists()) {
       $view->addProperty(
         pht('Branches'),
         phutil_tag(
         'span',
         array(
           'id' => 'commit-branches',
         ),
         pht('Unknown')));
 
       $view->addProperty(
         pht('Tags'),
         phutil_tag(
         'span',
         array(
           'id' => 'commit-tags',
         ),
         pht('Unknown')));
 
       $identifier = $commit->getCommitIdentifier();
       $root = $repository->getPathURI("commit/{$identifier}");
       Javelin::initBehavior(
         'diffusion-commit-branches',
         array(
           $root.'/branches/' => 'commit-branches',
           $root.'/tags/' => 'commit-tags',
         ));
     }
 
     $refs = $this->getCommitRefs();
     if ($refs) {
       $ref_links = array();
       foreach ($refs as $ref_data) {
         $ref_links[] = phutil_tag(
           'a',
           array(
             'href' => $ref_data['href'],
           ),
           $ref_data['ref']);
       }
       $view->addProperty(
         pht('References'),
         phutil_implode_html(', ', $ref_links));
     }
 
     if ($reverts_phids) {
       $view->addProperty(
         pht('Reverts'),
         $viewer->renderHandleList($reverts_phids));
     }
 
     if ($reverted_by_phids) {
       $view->addProperty(
         pht('Reverted By'),
         $viewer->renderHandleList($reverted_by_phids));
     }
 
     if ($task_phids) {
       $task_list = array();
       foreach ($task_phids as $phid) {
         $task_list[] = $handles[$phid]->renderLink();
       }
       $task_list = phutil_implode_html(phutil_tag('br'), $task_list);
       $view->addProperty(
         pht('Tasks'),
         $task_list);
     }
 
     return $view;
   }
 
   private function buildSubheaderView(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepositoryCommitData $data) {
 
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     if ($repository->isSVN()) {
       return null;
     }
 
     $author_phid = $data->getCommitDetail('authorPHID');
     $author_name = $data->getAuthorName();
     $author_epoch = $data->getCommitDetail('authorEpoch');
     $date = null;
     if ($author_epoch !== null) {
       $date = phabricator_datetime($author_epoch, $viewer);
     }
 
     if ($author_phid) {
       $handles = $viewer->loadHandles(array($author_phid));
       $image_uri = $handles[$author_phid]->getImageURI();
       $image_href = $handles[$author_phid]->getURI();
       $author = $handles[$author_phid]->renderLink();
     } else if (strlen($author_name)) {
       $author = $author_name;
       $image_uri = null;
       $image_href = null;
     } else {
       return null;
     }
 
     $author = phutil_tag('strong', array(), $author);
     if ($date) {
       $content = pht('Authored by %s on %s.', $author, $date);
     } else {
       $content = pht('Authored by %s.', $author);
     }
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
 
   }
 
 
   private function buildComments(PhabricatorRepositoryCommit $commit) {
     $timeline = $this->buildTransactionTimeline(
       $commit,
       new PhabricatorAuditTransactionQuery());
     $commit->willRenderTimeline($timeline, $this->getRequest());
     return $timeline;
   }
 
   private function renderAddCommentPanel(
     PhabricatorRepositoryCommit $commit,
     array $audit_requests) {
     assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
 
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     if (!$viewer->isLoggedIn()) {
       return id(new PhabricatorApplicationTransactionCommentView())
         ->setUser($viewer)
         ->setRequestURI($request->getRequestURI());
     }
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     $pane_id = celerity_generate_unique_node_id();
     Javelin::initBehavior(
       'differential-keyboard-navigation',
       array(
         'haunt' => $pane_id,
       ));
 
     $draft = id(new PhabricatorDraft())->loadOneWhere(
       'authorPHID = %s AND draftKey = %s',
       $viewer->getPHID(),
       'diffusion-audit-'.$commit->getID());
     if ($draft) {
       $draft = $draft->getDraft();
     } else {
       $draft = null;
     }
 
     $actions = $this->getAuditActions($commit, $audit_requests);
 
     $mailable_source = new PhabricatorMetaMTAMailableDatasource();
     $auditor_source = new DiffusionAuditorDatasource();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setAction('/audit/addcomment/')
       ->addHiddenInput('commit', $commit->getPHID())
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel(pht('Action'))
           ->setName('action')
           ->setID('audit-action')
           ->setOptions($actions))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Add Auditors'))
           ->setName('auditors')
           ->setControlID('add-auditors')
           ->setControlStyle('display: none')
           ->setID('add-auditors-tokenizer')
           ->setDisableBehavior(true)
           ->setDatasource($auditor_source))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Add CCs'))
           ->setName('ccs')
           ->setControlID('add-ccs')
           ->setControlStyle('display: none')
           ->setID('add-ccs-tokenizer')
           ->setDisableBehavior(true)
           ->setDatasource($mailable_source))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setLabel(pht('Comments'))
           ->setName('content')
           ->setValue($draft)
           ->setID('audit-content')
           ->setUser($viewer))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Submit')));
 
     $header = new PHUIHeaderView();
     $header->setHeader(
       $is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
 
     Javelin::initBehavior(
       'differential-add-reviewers-and-ccs',
       array(
         'dynamic' => array(
           'add-auditors-tokenizer' => array(
             'actions' => array('add_auditors' => 1),
             'src' => $auditor_source->getDatasourceURI(),
             'row' => 'add-auditors',
             'placeholder' => $auditor_source->getPlaceholderText(),
           ),
           'add-ccs-tokenizer' => array(
             'actions' => array('add_ccs' => 1),
             'src' => $mailable_source->getDatasourceURI(),
             'row' => 'add-ccs',
             'placeholder' => $mailable_source->getPlaceholderText(),
           ),
         ),
         'select' => 'audit-action',
       ));
 
     Javelin::initBehavior('differential-feedback-preview', array(
       'uri'       => '/audit/preview/'.$commit->getID().'/',
       'preview'   => 'audit-preview',
       'content'   => 'audit-content',
       'action'    => 'audit-action',
       'previewTokenizers' => array(
         'auditors' => 'add-auditors-tokenizer',
         'ccs'      => 'add-ccs-tokenizer',
       ),
       'inline'     => 'inline-comment-preview',
       'inlineuri'  => '/diffusion/inline/preview/'.$commit->getPHID().'/',
     ));
 
     $loading = phutil_tag_div(
       'aphront-panel-preview-loading-text',
       pht('Loading preview...'));
 
     $preview_panel = phutil_tag_div(
       'aphront-panel-preview aphront-panel-flush',
       array(
         phutil_tag('div', array('id' => 'audit-preview'), $loading),
         phutil_tag('div', array('id' => 'inline-comment-preview')),
       ));
 
     // TODO: This is pretty awkward, unify the CSS between Diffusion and
     // Differential better.
     require_celerity_resource('differential-core-view-css');
 
     $anchor = id(new PhabricatorAnchorView())
       ->setAnchorName('comment')
       ->setNavigationMarker(true)
       ->render();
 
     $comment_box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild($form);
 
     return phutil_tag(
       'div',
       array(
         'id' => $pane_id,
       ),
       phutil_tag_div(
         'differential-add-comment-panel',
         array($anchor, $comment_box, $preview_panel)));
   }
 
   /**
    * Return a map of available audit actions for rendering into a <select />.
    * This shows the user valid actions, and does not show nonsense/invalid
    * actions (like closing an already-closed commit, or resigning from a commit
    * you have no association with).
    */
   private function getAuditActions(
     PhabricatorRepositoryCommit $commit,
     array $audit_requests) {
     assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
     $viewer = $this->getViewer();
 
     $user_is_author = ($commit->getAuthorPHID() == $viewer->getPHID());
 
     $user_request = null;
     foreach ($audit_requests as $audit_request) {
       if ($audit_request->getAuditorPHID() == $viewer->getPHID()) {
         $user_request = $audit_request;
         break;
       }
     }
 
     $actions = array();
     $actions[PhabricatorAuditActionConstants::COMMENT] = true;
 
     // We allow you to accept your own commits. A use case here is that you
     // notice an issue with your own commit and "Raise Concern" as an indicator
     // to other auditors that you're on top of the issue, then later resolve it
     // and "Accept". You can not accept on behalf of projects or packages,
     // however.
     $actions[PhabricatorAuditActionConstants::ACCEPT]  = true;
     $actions[PhabricatorAuditActionConstants::CONCERN] = true;
 
     // To resign, a user must have authority on some request and not be the
     // commit's author.
     if (!$user_is_author) {
       $may_resign = false;
 
       $authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
       foreach ($audit_requests as $request) {
         if (empty($authority_map[$request->getAuditorPHID()])) {
           continue;
         }
         $may_resign = true;
         break;
       }
 
       // If the user has already resigned, don't show "Resign...".
       $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
       if ($user_request) {
         if ($user_request->getAuditStatus() == $status_resigned) {
           $may_resign = false;
         }
       }
 
       if ($may_resign) {
         $actions[PhabricatorAuditActionConstants::RESIGN] = true;
       }
     }
 
     $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
     $concern_raised = ($commit->getAuditStatus() == $status_concern);
     $can_close_option = PhabricatorEnv::getEnvConfig(
       'audit.can-author-close-audit');
     if ($can_close_option && $user_is_author && $concern_raised) {
       $actions[PhabricatorAuditActionConstants::CLOSE] = true;
     }
 
     $actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
     $actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
 
     foreach ($actions as $constant => $ignored) {
       $actions[$constant] =
         PhabricatorAuditActionConstants::getActionName($constant);
     }
 
     return $actions;
   }
 
   private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $merges = $this->getCommitMerges();
     if (!$merges) {
       return null;
     }
 
     $limit = $this->getMergeDisplayLimit();
 
     $caption = null;
     if (count($merges) > $limit) {
       $merges = array_slice($merges, 0, $limit);
       $caption = new PHUIInfoView();
       $caption->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
       $caption->appendChild(
         pht(
           'This commit merges a very large number of changes. '.
           'Only the first %s are shown.',
           new PhutilNumber($limit)));
     }
 
     $history_table = id(new DiffusionHistoryTableView())
       ->setUser($viewer)
       ->setDiffusionRequest($drequest)
       ->setHistory($merges);
 
     $history_table->loadRevisions();
 
     $panel = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Merged Changes'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($history_table);
     if ($caption) {
       $panel->setInfoView($caption);
     }
 
     return $panel;
   }
 
   private function buildCurtain(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepository $repository) {
 
     $request = $this->getRequest();
     $viewer = $this->getViewer();
     $curtain = $this->newCurtainView($commit);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $commit,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $identifier = $commit->getCommitIdentifier();
     $uri = $repository->getPathURI("commit/{$identifier}/edit/");
 
     $action = id(new PhabricatorActionView())
       ->setName(pht('Edit Commit'))
       ->setHref($uri)
       ->setIcon('fa-pencil')
       ->setDisabled(!$can_edit)
       ->setWorkflow(!$can_edit);
     $curtain->addAction($action);
 
     require_celerity_resource('phabricator-object-selector-css');
     require_celerity_resource('javelin-behavior-phabricator-object-selector');
 
     $maniphest = 'PhabricatorManiphestApplication';
     if (PhabricatorApplication::isClassInstalled($maniphest)) {
       $action = id(new PhabricatorActionView())
         ->setName(pht('Edit Maniphest Tasks'))
         ->setIcon('fa-anchor')
         ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
         ->setWorkflow(true)
         ->setDisabled(!$can_edit);
       $curtain->addAction($action);
     }
 
     $action = id(new PhabricatorActionView())
       ->setName(pht('Download Raw Diff'))
       ->setHref($request->getRequestURI()->alter('diff', true))
       ->setIcon('fa-download');
     $curtain->addAction($action);
 
     return $curtain;
   }
 
   private function buildRawDiffResponse(DiffusionRequest $drequest) {
     $raw_diff = $this->callConduitWithDiffusionRequest(
       'diffusion.rawdiffquery',
       array(
         'commit' => $drequest->getCommit(),
         'path' => $drequest->getPath(),
       ));
 
     $file = PhabricatorFile::buildFromFileDataOrHash(
       $raw_diff,
       array(
         'name' => $drequest->getCommit().'.diff',
         'ttl' => (60 * 60 * 24),
         'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file->attachToObject($drequest->getRepository()->getPHID());
     unset($unguarded);
 
     return $file->getRedirectResponse();
   }
 
   private function renderAuditStatusView(array $audit_requests) {
     assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
     $viewer = $this->getViewer();
 
     $authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
 
     $view = new PHUIStatusListView();
     foreach ($audit_requests as $request) {
       $code = $request->getAuditStatus();
       $item = new PHUIStatusItemView();
       $item->setIcon(
         PhabricatorAuditStatusConstants::getStatusIcon($code),
         PhabricatorAuditStatusConstants::getStatusColor($code),
         PhabricatorAuditStatusConstants::getStatusName($code));
 
       $note = array();
       foreach ($request->getAuditReasons() as $reason) {
         $note[] = phutil_tag('div', array(), $reason);
       }
       $item->setNote($note);
 
       $auditor_phid = $request->getAuditorPHID();
       $target = $viewer->renderHandle($auditor_phid);
       $item->setTarget($target);
 
       if (isset($authority_map[$auditor_phid])) {
         $item->setHighlighted(true);
       }
 
       $view->addItem($item);
     }
 
     return $view;
   }
 
   private function linkBugtraq($corpus) {
     $url = PhabricatorEnv::getEnvConfig('bugtraq.url');
     if (!strlen($url)) {
       return $corpus;
     }
 
     $regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex');
     if (!$regexes) {
       return $corpus;
     }
 
     $parser = id(new PhutilBugtraqParser())
       ->setBugtraqPattern("[[ {$url} | %BUGID% ]]")
       ->setBugtraqCaptureExpression(array_shift($regexes));
 
     $select = array_shift($regexes);
     if ($select) {
       $parser->setBugtraqSelectExpression($select);
     }
 
     return $parser->processCorpus($corpus);
   }
 
   private function buildTableOfContents(
     array $changesets,
     $header,
     $info_view) {
 
     $drequest = $this->getDiffusionRequest();
     $viewer = $this->getViewer();
 
     $toc_view = id(new PHUIDiffTableOfContentsListView())
       ->setUser($viewer)
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
     if ($info_view) {
       $toc_view->setInfoView($info_view);
     }
 
     // TODO: This is hacky, we just want access to the linkX() methods on
     // DiffusionView.
     $diffusion_view = id(new DiffusionEmptyResultView())
       ->setDiffusionRequest($drequest);
 
     $have_owners = PhabricatorApplication::isClassInstalledForViewer(
       'PhabricatorOwnersApplication',
       $viewer);
 
     if (!$changesets) {
       $have_owners = false;
     }
 
     if ($have_owners) {
       if ($viewer->getPHID()) {
         $packages = id(new PhabricatorOwnersPackageQuery())
           ->setViewer($viewer)
           ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
           ->withAuthorityPHIDs(array($viewer->getPHID()))
           ->execute();
         $toc_view->setAuthorityPackages($packages);
       }
 
       $repository = $drequest->getRepository();
       $repository_phid = $repository->getPHID();
 
       $control_query = id(new PhabricatorOwnersPackageQuery())
         ->setViewer($viewer)
         ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
         ->withControl($repository_phid, mpull($changesets, 'getFilename'));
       $control_query->execute();
     }
 
     foreach ($changesets as $changeset_id => $changeset) {
       $path = $changeset->getFilename();
       $anchor = substr(md5($path), 0, 8);
 
       $history_link = $diffusion_view->linkHistory($path);
       $browse_link = $diffusion_view->linkBrowse(
         $path,
         array(
           'type' => $changeset->getFileType(),
         ));
 
       $item = id(new PHUIDiffTableOfContentsItemView())
         ->setChangeset($changeset)
         ->setAnchor($anchor)
         ->setContext(
           array(
             $history_link,
             ' ',
             $browse_link,
           ));
 
       if ($have_owners) {
         $packages = $control_query->getControllingPackagesForPath(
           $repository_phid,
           $changeset->getFilename());
         $item->setPackages($packages);
       }
 
       $toc_view->addItem($item);
     }
 
     return $toc_view;
   }
 
   private function loadCommitState() {
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $commit = $drequest->getCommit();
 
     // TODO: We could use futures here and resolve these calls in parallel.
 
     $exceptions = array();
 
     try {
       $parent_refs = $this->callConduitWithDiffusionRequest(
         'diffusion.commitparentsquery',
         array(
           'commit' => $commit,
         ));
 
       if ($parent_refs) {
         $parents = id(new DiffusionCommitQuery())
           ->setViewer($viewer)
           ->withRepository($repository)
           ->withIdentifiers($parent_refs)
           ->execute();
       } else {
         $parents = array();
       }
 
       $this->commitParents = $parents;
     } catch (Exception $ex) {
       $this->commitParents = false;
       $exceptions[] = $ex;
     }
 
     $merge_limit = $this->getMergeDisplayLimit();
 
     try {
       if ($repository->isSVN()) {
         $this->commitMerges = array();
       } else {
         $merges = $this->callConduitWithDiffusionRequest(
           'diffusion.mergedcommitsquery',
           array(
             'commit' => $commit,
             'limit' => $merge_limit + 1,
           ));
         $this->commitMerges = DiffusionPathChange::newFromConduit($merges);
       }
     } catch (Exception $ex) {
       $this->commitMerges = false;
       $exceptions[] = $ex;
     }
 
 
     try {
       if ($repository->isGit()) {
         $refs = $this->callConduitWithDiffusionRequest(
           'diffusion.refsquery',
           array(
             'commit' => $commit,
           ));
       } else {
         $refs = array();
       }
 
       $this->commitRefs = $refs;
     } catch (Exception $ex) {
       $this->commitRefs = false;
       $exceptions[] = $ex;
     }
 
     if ($exceptions) {
       $exists = $this->callConduitWithDiffusionRequest(
         'diffusion.existsquery',
         array(
           'commit' => $commit,
         ));
 
       if ($exists) {
         $this->commitExists = true;
         foreach ($exceptions as $exception) {
           $this->commitErrors[] = $exception->getMessage();
         }
       } else {
         $this->commitExists = false;
         $this->commitErrors[] = pht(
           'This commit no longer exists in the repository. It may have '.
           'been part of a branch which was deleted.');
       }
     } else {
       $this->commitExists = true;
       $this->commitErrors = array();
     }
   }
 
   private function getMergeDisplayLimit() {
     return 50;
   }
 
   private function getCommitExists() {
     if ($this->commitExists === null) {
       $this->loadCommitState();
     }
 
     return $this->commitExists;
   }
 
   private function getCommitParents() {
     if ($this->commitParents === null) {
       $this->loadCommitState();
     }
 
     return $this->commitParents;
   }
 
   private function getCommitRefs() {
     if ($this->commitRefs === null) {
       $this->loadCommitState();
     }
 
     return $this->commitRefs;
   }
 
   private function getCommitMerges() {
     if ($this->commitMerges === null) {
       $this->loadCommitState();
     }
 
     return $this->commitMerges;
   }
 
   private function getCommitErrors() {
     if ($this->commitErrors === null) {
       $this->loadCommitState();
     }
 
     return $this->commitErrors;
   }
 
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
index 792bcc424..b9e8fa40c 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -1,780 +1,780 @@
 <?php
 
 final class DiffusionRepositoryController extends DiffusionController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $response = $this->loadDiffusionContext();
     if ($response) {
       return $response;
     }
 
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $crumbs = $this->buildCrumbs();
     $crumbs->setBorder(true);
 
     $header = $this->buildHeaderView($repository);
     $curtain = $this->buildCurtain($repository);
     $property_table = $this->buildPropertiesTable($repository);
     $description = $this->buildDescriptionView($repository);
     $locate_file = $this->buildLocateFile();
 
     // Before we do any work, make sure we're looking at a some content: we're
     // on a valid branch, and the repository is not empty.
     $page_has_content = false;
     $empty_title = null;
     $empty_message = null;
 
     // If this VCS supports branches, check that the selected branch actually
     // exists.
     if ($drequest->supportsBranches()) {
       // NOTE: Mercurial may have multiple branch heads with the same name.
       $ref_cursors = id(new PhabricatorRepositoryRefCursorQuery())
         ->setViewer($viewer)
         ->withRepositoryPHIDs(array($repository->getPHID()))
         ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH))
         ->withRefNames(array($drequest->getBranch()))
         ->execute();
       if ($ref_cursors) {
         // This is a valid branch, so we necessarily have some content.
         $page_has_content = true;
       } else {
         $empty_title = pht('No Such Branch');
         $empty_message = pht(
           'There is no branch named "%s" in this repository.',
           $drequest->getBranch());
       }
     }
 
     // If we didn't find any branches, check if there are any commits at all.
     // This can tailor the message for empty repositories.
     if (!$page_has_content) {
       $any_commit = id(new DiffusionCommitQuery())
         ->setViewer($viewer)
         ->withRepository($repository)
         ->setLimit(1)
         ->execute();
       if ($any_commit) {
         if (!$drequest->supportsBranches()) {
           $page_has_content = true;
         }
       } else {
         $empty_title = pht('Empty Repository');
         $empty_message = pht('This repository does not have any commits yet.');
       }
     }
 
     if ($page_has_content) {
       $content = $this->buildNormalContent($drequest);
     } else {
       $content = id(new PHUIInfoView())
         ->setTitle($empty_title)
         ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
         ->setErrors(array($empty_message));
     }
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $property_table,
         $description,
         $locate_file,
       ))
       ->setFooter($content);
 
     return $this->newPage()
       ->setTitle(
         array(
           $repository->getName(),
           $repository->getDisplayName(),
         ))
       ->setCrumbs($crumbs)
       ->appendChild(array(
         $view,
       ));
   }
 
 
   private function buildNormalContent(DiffusionRequest $drequest) {
     $request = $this->getRequest();
     $repository = $drequest->getRepository();
 
     $phids = array();
     $content = array();
 
     try {
       $history_results = $this->callConduitWithDiffusionRequest(
         'diffusion.historyquery',
         array(
           'commit' => $drequest->getCommit(),
           'path' => $drequest->getPath(),
           'offset' => 0,
           'limit' => 15,
         ));
       $history = DiffusionPathChange::newFromConduit(
         $history_results['pathChanges']);
 
       foreach ($history as $item) {
         $data = $item->getCommitData();
         if ($data) {
           if ($data->getCommitDetail('authorPHID')) {
             $phids[$data->getCommitDetail('authorPHID')] = true;
           }
           if ($data->getCommitDetail('committerPHID')) {
             $phids[$data->getCommitDetail('committerPHID')] = true;
           }
         }
       }
       $history_exception = null;
     } catch (Exception $ex) {
       $history_results = null;
       $history = null;
       $history_exception = $ex;
     }
 
     $browse_pager = id(new PHUIPagerView())
       ->readFromRequest($request);
 
     try {
       $browse_results = DiffusionBrowseResultSet::newFromConduit(
         $this->callConduitWithDiffusionRequest(
           'diffusion.browsequery',
           array(
             'path' => $drequest->getPath(),
             'commit' => $drequest->getCommit(),
             'limit' => $browse_pager->getPageSize() + 1,
           )));
       $browse_paths = $browse_results->getPaths();
       $browse_paths = $browse_pager->sliceResults($browse_paths);
 
       foreach ($browse_paths as $item) {
         $data = $item->getLastCommitData();
         if ($data) {
           if ($data->getCommitDetail('authorPHID')) {
             $phids[$data->getCommitDetail('authorPHID')] = true;
           }
           if ($data->getCommitDetail('committerPHID')) {
             $phids[$data->getCommitDetail('committerPHID')] = true;
           }
         }
       }
 
       $browse_exception = null;
     } catch (Exception $ex) {
       $browse_results = null;
       $browse_paths = null;
       $browse_exception = $ex;
     }
 
     $phids = array_keys($phids);
     $handles = $this->loadViewerHandles($phids);
 
     if ($browse_results) {
       $readme = $this->renderDirectoryReadme($browse_results);
     } else {
       $readme = null;
     }
 
     $content[] = $this->buildBrowseTable(
       $browse_results,
       $browse_paths,
       $browse_exception,
       $handles,
       $browse_pager);
 
     $content[] = $this->buildHistoryTable(
       $history_results,
       $history,
       $history_exception);
 
     try {
       $content[] = $this->buildTagListTable($drequest);
     } catch (Exception $ex) {
       if (!$repository->isImporting()) {
         $content[] = $this->renderStatusMessage(
           pht('Unable to Load Tags'),
           $ex->getMessage());
       }
     }
 
     try {
       $content[] = $this->buildBranchListTable($drequest);
     } catch (Exception $ex) {
       if (!$repository->isImporting()) {
         $content[] = $this->renderStatusMessage(
           pht('Unable to Load Branches'),
           $ex->getMessage());
       }
     }
 
     if ($readme) {
       $content[] = $readme;
     }
 
     return $content;
   }
 
   private function buildHeaderView(PhabricatorRepository $repository) {
     $viewer = $this->getViewer();
     $header = id(new PHUIHeaderView())
       ->setHeader($repository->getName())
       ->setUser($viewer)
       ->setPolicyObject($repository)
       ->setHeaderIcon('fa-code');
 
     if (!$repository->isTracked()) {
       $header->setStatus('fa-ban', 'dark', pht('Inactive'));
     } else if ($repository->isImporting()) {
       $ratio = $repository->loadImportProgress();
       $percentage = sprintf('%.2f%%', 100 * $ratio);
       $header->setStatus(
         'fa-clock-o',
         'indigo',
         pht('Importing (%s)...', $percentage));
     } else {
       $header->setStatus('fa-check', 'bluegrey', pht('Active'));
     }
 
     return $header;
   }
 
   private function buildCurtain(PhabricatorRepository $repository) {
     $viewer = $this->getViewer();
 
     $edit_uri = $repository->getPathURI('edit/');
     $curtain = $this->newCurtainView($repository);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $repository,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Repository'))
         ->setIcon('fa-pencil')
         ->setHref($edit_uri)
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit));
 
     if ($repository->isHosted()) {
       $push_uri = $this->getApplicationURI(
         'pushlog/?repositories='.$repository->getMonogram());
 
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('View Push Logs'))
           ->setIcon('fa-list-alt')
           ->setHref($push_uri));
     }
 
     return $curtain;
   }
 
   private function buildDescriptionView(PhabricatorRepository $repository) {
     $viewer = $this->getViewer();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $description = $repository->getDetail('description');
     if (strlen($description)) {
       $description = new PHUIRemarkupView($viewer, $description);
       $view->addTextContent($description);
       return id(new PHUIObjectBoxView())
-        ->setHeaderText(pht('DESCRIPTION'))
+        ->setHeaderText(pht('Description'))
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->appendChild($view);
     }
     return null;
   }
 
   private function buildPropertiesTable(PhabricatorRepository $repository) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     if ($repository->isHosted()) {
       $ssh_uri = $repository->getSSHCloneURIObject();
       if ($ssh_uri) {
         $clone_uri = $this->renderCloneCommand(
           $repository,
           $ssh_uri,
           $repository->getServeOverSSH(),
           '/settings/panel/ssh/');
 
         $view->addProperty(
           $repository->isSVN()
             ? pht('Checkout (SSH)')
             : pht('Clone (SSH)'),
           $clone_uri);
       }
 
       $http_uri = $repository->getHTTPCloneURIObject();
       if ($http_uri) {
         $clone_uri = $this->renderCloneCommand(
           $repository,
           $http_uri,
           $repository->getServeOverHTTP(),
           PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')
             ? '/settings/panel/vcspassword/'
             : null);
 
         $view->addProperty(
           $repository->isSVN()
             ? pht('Checkout (HTTP)')
             : pht('Clone (HTTP)'),
           $clone_uri);
       }
     } else {
       switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $view->addProperty(
             pht('Clone'),
             $this->renderCloneCommand(
               $repository,
               $repository->getPublicCloneURI()));
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $view->addProperty(
             pht('Checkout'),
             $this->renderCloneCommand(
               $repository,
               $repository->getPublicCloneURI()));
           break;
       }
     }
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($view);
 
     $info = null;
     $drequest = $this->getDiffusionRequest();
 
     // Try to load alternatives. This may fail for repositories which have not
     // cloned yet. If it does, just ignore it and continue.
     try {
       $alternatives = $drequest->getRefAlternatives();
     } catch (ConduitClientException $ex) {
       $alternatives = array();
     }
 
     if ($alternatives) {
       $message = array(
         pht(
           'The ref "%s" is ambiguous in this repository.',
           $drequest->getBranch()),
         ' ',
         phutil_tag(
           'a',
           array(
             'href' => $drequest->generateURI(
               array(
                 'action' => 'refs',
               )),
           ),
           pht('View Alternatives')),
       );
 
       $messages = array($message);
 
       $info = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
         ->setErrors(array($message));
 
       $box->setInfoView($info);
     }
 
 
     return $box;
   }
 
   private function buildBranchListTable(DiffusionRequest $drequest) {
     $viewer = $this->getViewer();
 
     if ($drequest->getBranch() === null) {
       return null;
     }
 
     $limit = 15;
 
     $branches = $this->callConduitWithDiffusionRequest(
       'diffusion.branchquery',
       array(
         'closed' => false,
         'limit' => $limit + 1,
       ));
     if (!$branches) {
       return null;
     }
 
     $more_branches = (count($branches) > $limit);
     $branches = array_slice($branches, 0, $limit);
 
     $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches);
 
     $commits = id(new DiffusionCommitQuery())
       ->setViewer($viewer)
       ->withIdentifiers(mpull($branches, 'getCommitIdentifier'))
       ->withRepository($drequest->getRepository())
       ->execute();
 
     $table = id(new DiffusionBranchTableView())
       ->setUser($viewer)
       ->setDiffusionRequest($drequest)
       ->setBranches($branches)
       ->setCommits($commits);
 
     $panel = id(new PHUIObjectBoxView())
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
     $header = new PHUIHeaderView();
     $header->setHeader(pht('Branches'));
 
     if ($more_branches) {
       $header->setSubHeader(pht('Showing %d branches.', $limit));
     }
 
     $button = new PHUIButtonView();
     $button->setText(pht('Show All'));
     $button->setTag('a');
     $button->setIcon('fa-code-fork');
     $button->setHref($drequest->generateURI(
       array(
         'action' => 'branches',
       )));
 
     $header->addActionLink($button);
     $panel->setHeader($header);
     $panel->setTable($table);
 
     return $panel;
   }
 
   private function buildTagListTable(DiffusionRequest $drequest) {
     $viewer = $this->getViewer();
     $repository = $drequest->getRepository();
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         // no tags in SVN
         return null;
     }
     $tag_limit = 15;
     $tags = array();
     $tags = DiffusionRepositoryTag::newFromConduit(
       $this->callConduitWithDiffusionRequest(
         'diffusion.tagsquery',
         array(
           // On the home page, we want to find tags on any branch.
           'commit' => null,
           'limit' => $tag_limit + 1,
         )));
 
     if (!$tags) {
       return null;
     }
 
     $more_tags = (count($tags) > $tag_limit);
     $tags = array_slice($tags, 0, $tag_limit);
 
     $commits = id(new DiffusionCommitQuery())
       ->setViewer($viewer)
       ->withIdentifiers(mpull($tags, 'getCommitIdentifier'))
       ->withRepository($repository)
       ->needCommitData(true)
       ->execute();
 
     $view = id(new DiffusionTagListView())
       ->setUser($viewer)
       ->setDiffusionRequest($drequest)
       ->setTags($tags)
       ->setCommits($commits);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     $panel = new PHUIObjectBoxView();
     $header = new PHUIHeaderView();
     $header->setHeader(pht('Tags'));
 
     if ($more_tags) {
       $header->setSubHeader(
         pht('Showing the %d most recent tags.', $tag_limit));
     }
 
     $button = new PHUIButtonView();
     $button->setText(pht('Show All Tags'));
     $button->setTag('a');
     $button->setIcon('fa-tag');
     $button->setHref($drequest->generateURI(
       array(
         'action' => 'tags',
       )));
 
     $header->addActionLink($button);
 
     $panel->setHeader($header);
     $panel->setTable($view);
     $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
     return $panel;
   }
 
   private function buildHistoryTable(
     $history_results,
     $history,
     $history_exception) {
 
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     if ($history_exception) {
       if ($repository->isImporting()) {
         return $this->renderStatusMessage(
           pht('Still Importing...'),
           pht(
             'This repository is still importing. History is not yet '.
             'available.'));
       } else {
         return $this->renderStatusMessage(
           pht('Unable to Retrieve History'),
           $history_exception->getMessage());
       }
     }
 
     $history_table = id(new DiffusionHistoryTableView())
       ->setUser($viewer)
       ->setDiffusionRequest($drequest)
       ->setHistory($history);
 
     // TODO: Super sketchy.
     $history_table->loadRevisions();
 
     if ($history_results) {
       $history_table->setParents($history_results['parents']);
     }
 
     $history_table->setIsHead(true);
 
     $icon = id(new PHUIIconView())
       ->setIcon('fa-list-alt');
 
     $button = id(new PHUIButtonView())
       ->setText(pht('View History'))
       ->setHref($drequest->generateURI(
         array(
           'action' => 'history',
         )))
       ->setTag('a')
       ->setIcon($icon);
 
     $panel = id(new PHUIObjectBoxView())
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Recent Commits'))
       ->addActionLink($button);
     $panel->setHeader($header);
     $panel->setTable($history_table);
 
     return $panel;
   }
 
   private function buildLocateFile() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     $locate_panel = null;
     if ($repository->canUsePathTree()) {
       Javelin::initBehavior(
         'diffusion-locate-file',
         array(
           'controlID' => 'locate-control',
           'inputID' => 'locate-input',
           'browseBaseURI' => (string)$drequest->generateURI(
             array(
               'action' => 'browse',
             )),
           'uri' => (string)$drequest->generateURI(
             array(
               'action' => 'pathtree',
             )),
         ));
 
       $form = id(new AphrontFormView())
         ->setUser($viewer)
         ->appendChild(
           id(new AphrontFormTypeaheadControl())
             ->setHardpointID('locate-control')
             ->setID('locate-input')
             ->setLabel(pht('Locate File')));
       $form_box = id(new PHUIBoxView())
         ->appendChild($form->buildLayoutView());
       $locate_panel = id(new PHUIObjectBoxView())
         ->setHeaderText(pht('Locate File'))
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->appendChild($form_box);
     }
     return $locate_panel;
   }
 
   private function buildBrowseTable(
     $browse_results,
     $browse_paths,
     $browse_exception,
     array $handles,
     PHUIPagerView $pager) {
 
     require_celerity_resource('diffusion-icons-css');
 
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
 
     if ($browse_exception) {
       if ($repository->isImporting()) {
         // The history table renders a useful message.
         return null;
       } else {
         return $this->renderStatusMessage(
           pht('Unable to Retrieve Paths'),
           $browse_exception->getMessage());
       }
     }
 
     $browse_table = id(new DiffusionBrowseTableView())
       ->setUser($viewer)
       ->setDiffusionRequest($drequest)
       ->setHandles($handles);
     if ($browse_paths) {
       $browse_table->setPaths($browse_paths);
     } else {
       $browse_table->setPaths(array());
     }
 
     $browse_uri = $drequest->generateURI(array('action' => 'browse'));
 
     $browse_panel = id(new PHUIObjectBoxView())
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
     $header = id(new PHUIHeaderView())
       ->setHeader($repository->getName());
 
     $icon = id(new PHUIIconView())
       ->setIcon('fa-folder-open');
 
     $button = new PHUIButtonView();
     $button->setText(pht('Browse Repository'));
     $button->setTag('a');
     $button->setIcon($icon);
     $button->setHref($browse_uri);
 
     $header->addActionLink($button);
     $browse_panel->setHeader($header);
     $browse_panel->setTable($browse_table);
 
     $pager->setURI($browse_uri, 'offset');
 
     if ($pager->willShowPagingControls()) {
       $pager_box = $this->renderTablePagerBox($pager);
     } else {
       $pager_box = null;
     }
 
     return array(
       $browse_panel,
       $pager_box,
     );
   }
 
   private function renderCloneCommand(
     PhabricatorRepository $repository,
     $uri,
     $serve_mode = null,
     $manage_uri = null) {
 
     require_celerity_resource('diffusion-icons-css');
 
     Javelin::initBehavior('select-on-click');
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $command = csprintf(
           'git clone %R',
           $uri);
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $command = csprintf(
           'hg clone %R',
           $uri);
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         if ($repository->isHosted()) {
           $command = csprintf(
             'svn checkout %R %R',
             $uri,
             $repository->getCloneName());
         } else {
           $command = csprintf(
             'svn checkout %R',
             $uri);
         }
         break;
     }
 
     $input = javelin_tag(
       'input',
       array(
         'type' => 'text',
         'value' => (string)$command,
         'class' => 'diffusion-clone-uri',
         'sigil' => 'select-on-click',
         'readonly' => 'true',
       ));
 
     $extras = array();
     if ($serve_mode) {
       if ($serve_mode === PhabricatorRepository::SERVE_READONLY) {
         $extras[] = pht('(Read Only)');
       }
     }
 
     if ($manage_uri) {
       if ($this->getRequest()->getUser()->isLoggedIn()) {
         $extras[] = phutil_tag(
           'a',
           array(
             'href' => $manage_uri,
           ),
           pht('Manage Credentials'));
       }
     }
 
     if ($extras) {
       $extras = phutil_implode_html(' ', $extras);
       $extras = phutil_tag(
         'div',
         array(
           'class' => 'diffusion-clone-extras',
         ),
         $extras);
     }
 
     return array($input, $extras);
   }
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php
index 80fb22416..a5871ca07 100644
--- a/src/applications/diffusion/controller/DiffusionServeController.php
+++ b/src/applications/diffusion/controller/DiffusionServeController.php
@@ -1,1183 +1,1184 @@
 <?php
 
 final class DiffusionServeController extends DiffusionController {
 
   private $serviceViewer;
   private $serviceRepository;
 
   private $isGitLFSRequest;
   private $gitLFSToken;
   private $gitLFSInput;
 
   public function setServiceViewer(PhabricatorUser $viewer) {
     $this->getRequest()->setUser($viewer);
 
     $this->serviceViewer = $viewer;
     return $this;
   }
 
   public function getServiceViewer() {
     return $this->serviceViewer;
   }
 
   public function setServiceRepository(PhabricatorRepository $repository) {
     $this->serviceRepository = $repository;
     return $this;
   }
 
   public function getServiceRepository() {
     return $this->serviceRepository;
   }
 
   public function getIsGitLFSRequest() {
     return $this->isGitLFSRequest;
   }
 
   public function getGitLFSToken() {
     return $this->gitLFSToken;
   }
 
   public function isVCSRequest(AphrontRequest $request) {
     $identifier = $this->getRepositoryIdentifierFromRequest($request);
     if ($identifier === null) {
       return null;
     }
 
     $content_type = $request->getHTTPHeader('Content-Type');
     $user_agent = idx($_SERVER, 'HTTP_USER_AGENT');
     $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
 
     // This may have a "charset" suffix, so only match the prefix.
     $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))';
 
     $vcs = null;
     if ($request->getExists('service')) {
       $service = $request->getStr('service');
       // We get this initially for `info/refs`.
       // Git also gives us a User-Agent like "git/1.8.2.3".
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     } else if (strncmp($user_agent, 'git/', 4) === 0) {
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     } else if ($content_type == 'application/x-git-upload-pack-request') {
       // We get this for `git-upload-pack`.
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     } else if ($content_type == 'application/x-git-receive-pack-request') {
       // We get this for `git-receive-pack`.
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     } else if (preg_match($lfs_pattern, $content_type)) {
       // This is a Git LFS HTTP API request.
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
       $this->isGitLFSRequest = true;
     } else if ($request_type == 'git-lfs') {
       // This is a Git LFS object content request.
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
       $this->isGitLFSRequest = true;
     } else if ($request->getExists('cmd')) {
       // Mercurial also sends an Accept header like
       // "application/mercurial-0.1", and a User-Agent like
       // "mercurial/proto-1.0".
       $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
     } else {
       // Subversion also sends an initial OPTIONS request (vs GET/POST), and
       // has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2)
       // serf/1.3.2".
       $dav = $request->getHTTPHeader('DAV');
       $dav = new PhutilURI($dav);
       if ($dav->getDomain() === 'subversion.tigris.org') {
         $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
       }
     }
 
     return $vcs;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $service_exception = null;
     $response = null;
 
     try {
       $response = $this->serveRequest($request);
     } catch (Exception $ex) {
       $service_exception = $ex;
     }
 
     try {
       $remote_addr = $request->getRemoteAddress();
 
       $pull_event = id(new PhabricatorRepositoryPullEvent())
         ->setEpoch(PhabricatorTime::getNow())
         ->setRemoteAddress($remote_addr)
         ->setRemoteProtocol('http');
 
       if ($response) {
         $pull_event
           ->setResultType('wild')
           ->setResultCode($response->getHTTPResponseCode());
 
         if ($response instanceof PhabricatorVCSResponse) {
           $pull_event->setProperties(
             array(
               'response.message' => $response->getMessage(),
             ));
         }
       } else {
         $pull_event
           ->setResultType('exception')
           ->setResultCode(500)
           ->setProperties(
             array(
               'exception.class' => get_class($ex),
               'exception.message' => $ex->getMessage(),
             ));
       }
 
       $viewer = $this->getServiceViewer();
       if ($viewer) {
         $pull_event->setPullerPHID($viewer->getPHID());
       }
 
       $repository = $this->getServiceRepository();
       if ($repository) {
         $pull_event->setRepositoryPHID($repository->getPHID());
       }
 
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $pull_event->save();
       unset($unguarded);
 
     } catch (Exception $ex) {
       if ($service_exception) {
         throw $service_exception;
       }
       throw $ex;
     }
 
     if ($service_exception) {
       throw $service_exception;
     }
 
     return $response;
   }
 
   private function serveRequest(AphrontRequest $request) {
     $identifier = $this->getRepositoryIdentifierFromRequest($request);
 
     // If authentication credentials have been provided, try to find a user
     // that actually matches those credentials.
     if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
       $username = $_SERVER['PHP_AUTH_USER'];
       $password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']);
 
       // Try Git LFS auth first since we can usually reject it without doing
       // any queries, since the username won't match the one we expect or the
       // request won't be LFS.
       $viewer = $this->authenticateGitLFSUser($username, $password);
 
       // If that failed, try normal auth. Note that we can use normal auth on
       // LFS requests, so this isn't strictly an alternative to LFS auth.
       if (!$viewer) {
         $viewer = $this->authenticateHTTPRepositoryUser($username, $password);
       }
 
       if (!$viewer) {
         return new PhabricatorVCSResponse(
           403,
           pht('Invalid credentials.'));
       }
     } else {
       // User hasn't provided credentials, which means we count them as
       // being "not logged in".
       $viewer = new PhabricatorUser();
     }
 
     $this->setServiceViewer($viewer);
 
     $allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
     $allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
     if (!$allow_public) {
       if (!$viewer->isLoggedIn()) {
         if ($allow_auth) {
           return new PhabricatorVCSResponse(
             401,
             pht('You must log in to access repositories.'));
         } else {
           return new PhabricatorVCSResponse(
             403,
             pht('Public and authenticated HTTP access are both forbidden.'));
         }
       }
     }
 
     try {
       $repository = id(new PhabricatorRepositoryQuery())
         ->setViewer($viewer)
         ->withIdentifiers(array($identifier))
         ->executeOne();
       if (!$repository) {
         return new PhabricatorVCSResponse(
           404,
           pht('No such repository exists.'));
       }
     } catch (PhabricatorPolicyException $ex) {
       if ($viewer->isLoggedIn()) {
         return new PhabricatorVCSResponse(
           403,
           pht('You do not have permission to access this repository.'));
       } else {
         if ($allow_auth) {
           return new PhabricatorVCSResponse(
             401,
             pht('You must log in to access this repository.'));
         } else {
           return new PhabricatorVCSResponse(
             403,
             pht(
               'This repository requires authentication, which is forbidden '.
               'over HTTP.'));
         }
       }
     }
 
     $response = $this->validateGitLFSRequest($repository, $viewer);
     if ($response) {
       return $response;
     }
 
     $this->setServiceRepository($repository);
 
     if (!$repository->isTracked()) {
       return new PhabricatorVCSResponse(
         403,
         pht('This repository is inactive.'));
     }
 
     $is_push = !$this->isReadOnlyRequest($repository);
 
     if ($this->getIsGitLFSRequest() && $this->getGitLFSToken()) {
       // We allow git LFS requests over HTTP even if the repository does not
       // otherwise support HTTP reads or writes, as long as the user is using a
       // token from SSH. If they're using HTTP username + password auth, they
       // have to obey the normal HTTP rules.
     } else {
       switch ($repository->getServeOverHTTP()) {
         case PhabricatorRepository::SERVE_READONLY:
           if ($is_push) {
             return new PhabricatorVCSResponse(
               403,
               pht('This repository is read-only over HTTP.'));
           }
           break;
         case PhabricatorRepository::SERVE_READWRITE:
           // We'll check for push capability below.
           break;
         case PhabricatorRepository::SERVE_OFF:
         default:
           return new PhabricatorVCSResponse(
             403,
             pht('This repository is not available over HTTP.'));
       }
     }
 
     if ($is_push) {
       $can_push = PhabricatorPolicyFilter::hasCapability(
         $viewer,
         $repository,
         DiffusionPushCapability::CAPABILITY);
       if (!$can_push) {
         if ($viewer->isLoggedIn()) {
           $error_code = 403;
           $error_message = pht(
             'You do not have permission to push to this repository ("%s").',
             $repository->getDisplayName());
 
           if ($this->getIsGitLFSRequest()) {
             return DiffusionGitLFSResponse::newErrorResponse(
               $error_code,
               $error_message);
           } else {
             return new PhabricatorVCSResponse(
               $error_code,
               $error_message);
           }
         } else {
           if ($allow_auth) {
             return new PhabricatorVCSResponse(
               401,
               pht('You must log in to push to this repository.'));
           } else {
             return new PhabricatorVCSResponse(
               403,
               pht(
                 'Pushing to this repository requires authentication, '.
                 'which is forbidden over HTTP.'));
           }
         }
       }
     }
 
     $vcs_type = $repository->getVersionControlSystem();
     $req_type = $this->isVCSRequest($request);
 
     if ($vcs_type != $req_type) {
       switch ($req_type) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
           $result = new PhabricatorVCSResponse(
             500,
             pht(
               'This repository ("%s") is not a Git repository.',
               $repository->getDisplayName()));
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $result = new PhabricatorVCSResponse(
             500,
             pht(
               'This repository ("%s") is not a Mercurial repository.',
               $repository->getDisplayName()));
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $result = new PhabricatorVCSResponse(
             500,
             pht(
               'This repository ("%s") is not a Subversion repository.',
               $repository->getDisplayName()));
           break;
         default:
           $result = new PhabricatorVCSResponse(
             500,
             pht('Unknown request type.'));
           break;
       }
     } else {
       switch ($vcs_type) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $result = $this->serveVCSRequest($repository, $viewer);
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $result = new PhabricatorVCSResponse(
             500,
             pht(
               'Phabricator does not support HTTP access to Subversion '.
               'repositories.'));
           break;
         default:
           $result = new PhabricatorVCSResponse(
             500,
             pht('Unknown version control system.'));
           break;
       }
     }
 
     $code = $result->getHTTPResponseCode();
 
     if ($is_push && ($code == 200)) {
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $repository->writeStatusMessage(
           PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
           PhabricatorRepositoryStatusMessage::CODE_OKAY);
       unset($unguarded);
     }
 
     return $result;
   }
 
   private function serveVCSRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
 
     // We can serve Git LFS requests first, since we don't need to proxy them.
     // It's also important that LFS requests never fall through to standard
     // service pathways, because that would let you use LFS tokens to read
     // normal repository data.
     if ($this->getIsGitLFSRequest()) {
       return $this->serveGitLFSRequest($repository, $viewer);
     }
 
     // If this repository is hosted on a service, we need to proxy the request
     // to a host which can serve it.
     $is_cluster_request = $this->getRequest()->isProxiedClusterRequest();
 
     $uri = $repository->getAlmanacServiceURI(
       $viewer,
       $is_cluster_request,
       array(
         'http',
         'https',
       ));
     if ($uri) {
       $future = $this->getRequest()->newClusterProxyFuture($uri);
       return id(new AphrontHTTPProxyResponse())
         ->setHTTPFuture($future);
     }
 
     // Otherwise, we're going to handle the request locally.
 
     $vcs_type = $repository->getVersionControlSystem();
     switch ($vcs_type) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $result = $this->serveGitRequest($repository, $viewer);
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $result = $this->serveMercurialRequest($repository, $viewer);
         break;
     }
 
     return $result;
   }
 
   private function isReadOnlyRequest(
     PhabricatorRepository $repository) {
     $request = $this->getRequest();
     $method = $_SERVER['REQUEST_METHOD'];
 
     // TODO: This implementation is safe by default, but very incomplete.
 
     if ($this->getIsGitLFSRequest()) {
       return $this->isGitLFSReadOnlyRequest($repository);
     }
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $service = $request->getStr('service');
         $path = $this->getRequestDirectoryPath($repository);
         // NOTE: Service names are the reverse of what you might expect, as they
         // are from the point of view of the server. The main read service is
         // "git-upload-pack", and the main write service is "git-receive-pack".
 
         if ($method == 'GET' &&
             $path == '/info/refs' &&
             $service == 'git-upload-pack') {
           return true;
         }
 
         if ($path == '/git-upload-pack') {
           return true;
         }
 
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $cmd = $request->getStr('cmd');
         if ($cmd == 'batch') {
           $cmds = idx($this->getMercurialArguments(), 'cmds');
           return DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds);
         }
         return DiffusionMercurialWireProtocol::isReadOnlyCommand($cmd);
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         break;
     }
 
     return false;
   }
 
   /**
    * @phutil-external-symbol class PhabricatorStartup
    */
   private function serveGitRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
     $request = $this->getRequest();
 
     $request_path = $this->getRequestDirectoryPath($repository);
     $repository_root = $repository->getLocalPath();
 
     // Rebuild the query string to strip `__magic__` parameters and prevent
     // issues where we might interpret inputs like "service=read&service=write"
     // differently than the server does and pass it an unsafe command.
 
     // NOTE: This does not use getPassthroughRequestParameters() because
     // that code is HTTP-method agnostic and will encode POST data.
 
     $query_data = $_GET;
     foreach ($query_data as $key => $value) {
       if (!strncmp($key, '__', 2)) {
         unset($query_data[$key]);
       }
     }
     $query_string = http_build_query($query_data, '', '&');
 
     // We're about to wipe out PATH with the rest of the environment, so
     // resolve the binary first.
     $bin = Filesystem::resolveBinary('git-http-backend');
     if (!$bin) {
       throw new Exception(
         pht(
           'Unable to find `%s` in %s!',
           'git-http-backend',
           '$PATH'));
     }
 
     // NOTE: We do not set HTTP_CONTENT_ENCODING here, because we already
     // decompressed the request when we read the request body, so the body is
     // just plain data with no encoding.
 
     $env = array(
       'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
       'QUERY_STRING' => $query_string,
       'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
       'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
       'GIT_PROJECT_ROOT' => $repository_root,
       'GIT_HTTP_EXPORT_ALL' => '1',
       'PATH_INFO' => $request_path,
 
       'REMOTE_USER' => $viewer->getUsername(),
 
       // TODO: Set these correctly.
       // GIT_COMMITTER_NAME
       // GIT_COMMITTER_EMAIL
     ) + $this->getCommonEnvironment($viewer);
 
     $input = PhabricatorStartup::getRawInput();
 
     $command = csprintf('%s', $bin);
     $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
 
     list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
       ->setEnv($env, true)
       ->write($input)
       ->resolve();
 
     if ($err) {
       if ($this->isValidGitShallowCloneResponse($stdout, $stderr)) {
         // Ignore the error if the response passes this special check for
         // validity.
         $err = 0;
       }
     }
 
     if ($err) {
       return new PhabricatorVCSResponse(
         500,
         pht(
           'Error %d: %s',
           $err,
           phutil_utf8ize($stderr)));
     }
 
     return id(new DiffusionGitResponse())->setGitData($stdout);
   }
 
   private function getRequestDirectoryPath(PhabricatorRepository $repository) {
     $request = $this->getRequest();
     $request_path = $request->getRequestURI()->getPath();
 
     $info = PhabricatorRepository::parseRepositoryServicePath($request_path);
     $base_path = $info['path'];
 
     // For Git repositories, strip an optional directory component if it
     // isn't the name of a known Git resource. This allows users to clone
     // repositories as "/diffusion/X/anything.git", for example.
     if ($repository->isGit()) {
       $known = array(
         'info',
         'git-upload-pack',
         'git-receive-pack',
       );
 
       foreach ($known as $key => $path) {
         $known[$key] = preg_quote($path, '@');
       }
 
       $known = implode('|', $known);
 
       if (preg_match('@^/([^/]+)/('.$known.')(/|$)@', $base_path)) {
         $base_path = preg_replace('@^/([^/]+)@', '', $base_path);
       }
     }
 
     return $base_path;
   }
 
   private function authenticateGitLFSUser(
     $username,
     PhutilOpaqueEnvelope $password) {
 
     // Never accept these credentials for requests which aren't LFS requests.
     if (!$this->getIsGitLFSRequest()) {
       return null;
     }
 
     // If we have the wrong username, don't bother checking if the token
     // is right.
     if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) {
       return null;
     }
 
     $lfs_pass = $password->openEnvelope();
     $lfs_hash = PhabricatorHash::digest($lfs_pass);
 
     $token = id(new PhabricatorAuthTemporaryTokenQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE))
       ->withTokenCodes(array($lfs_hash))
       ->withExpired(false)
       ->executeOne();
     if (!$token) {
       return null;
     }
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withPHIDs(array($token->getUserPHID()))
       ->executeOne();
 
     if (!$user) {
       return null;
     }
 
     if (!$user->isUserActivated()) {
       return null;
     }
 
     $this->gitLFSToken = $token;
 
     return $user;
   }
 
   private function authenticateHTTPRepositoryUser(
     $username,
     PhutilOpaqueEnvelope $password) {
 
     if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) {
       // No HTTP auth permitted.
       return null;
     }
 
     if (!strlen($username)) {
       // No username.
       return null;
     }
 
     if (!strlen($password->openEnvelope())) {
       // No password.
       return null;
     }
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withUsernames(array($username))
       ->executeOne();
     if (!$user) {
       // Username doesn't match anything.
       return null;
     }
 
     if (!$user->isUserActivated()) {
       // User is not activated.
       return null;
     }
 
     $password_entry = id(new PhabricatorRepositoryVCSPassword())
       ->loadOneWhere('userPHID = %s', $user->getPHID());
     if (!$password_entry) {
       // User doesn't have a password set.
       return null;
     }
 
     if (!$password_entry->comparePassword($password, $user)) {
       // Password doesn't match.
       return null;
     }
 
     // If the user's password is stored using a less-than-optimal hash, upgrade
     // them to the strongest available hash.
 
     $hash_envelope = new PhutilOpaqueEnvelope(
       $password_entry->getPasswordHash());
     if (PhabricatorPasswordHasher::canUpgradeHash($hash_envelope)) {
       $password_entry->setPassword($password, $user);
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $password_entry->save();
       unset($unguarded);
     }
 
     return $user;
   }
 
   private function serveMercurialRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
     $request = $this->getRequest();
 
     $bin = Filesystem::resolveBinary('hg');
     if (!$bin) {
       throw new Exception(
         pht(
           'Unable to find `%s` in %s!',
           'hg',
           '$PATH'));
     }
 
     $env = $this->getCommonEnvironment($viewer);
     $input = PhabricatorStartup::getRawInput();
 
     $cmd = $request->getStr('cmd');
 
     $args = $this->getMercurialArguments();
     $args = $this->formatMercurialArguments($cmd, $args);
 
     if (strlen($input)) {
       $input = strlen($input)."\n".$input."0\n";
     }
 
     $command = csprintf('%s serve --stdio', $bin);
     $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
 
     list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
       ->setEnv($env, true)
       ->setCWD($repository->getLocalPath())
       ->write("{$cmd}\n{$args}{$input}")
       ->resolve();
 
     if ($err) {
       return new PhabricatorVCSResponse(
         500,
         pht('Error %d: %s', $err, $stderr));
     }
 
     if ($cmd == 'getbundle' ||
         $cmd == 'changegroup' ||
         $cmd == 'changegroupsubset') {
       // We're not completely sure that "changegroup" and "changegroupsubset"
       // actually work, they're for very old Mercurial.
       $body = gzcompress($stdout);
     } else if ($cmd == 'unbundle') {
       // This includes diagnostic information and anything echoed by commit
       // hooks. We ignore `stdout` since it just has protocol garbage, and
       // substitute `stderr`.
       $body = strlen($stderr)."\n".$stderr;
     } else {
       list($length, $body) = explode("\n", $stdout, 2);
       if ($cmd == 'capabilities') {
         $body = DiffusionMercurialWireProtocol::filterBundle2Capability($body);
       }
     }
 
     return id(new DiffusionMercurialResponse())->setContent($body);
   }
 
   private function getMercurialArguments() {
     // Mercurial sends arguments in HTTP headers. "Why?", you might wonder,
     // "Why would you do this?".
 
     $args_raw = array();
     for ($ii = 1;; $ii++) {
       $header = 'HTTP_X_HGARG_'.$ii;
       if (!array_key_exists($header, $_SERVER)) {
         break;
       }
       $args_raw[] = $_SERVER[$header];
     }
     $args_raw = implode('', $args_raw);
 
     return id(new PhutilQueryStringParser())
       ->parseQueryString($args_raw);
   }
 
   private function formatMercurialArguments($command, array $arguments) {
     $spec = DiffusionMercurialWireProtocol::getCommandArgs($command);
 
     $out = array();
 
     // Mercurial takes normal arguments like this:
     //
     //   name <length(value)>
     //   value
 
     $has_star = false;
     foreach ($spec as $arg_key) {
       if ($arg_key == '*') {
         $has_star = true;
         continue;
       }
       if (isset($arguments[$arg_key])) {
         $value = $arguments[$arg_key];
         $size = strlen($value);
         $out[] = "{$arg_key} {$size}\n{$value}";
         unset($arguments[$arg_key]);
       }
     }
 
     if ($has_star) {
 
       // Mercurial takes arguments for variable argument lists roughly like
       // this:
       //
       //   * <count(args)>
       //   argname1 <length(argvalue1)>
       //   argvalue1
       //   argname2 <length(argvalue2)>
       //   argvalue2
 
       $count = count($arguments);
 
       $out[] = "* {$count}\n";
 
       foreach ($arguments as $key => $value) {
         if (in_array($key, $spec)) {
           // We already added this argument above, so skip it.
           continue;
         }
         $size = strlen($value);
         $out[] = "{$key} {$size}\n{$value}";
       }
     }
 
     return implode('', $out);
   }
 
   private function isValidGitShallowCloneResponse($stdout, $stderr) {
     // If you execute `git clone --depth N ...`, git sends a request which
     // `git-http-backend` responds to by emitting valid output and then exiting
     // with a failure code and an error message. If we ignore this error,
     // everything works.
 
     // This is a pretty funky fix: it would be nice to more precisely detect
     // that a request is a `--depth N` clone request, but we don't have any code
     // to decode protocol frames yet. Instead, look for reasonable evidence
     // in the error and output that we're looking at a `--depth` clone.
 
     // For evidence this isn't completely crazy, see:
     // https://github.com/schacon/grack/pull/7
 
     $stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m';
     $stderr_regexp = '(The remote end hung up unexpectedly)';
 
     $has_pack = preg_match($stdout_regexp, $stdout);
     $is_hangup = preg_match($stderr_regexp, $stderr);
 
     return $has_pack && $is_hangup;
   }
 
   private function getCommonEnvironment(PhabricatorUser $viewer) {
     $remote_address = $this->getRequest()->getRemoteAddress();
 
     return array(
       DiffusionCommitHookEngine::ENV_USER => $viewer->getUsername(),
       DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_address,
       DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'http',
     );
   }
 
   private function validateGitLFSRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
     if (!$this->getIsGitLFSRequest()) {
       return null;
     }
 
     if (!$repository->canUseGitLFS()) {
       return new PhabricatorVCSResponse(
         403,
         pht(
           'The requested repository ("%s") does not support Git LFS.',
           $repository->getDisplayName()));
     }
 
     // If this is using an LFS token, sanity check that we're using it on the
     // correct repository. This shouldn't really matter since the user could
     // just request a proper token anyway, but it suspicious and should not
     // be permitted.
 
     $token = $this->getGitLFSToken();
     if ($token) {
       $resource = $token->getTokenResource();
       if ($resource !== $repository->getPHID()) {
         return new PhabricatorVCSResponse(
           403,
           pht(
             'The authentication token provided in the request is bound to '.
             'a different repository than the requested repository ("%s").',
             $repository->getDisplayName()));
       }
     }
 
     return null;
   }
 
   private function serveGitLFSRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
 
     if (!$this->getIsGitLFSRequest()) {
       throw new Exception(pht('This is not a Git LFS request!'));
     }
 
     $path = $this->getGitLFSRequestPath($repository);
     $matches = null;
 
     if (preg_match('(^upload/(.*)\z)', $path, $matches)) {
       $oid = $matches[1];
       return $this->serveGitLFSUploadRequest($repository, $viewer, $oid);
     } else if ($path == 'objects/batch') {
       return $this->serveGitLFSBatchRequest($repository, $viewer);
     } else {
       return DiffusionGitLFSResponse::newErrorResponse(
         404,
         pht(
           'Git LFS operation "%s" is not supported by this server.',
           $path));
     }
   }
 
   private function serveGitLFSBatchRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer) {
 
     $input = $this->getGitLFSInput();
 
     $operation = idx($input, 'operation');
     switch ($operation) {
       case 'upload':
         $want_upload = true;
         break;
       case 'download':
         $want_upload = false;
         break;
       default:
         return DiffusionGitLFSResponse::newErrorResponse(
           404,
           pht(
             'Git LFS batch operation "%s" is not supported by this server.',
             $operation));
     }
 
     $objects = idx($input, 'objects', array());
 
     $hashes = array();
     foreach ($objects as $object) {
       $hashes[] = idx($object, 'oid');
     }
 
     if ($hashes) {
       $refs = id(new PhabricatorRepositoryGitLFSRefQuery())
         ->setViewer($viewer)
         ->withRepositoryPHIDs(array($repository->getPHID()))
         ->withObjectHashes($hashes)
         ->execute();
       $refs = mpull($refs, null, 'getObjectHash');
     } else {
       $refs = array();
     }
 
     $file_phids = mpull($refs, 'getFilePHID');
     if ($file_phids) {
       $files = id(new PhabricatorFileQuery())
         ->setViewer($viewer)
         ->withPHIDs($file_phids)
         ->execute();
       $files = mpull($files, null, 'getPHID');
     } else {
       $files = array();
     }
 
     $authorization = null;
     $output = array();
     foreach ($objects as $object) {
       $oid = idx($object, 'oid');
       $size = idx($object, 'size');
       $ref = idx($refs, $oid);
       $error = null;
 
       // NOTE: If we already have a ref for this object, we only emit a
       // "download" action. The client should not upload the file again.
 
       $actions = array();
       if ($ref) {
         $file = idx($files, $ref->getFilePHID());
         if ($file) {
           // Git LFS may prompt users for authentication if the action does
           // not provide an "Authorization" header and does not have a query
           // parameter named "token". See here for discussion:
           // <https://github.com/github/git-lfs/issues/1088>
           $no_authorization = 'Basic '.base64_encode('none');
 
-          $get_uri = $file->getCDNURIWithToken();
+          $get_uri = $file->getCDNURI();
           $actions['download'] = array(
             'href' => $get_uri,
             'header' => array(
               'Authorization' => $no_authorization,
+              'X-Phabricator-Request-Type' => 'git-lfs',
             ),
           );
         } else {
           $error = array(
             'code' => 404,
             'message' => pht(
               'Object "%s" was previously uploaded, but no longer exists '.
               'on this server.',
               $oid),
           );
         }
       } else if ($want_upload) {
         if (!$authorization) {
           // Here, we could reuse the existing authorization if we have one,
           // but it's a little simpler to just generate a new one
           // unconditionally.
           $authorization = $this->newGitLFSHTTPAuthorization(
             $repository,
             $viewer,
             $operation);
         }
 
         $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}");
 
         $actions['upload'] = array(
           'href' => $put_uri,
           'header' => array(
             'Authorization' => $authorization,
             'X-Phabricator-Request-Type' => 'git-lfs',
           ),
         );
       }
 
       $object = array(
         'oid' => $oid,
         'size' => $size,
       );
 
       if ($actions) {
         $object['actions'] = $actions;
       }
 
       if ($error) {
         $object['error'] = $error;
       }
 
       $output[] = $object;
     }
 
     $output = array(
       'objects' => $output,
     );
 
     return id(new DiffusionGitLFSResponse())
       ->setContent($output);
   }
 
   private function serveGitLFSUploadRequest(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer,
     $oid) {
 
     $ref = id(new PhabricatorRepositoryGitLFSRefQuery())
       ->setViewer($viewer)
       ->withRepositoryPHIDs(array($repository->getPHID()))
       ->withObjectHashes(array($oid))
       ->executeOne();
     if ($ref) {
       return DiffusionGitLFSResponse::newErrorResponse(
         405,
         pht(
           'Content for object "%s" is already known to this server. It can '.
           'not be uploaded again.',
           $oid));
     }
 
     // Remove the execution time limit because uploading large files may take
     // a while.
     set_time_limit(0);
 
     $request_stream = new AphrontRequestStream();
     $request_iterator = $request_stream->getIterator();
     $hashing_iterator = id(new PhutilHashingIterator($request_iterator))
       ->setAlgorithm('sha256');
 
     $source = id(new PhabricatorIteratorFileUploadSource())
       ->setName('lfs-'.$oid)
       ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
       ->setIterator($hashing_iterator);
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file = $source->uploadFile();
     unset($unguarded);
 
     $hash = $hashing_iterator->getHash();
     if ($hash !== $oid) {
       return DiffusionGitLFSResponse::newErrorResponse(
         400,
         pht(
           'Uploaded data is corrupt or invalid. Expected hash "%s", actual '.
           'hash "%s".',
           $oid,
           $hash));
     }
 
     $ref = id(new PhabricatorRepositoryGitLFSRef())
       ->setRepositoryPHID($repository->getPHID())
       ->setObjectHash($hash)
       ->setByteSize($file->getByteSize())
       ->setAuthorPHID($viewer->getPHID())
       ->setFilePHID($file->getPHID());
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       // Attach the file to the repository to give users permission
       // to access it.
       $file->attachToObject($repository->getPHID());
       $ref->save();
     unset($unguarded);
 
     // This is just a plain HTTP 200 with no content, which is what `git lfs`
     // expects.
     return new DiffusionGitLFSResponse();
   }
 
   private function newGitLFSHTTPAuthorization(
     PhabricatorRepository $repository,
     PhabricatorUser $viewer,
     $operation) {
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 
     $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization(
       $repository,
       $viewer,
       $operation);
 
     unset($unguarded);
 
     return $authorization;
   }
 
   private function getGitLFSRequestPath(PhabricatorRepository $repository) {
     $request_path = $this->getRequestDirectoryPath($repository);
 
     $matches = null;
     if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) {
       return $matches[1];
     }
 
     return null;
   }
 
   private function getGitLFSInput() {
     if (!$this->gitLFSInput) {
       $input = PhabricatorStartup::getRawInput();
       $input = phutil_json_decode($input);
       $this->gitLFSInput = $input;
     }
 
     return $this->gitLFSInput;
   }
 
   private function isGitLFSReadOnlyRequest(PhabricatorRepository $repository) {
     if (!$this->getIsGitLFSRequest()) {
       return false;
     }
 
     $path = $this->getGitLFSRequestPath($repository);
 
     if ($path === 'objects/batch') {
       $input = $this->getGitLFSInput();
       $operation = idx($input, 'operation');
       switch ($operation) {
         case 'download':
           return true;
         default:
           return false;
       }
     }
 
     return false;
   }
 
 
 }
diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php
index ec7646833..4aeca4c38 100644
--- a/src/applications/diffusion/query/DiffusionCommitQuery.php
+++ b/src/applications/diffusion/query/DiffusionCommitQuery.php
@@ -1,648 +1,647 @@
 <?php
 
 final class DiffusionCommitQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $authorPHIDs;
   private $defaultRepository;
   private $identifiers;
   private $repositoryIDs;
   private $repositoryPHIDs;
   private $identifierMap;
 
   private $needAuditRequests;
   private $auditIDs;
   private $auditorPHIDs;
   private $needsAuditByPHIDs;
   private $auditStatus;
   private $epochMin;
   private $epochMax;
   private $importing;
 
   const AUDIT_STATUS_ANY       = 'audit-status-any';
   const AUDIT_STATUS_OPEN      = 'audit-status-open';
   const AUDIT_STATUS_CONCERN   = 'audit-status-concern';
   const AUDIT_STATUS_ACCEPTED  = 'audit-status-accepted';
   const AUDIT_STATUS_PARTIAL   = 'audit-status-partial';
 
   private $needCommitData;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withAuthorPHIDs(array $phids) {
     $this->authorPHIDs = $phids;
     return $this;
   }
 
   /**
    * Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234",
    * or "a9caf12". When an identifier matches multiple commits, they will all
    * be returned; callers should be prepared to deal with more results than
    * they queried for.
    */
   public function withIdentifiers(array $identifiers) {
     // Some workflows (like blame lookups) can pass in large numbers of
     // duplicate identifiers. We only care about unique identifiers, so
     // get rid of duplicates immediately.
     $identifiers = array_fuse($identifiers);
 
     $this->identifiers = $identifiers;
     return $this;
   }
 
   /**
    * Look up commits in a specific repository. This is a shorthand for calling
    * @{method:withDefaultRepository} and @{method:withRepositoryIDs}.
    */
   public function withRepository(PhabricatorRepository $repository) {
     $this->withDefaultRepository($repository);
     $this->withRepositoryIDs(array($repository->getID()));
     return $this;
   }
 
   /**
    * Look up commits in a specific repository. Prefer
    * @{method:withRepositoryIDs}; the underyling table is keyed by ID such
    * that this method requires a separate initial query to map PHID to ID.
    */
   public function withRepositoryPHIDs(array $phids) {
     $this->repositoryPHIDs = $phids;
     return $this;
   }
 
   /**
    * If a default repository is provided, ambiguous commit identifiers will
    * be assumed to belong to the default repository.
    *
    * For example, "r123" appearing in a commit message in repository X is
    * likely to be unambiguously "rX123". Normally the reference would be
    * considered ambiguous, but if you provide a default repository it will
    * be correctly resolved.
    */
   public function withDefaultRepository(PhabricatorRepository $repository) {
     $this->defaultRepository = $repository;
     return $this;
   }
 
   public function withRepositoryIDs(array $repository_ids) {
     $this->repositoryIDs = $repository_ids;
     return $this;
   }
 
   public function needCommitData($need) {
     $this->needCommitData = $need;
     return $this;
   }
 
   public function needAuditRequests($need) {
     $this->needAuditRequests = $need;
     return $this;
   }
 
   public function withAuditIDs(array $ids) {
     $this->auditIDs = $ids;
     return $this;
   }
 
   public function withAuditorPHIDs(array $auditor_phids) {
     $this->auditorPHIDs = $auditor_phids;
     return $this;
   }
 
   public function withNeedsAuditByPHIDs(array $needs_phids) {
     $this->needsAuditByPHIDs = $needs_phids;
     return $this;
   }
 
   public function withAuditStatus($status) {
     $this->auditStatus = $status;
     return $this;
   }
 
   public function withEpochRange($min, $max) {
     $this->epochMin = $min;
     $this->epochMax = $max;
     return $this;
   }
 
   public function withImporting($importing) {
     $this->importing = $importing;
     return $this;
   }
 
   public function getIdentifierMap() {
     if ($this->identifierMap === null) {
       throw new Exception(
         pht(
           'You must %s the query before accessing the identifier map.',
           'execute()'));
     }
     return $this->identifierMap;
   }
 
   protected function getPrimaryTableAlias() {
     return 'commit';
   }
 
   protected function willExecute() {
     if ($this->identifierMap === null) {
       $this->identifierMap = array();
     }
   }
 
   public function newResultObject() {
     return new PhabricatorRepositoryCommit();
   }
 
   protected function loadPage() {
     return $this->loadStandardPage($this->newResultObject());
   }
 
   protected function willFilterPage(array $commits) {
     $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID');
     $repos = id(new PhabricatorRepositoryQuery())
       ->setViewer($this->getViewer())
       ->withIDs($repository_ids)
       ->execute();
 
     $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
     $result = array();
 
     foreach ($commits as $key => $commit) {
       $repo = idx($repos, $commit->getRepositoryID());
       if ($repo) {
         $commit->attachRepository($repo);
       } else {
         $this->didRejectResult($commit);
         unset($commits[$key]);
         continue;
       }
 
       // Build the identifierMap
       if ($this->identifiers !== null) {
         $ids = $this->identifiers;
         $prefixes = array(
           'r'.$commit->getRepository()->getCallsign(),
           'r'.$commit->getRepository()->getCallsign().':',
           'R'.$commit->getRepository()->getID().':',
           '', // No prefix is valid too and will only match the commitIdentifier
         );
         $suffix = $commit->getCommitIdentifier();
 
         if ($commit->getRepository()->isSVN()) {
           foreach ($prefixes as $prefix) {
             if (isset($ids[$prefix.$suffix])) {
               $result[$prefix.$suffix][] = $commit;
             }
           }
         } else {
           // This awkward construction is so we can link the commits up in O(N)
           // time instead of O(N^2).
           for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) {
             $part = substr($suffix, 0, $ii);
             foreach ($prefixes as $prefix) {
               if (isset($ids[$prefix.$part])) {
                 $result[$prefix.$part][] = $commit;
               }
             }
           }
         }
       }
     }
 
     if ($result) {
       foreach ($result as $identifier => $matching_commits) {
         if (count($matching_commits) == 1) {
           $result[$identifier] = head($matching_commits);
         } else {
           // This reference is ambiguous (it matches more than one commit) so
           // don't link it.
           unset($result[$identifier]);
         }
       }
       $this->identifierMap += $result;
     }
 
     return $commits;
   }
 
   protected function didFilterPage(array $commits) {
     if ($this->needCommitData) {
       $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
         'commitID in (%Ld)',
         mpull($commits, 'getID'));
       $data = mpull($data, null, 'getCommitID');
       foreach ($commits as $commit) {
         $commit_data = idx($data, $commit->getID());
         if (!$commit_data) {
           $commit_data = new PhabricatorRepositoryCommitData();
         }
         $commit->attachCommitData($commit_data);
       }
     }
 
     // TODO: This should just be `needAuditRequests`, not `shouldJoinAudits()`,
     // but leave that for a future diff.
 
     if ($this->needAuditRequests || $this->shouldJoinAudits()) {
       $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
         'commitPHID IN (%Ls)',
         mpull($commits, 'getPHID'));
 
       $requests = mgroup($requests, 'getCommitPHID');
       foreach ($commits as $commit) {
         $audit_requests = idx($requests, $commit->getPHID(), array());
         $commit->attachAudits($audit_requests);
         foreach ($audit_requests as $audit_request) {
           $audit_request->attachCommit($commit);
         }
       }
     }
 
     return $commits;
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->repositoryPHIDs !== null) {
       $map_repositories = id(new PhabricatorRepositoryQuery())
         ->setViewer($this->getViewer())
         ->withPHIDs($this->repositoryPHIDs)
         ->execute();
 
       if (!$map_repositories) {
         throw new PhabricatorEmptyQueryException();
       }
       $repository_ids = mpull($map_repositories, 'getID');
       if ($this->repositoryIDs !== null) {
         $repository_ids = array_merge($repository_ids, $this->repositoryIDs);
       }
       $this->withRepositoryIDs($repository_ids);
     }
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->repositoryIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.repositoryID IN (%Ld)',
         $this->repositoryIDs);
     }
 
     if ($this->authorPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.authorPHID IN (%Ls)',
         $this->authorPHIDs);
     }
 
     if ($this->epochMin !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.epoch >= %d',
         $this->epochMin);
     }
 
     if ($this->epochMax !== null) {
       $where[] = qsprintf(
         $conn,
         'commit.epoch <= %d',
         $this->epochMax);
     }
 
     if ($this->importing !== null) {
       if ($this->importing) {
         $where[] = qsprintf(
           $conn,
           '(commit.importStatus & %d) != %d',
           PhabricatorRepositoryCommit::IMPORTED_ALL,
           PhabricatorRepositoryCommit::IMPORTED_ALL);
       } else {
         $where[] = qsprintf(
           $conn,
           '(commit.importStatus & %d) = %d',
           PhabricatorRepositoryCommit::IMPORTED_ALL,
           PhabricatorRepositoryCommit::IMPORTED_ALL);
       }
     }
 
     if ($this->identifiers !== null) {
       $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
       $min_qualified   = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
 
       $refs = array();
       $bare = array();
       foreach ($this->identifiers as $identifier) {
         $matches = null;
         preg_match('/^(?:[rR]([A-Z]+:?|[0-9]+:))?(.*)$/',
           $identifier, $matches);
         $repo = nonempty(rtrim($matches[1], ':'), null);
         $commit_identifier = nonempty($matches[2], null);
 
         if ($repo === null) {
           if ($this->defaultRepository) {
-            $repo = $this->defaultRepository->getCallsign();
+            $repo = $this->defaultRepository->getPHID();
           }
         }
 
         if ($repo === null) {
           if (strlen($commit_identifier) < $min_unqualified) {
             continue;
           }
           $bare[] = $commit_identifier;
         } else {
           $refs[] = array(
-            'callsign' => $repo,
+            'repository' => $repo,
             'identifier' => $commit_identifier,
           );
         }
       }
 
       $sql = array();
 
       foreach ($bare as $identifier) {
         $sql[] = qsprintf(
           $conn,
           '(commit.commitIdentifier LIKE %> AND '.
           'LENGTH(commit.commitIdentifier) = 40)',
           $identifier);
       }
 
       if ($refs) {
-        $callsigns = ipull($refs, 'callsign');
+        $repositories = ipull($refs, 'repository');
 
         $repos = id(new PhabricatorRepositoryQuery())
           ->setViewer($this->getViewer())
-          ->withIdentifiers($callsigns);
+          ->withIdentifiers($repositories);
         $repos->execute();
 
         $repos = $repos->getIdentifierMap();
         foreach ($refs as $key => $ref) {
-          $repo = idx($repos, $ref['callsign']);
-
+          $repo = idx($repos, $ref['repository']);
           if (!$repo) {
             continue;
           }
 
           if ($repo->isSVN()) {
             if (!ctype_digit((string)$ref['identifier'])) {
               continue;
             }
             $sql[] = qsprintf(
               $conn,
               '(commit.repositoryID = %d AND commit.commitIdentifier = %s)',
               $repo->getID(),
               // NOTE: Because the 'commitIdentifier' column is a string, MySQL
               // ignores the index if we hand it an integer. Hand it a string.
               // See T3377.
               (int)$ref['identifier']);
           } else {
             if (strlen($ref['identifier']) < $min_qualified) {
               continue;
             }
 
             $identifier = $ref['identifier'];
             if (strlen($identifier) == 40) {
               // MySQL seems to do slightly better with this version if the
               // clause, so issue it if we have a full commit hash.
               $sql[] = qsprintf(
                 $conn,
                 '(commit.repositoryID = %d
                   AND commit.commitIdentifier = %s)',
                 $repo->getID(),
                 $identifier);
             } else {
               $sql[] = qsprintf(
                 $conn,
                 '(commit.repositoryID = %d
                   AND commit.commitIdentifier LIKE %>)',
                 $repo->getID(),
                 $identifier);
             }
           }
         }
       }
 
       if (!$sql) {
         // If we discarded all possible identifiers (e.g., they all referenced
         // bogus repositories or were all too short), make sure the query finds
         // nothing.
         throw new PhabricatorEmptyQueryException(
           pht('No commit identifiers.'));
       }
 
       $where[] = '('.implode(' OR ', $sql).')';
     }
 
     if ($this->auditIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'audit.id IN (%Ld)',
         $this->auditIDs);
     }
 
     if ($this->auditorPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'audit.auditorPHID IN (%Ls)',
         $this->auditorPHIDs);
     }
 
     if ($this->needsAuditByPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'needs.auditorPHID IN (%Ls)',
         $this->needsAuditByPHIDs);
     }
 
     $status = $this->auditStatus;
     if ($status !== null) {
       switch ($status) {
         case self::AUDIT_STATUS_PARTIAL:
           $where[] = qsprintf(
             $conn,
             'commit.auditStatus = %d',
             PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED);
           break;
         case self::AUDIT_STATUS_ACCEPTED:
           $where[] = qsprintf(
             $conn,
             'commit.auditStatus = %d',
             PhabricatorAuditCommitStatusConstants::FULLY_AUDITED);
           break;
         case self::AUDIT_STATUS_CONCERN:
           $where[] = qsprintf(
             $conn,
             'status.auditStatus = %s',
             PhabricatorAuditStatusConstants::CONCERNED);
           break;
         case self::AUDIT_STATUS_OPEN:
           $where[] = qsprintf(
             $conn,
             'status.auditStatus in (%Ls)',
             PhabricatorAuditStatusConstants::getOpenStatusConstants());
           break;
         case self::AUDIT_STATUS_ANY:
           break;
         default:
           $valid = array(
             self::AUDIT_STATUS_ANY,
             self::AUDIT_STATUS_OPEN,
             self::AUDIT_STATUS_CONCERN,
             self::AUDIT_STATUS_ACCEPTED,
             self::AUDIT_STATUS_PARTIAL,
           );
           throw new Exception(
             pht(
               "Unknown audit status '%s'! Valid statuses are: %s.",
               $status,
               implode(', ', $valid)));
       }
     }
 
     return $where;
   }
 
   protected function didFilterResults(array $filtered) {
     if ($this->identifierMap) {
       foreach ($this->identifierMap as $name => $commit) {
         if (isset($filtered[$commit->getPHID()])) {
           unset($this->identifierMap[$name]);
         }
       }
     }
   }
 
   private function shouldJoinStatus() {
     return $this->auditStatus;
   }
 
   private function shouldJoinAudits() {
     return $this->auditIDs || $this->auditorPHIDs;
   }
 
   private function shouldJoinNeeds() {
     return $this->needsAuditByPHIDs;
   }
 
   protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
     $join = parent::buildJoinClauseParts($conn);
     $audit_request = new PhabricatorRepositoryAuditRequest();
 
     if ($this->shouldJoinStatus()) {
       $join[] = qsprintf(
         $conn,
         'LEFT JOIN %T status ON commit.phid = status.commitPHID',
         $audit_request->getTableName());
     }
 
     if ($this->shouldJoinAudits()) {
       $join[] = qsprintf(
         $conn,
         'JOIN %T audit ON commit.phid = audit.commitPHID',
         $audit_request->getTableName());
     }
 
     if ($this->shouldJoinNeeds()) {
       $join[] = qsprintf(
         $conn,
         'JOIN %T needs ON commit.phid = needs.commitPHID
           AND needs.auditStatus IN (%Ls)',
         $audit_request->getTableName(),
         array(
           PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
           PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
         ));
     }
 
     return $join;
   }
 
   protected function shouldGroupQueryResultRows() {
     if ($this->shouldJoinStatus()) {
       return true;
     }
 
     if ($this->shouldJoinAudits()) {
       return true;
     }
 
     if ($this->shouldJoinNeeds()) {
       return true;
     }
 
     return parent::shouldGroupQueryResultRows();
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorDiffusionApplication';
   }
 
   public function getOrderableColumns() {
     return parent::getOrderableColumns() + array(
       'epoch' => array(
         'table' => $this->getPrimaryTableAlias(),
         'column' => 'epoch',
         'type' => 'int',
         'reverse' => false,
       ),
     );
   }
 
   protected function getPagingValueMap($cursor, array $keys) {
     $commit = $this->loadCursorObject($cursor);
     return array(
       'id' => $commit->getID(),
       'epoch' => $commit->getEpoch(),
     );
   }
 
   public function getBuiltinOrders() {
     $parent = parent::getBuiltinOrders();
 
     // Rename the default ID-based orders.
     $parent['importnew'] = array(
       'name' => pht('Import Date (Newest First)'),
     ) + $parent['newest'];
 
     $parent['importold'] = array(
       'name' => pht('Import Date (Oldest First)'),
     ) + $parent['oldest'];
 
     return array(
       'newest' => array(
         'vector' => array('epoch', 'id'),
         'name' => pht('Commit Date (Newest First)'),
       ),
       'oldest' => array(
         'vector' => array('-epoch', '-id'),
         'name' => pht('Commit Date (Oldest First)'),
       ),
     ) + $parent;
   }
 
 
 }
diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index 1785ae674..bf69497b1 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,690 +1,688 @@
 <?php
 
 final class DivinerAtomController extends DivinerController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $book_name    = $request->getURIData('book');
     $atom_type    = $request->getURIData('type');
     $atom_name    = $request->getURIData('name');
     $atom_context = nonempty($request->getURIData('context'), null);
     $atom_index   = nonempty($request->getURIData('index'), null);
 
     require_celerity_resource('diviner-shared-css');
 
     $book = id(new DivinerBookQuery())
       ->setViewer($viewer)
       ->withNames(array($book_name))
       ->executeOne();
 
     if (!$book) {
       return new Aphront404Response();
     }
 
     $symbol = id(new DivinerAtomQuery())
       ->setViewer($viewer)
       ->withBookPHIDs(array($book->getPHID()))
       ->withTypes(array($atom_type))
       ->withNames(array($atom_name))
       ->withContexts(array($atom_context))
       ->withIndexes(array($atom_index))
       ->withIsDocumentable(true)
       ->needAtoms(true)
       ->needExtends(true)
       ->needChildren(true)
       ->executeOne();
 
     if (!$symbol) {
       return new Aphront404Response();
     }
 
     $atom = $symbol->getAtom();
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
 
     $crumbs->addTextCrumb(
       $book->getShortTitle(),
       '/book/'.$book->getName().'/');
 
     $atom_short_title = $atom
       ? $atom->getDocblockMetaValue('short', $symbol->getTitle())
       : $symbol->getTitle();
 
     $crumbs->addTextCrumb($atom_short_title);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($this->renderFullSignature($symbol));
 
     $properties = new PHUIPropertyListView();
 
     $group = $atom ? $atom->getProperty('group') : $symbol->getGroupName();
     if ($group) {
       $group_name = $book->getGroupName($group);
     } else {
       $group_name = null;
     }
 
     $prop_list = new PHUIPropertyGroupView();
     $prop_list->addPropertyList($properties);
 
     $document = id(new PHUIDocumentViewPro())
       ->setBook($book->getTitle(), $group_name)
       ->setHeader($header)
       ->addClass('diviner-view');
 
     if ($atom) {
       $this->buildDefined($properties, $symbol);
       $this->buildExtendsAndImplements($properties, $symbol);
       $this->buildRepository($properties, $symbol);
 
       $warnings = $atom->getWarnings();
       if ($warnings) {
         $warnings = id(new PHUIInfoView())
           ->setErrors($warnings)
           ->setTitle(pht('Documentation Warnings'))
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING);
       }
 
       $document->appendChild($warnings);
     }
 
     $methods = $this->composeMethods($symbol);
 
     $field = 'default';
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer)
       ->addObject($symbol, $field);
     foreach ($methods as $method) {
       foreach ($method['atoms'] as $matom) {
         $engine->addObject($matom, $field);
       }
     }
     $engine->process();
 
     if ($atom) {
       $content = $this->renderDocumentationText($symbol, $engine);
       $document->appendChild($content);
     }
 
     $toc = $engine->getEngineMetadata(
       $symbol,
       $field,
       PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC,
       array());
 
     if (!$atom) {
       $document->appendChild(
         id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
           ->appendChild(pht('This atom no longer exists.')));
     }
 
     if ($atom) {
       $document->appendChild($this->buildParametersAndReturn(array($symbol)));
     }
 
     if ($methods) {
       $tasks = $this->composeTasks($symbol);
 
       if ($tasks) {
         $methods_by_task = igroup($methods, 'task');
 
         // Add phantom tasks for methods which have a "@task" name that isn't
         // documented anywhere, or methods that have no "@task" name.
         foreach ($methods_by_task as $task => $ignored) {
           if (empty($tasks[$task])) {
             $tasks[$task] = array(
               'name' => $task,
               'title' => $task ? $task : pht('Other Methods'),
               'defined' => $symbol,
             );
           }
         }
 
         $section = id(new DivinerSectionView())
           ->setHeader(pht('Tasks'));
 
         foreach ($tasks as $spec) {
           $section->addContent(
             id(new PHUIHeaderView())
               ->setNoBackground(true)
               ->setHeader($spec['title']));
 
           $task_methods = idx($methods_by_task, $spec['name'], array());
 
           $box_content = array();
           if ($task_methods) {
             $list_items = array();
             foreach ($task_methods as $task_method) {
               $atom = last($task_method['atoms']);
 
               $item = $this->renderFullSignature($atom, true);
 
               if (strlen($atom->getSummary())) {
                 $item = array(
                   $item,
                   " \xE2\x80\x94 ",
                   $atom->getSummary(),
                 );
               }
 
               $list_items[] = phutil_tag('li', array(), $item);
             }
 
             $box_content[] = phutil_tag(
               'ul',
               array(
                 'class' => 'diviner-list',
               ),
               $list_items);
           } else {
             $no_methods = pht('No methods for this task.');
             $box_content = phutil_tag('em', array(), $no_methods);
           }
 
           $inner_box = phutil_tag_div('diviner-task-items', $box_content);
           $section->addContent($inner_box);
         }
         $document->appendChild($section);
       }
 
       $section = id(new DivinerSectionView())
         ->setHeader(pht('Methods'));
 
       foreach ($methods as $spec) {
         $matom = last($spec['atoms']);
         $method_header = id(new PHUIHeaderView())
           ->setNoBackground(true);
 
         $inherited = $spec['inherited'];
         if ($inherited) {
           $method_header->addTag(
             id(new PHUITagView())
               ->setType(PHUITagView::TYPE_STATE)
               ->setBackgroundColor(PHUITagView::COLOR_GREY)
               ->setName(pht('Inherited')));
         }
 
         $method_header->setHeader($this->renderFullSignature($matom));
 
         $section->addContent(
           array(
             $method_header,
             $this->renderMethodDocumentationText($symbol, $spec, $engine),
             $this->buildParametersAndReturn($spec['atoms']),
           ));
       }
       $document->appendChild($section);
     }
 
     if ($toc) {
       $side = new PHUIListView();
       $side->addMenuItem(
         id(new PHUIListItemView())
           ->setName(pht('Contents'))
           ->setType(PHUIListItemView::TYPE_LABEL));
       foreach ($toc as $key => $entry) {
         $side->addMenuItem(
           id(new PHUIListItemView())
             ->setName($entry[1])
             ->setHref('#'.$key));
       }
 
       $document->setToc($side);
     }
 
     $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    return $this->newPage()
+      ->setTitle($symbol->getTitle())
+      ->setCrumbs($crumbs)
+      ->appendChild(array(
         $document,
         $prop_list,
-      ),
-      array(
-        'title' => $symbol->getTitle(),
       ));
   }
 
   private function buildExtendsAndImplements(
     PHUIPropertyListView $view,
     DivinerLiveSymbol $symbol) {
 
     $lineage = $this->getExtendsLineage($symbol);
     if ($lineage) {
       $tags = array();
       foreach ($lineage as $item) {
         $tags[] = $this->renderAtomTag($item);
       }
 
       $caret = phutil_tag('span', array('class' => 'caret-right msl msr'));
       $tags = phutil_implode_html($caret, $tags);
       $view->addProperty(pht('Extends'), $tags);
     }
 
     $implements = $this->getImplementsLineage($symbol);
     if ($implements) {
       $items = array();
       foreach ($implements as $spec) {
         $via = $spec['via'];
         $iface = $spec['interface'];
         if ($via == $symbol) {
           $items[] = $this->renderAtomTag($iface);
         } else {
           $items[] = array(
             $this->renderAtomTag($iface),
             "  \xE2\x97\x80  ",
             $this->renderAtomTag($via),
           );
         }
       }
 
       $view->addProperty(
         pht('Implements'),
         phutil_implode_html(phutil_tag('br'), $items));
     }
   }
 
   private function buildRepository(
     PHUIPropertyListView $view,
     DivinerLiveSymbol $symbol) {
 
     if (!$symbol->getRepositoryPHID()) {
       return;
     }
 
     $view->addProperty(
       pht('Repository'),
       $this->getViewer()->renderHandle($symbol->getRepositoryPHID()));
   }
 
   private function renderAtomTag(DivinerLiveSymbol $symbol) {
     return id(new PHUITagView())
       ->setType(PHUITagView::TYPE_OBJECT)
       ->setName($symbol->getName())
       ->setHref($symbol->getURI());
   }
 
   private function getExtendsLineage(DivinerLiveSymbol $symbol) {
     foreach ($symbol->getExtends() as $extends) {
       if ($extends->getType() == 'class') {
         $lineage = $this->getExtendsLineage($extends);
         $lineage[] = $extends;
         return $lineage;
       }
     }
     return array();
   }
 
   private function getImplementsLineage(DivinerLiveSymbol $symbol) {
     $implements = array();
 
     // Do these first so we get interfaces ordered from most to least specific.
     foreach ($symbol->getExtends() as $extends) {
       if ($extends->getType() == 'interface') {
         $implements[$extends->getName()] = array(
           'interface' => $extends,
           'via' => $symbol,
         );
       }
     }
 
     // Now do parent interfaces.
     foreach ($symbol->getExtends() as $extends) {
       if ($extends->getType() == 'class') {
         $implements += $this->getImplementsLineage($extends);
       }
     }
 
     return $implements;
   }
 
   private function buildDefined(
     PHUIPropertyListView $view,
     DivinerLiveSymbol $symbol) {
 
     $atom = $symbol->getAtom();
     $defined = $atom->getFile().':'.$atom->getLine();
 
     $link = $symbol->getBook()->getConfig('uri.source');
     if ($link) {
       $link = strtr(
         $link,
         array(
           '%%' => '%',
           '%f' => phutil_escape_uri($atom->getFile()),
           '%l' => phutil_escape_uri($atom->getLine()),
         ));
       $defined = phutil_tag(
         'a',
         array(
           'href' => $link,
           'target' => '_blank',
         ),
         $defined);
     }
 
     $view->addProperty(pht('Defined'), $defined);
   }
 
   private function composeMethods(DivinerLiveSymbol $symbol) {
     $methods = $this->findMethods($symbol);
     if (!$methods) {
       return $methods;
     }
 
     foreach ($methods as $name => $method) {
       // Check for "@task" on each parent, to find the most recently declared
       // "@task".
       $task = null;
       foreach ($method['atoms'] as $key => $method_symbol) {
         $atom = $method_symbol->getAtom();
         if ($atom->getDocblockMetaValue('task')) {
           $task = $atom->getDocblockMetaValue('task');
         }
       }
       $methods[$name]['task'] = $task;
 
       // Set 'inherited' if this atom has no implementation of the method.
       if (last($method['implementations']) !== $symbol) {
         $methods[$name]['inherited'] = true;
       } else {
         $methods[$name]['inherited'] = false;
       }
     }
 
     return $methods;
   }
 
   private function findMethods(DivinerLiveSymbol $symbol) {
     $child_specs = array();
     foreach ($symbol->getExtends() as $extends) {
       if ($extends->getType() == DivinerAtom::TYPE_CLASS) {
         $child_specs = $this->findMethods($extends);
       }
     }
 
     foreach ($symbol->getChildren() as $child) {
       if ($child->getType() == DivinerAtom::TYPE_METHOD) {
         $name = $child->getName();
         if (isset($child_specs[$name])) {
           $child_specs[$name]['atoms'][] = $child;
           $child_specs[$name]['implementations'][] = $symbol;
         } else {
           $child_specs[$name] = array(
             'atoms' => array($child),
             'defined' => $symbol,
             'implementations' => array($symbol),
           );
         }
       }
     }
 
     return $child_specs;
   }
 
   private function composeTasks(DivinerLiveSymbol $symbol) {
     $extends_task_specs = array();
     foreach ($symbol->getExtends() as $extends) {
       $extends_task_specs += $this->composeTasks($extends);
     }
 
     $task_specs = array();
 
     $tasks = $symbol->getAtom()->getDocblockMetaValue('task');
     if (strlen($tasks)) {
       $tasks = phutil_split_lines($tasks, $retain_endings = false);
 
       foreach ($tasks as $task) {
         list($name, $title) = explode(' ', $task, 2);
         $name = trim($name);
         $title = trim($title);
 
         $task_specs[$name] = array(
           'name'        => $name,
           'title'       => $title,
           'defined'     => $symbol,
         );
       }
     }
 
     $specs = $task_specs + $extends_task_specs;
 
     // Reorder "@tasks" in original declaration order. Basically, we want to
     // use the documentation of the closest subclass, but put tasks which
     // were declared by parents first.
     $keys = array_keys($extends_task_specs);
     $specs = array_select_keys($specs, $keys) + $specs;
 
     return $specs;
   }
 
   private function renderFullSignature(
     DivinerLiveSymbol $symbol,
     $is_link = false) {
 
     switch ($symbol->getType()) {
       case DivinerAtom::TYPE_CLASS:
       case DivinerAtom::TYPE_INTERFACE:
       case DivinerAtom::TYPE_METHOD:
       case DivinerAtom::TYPE_FUNCTION:
         break;
       default:
         return $symbol->getTitle();
     }
 
     $atom = $symbol->getAtom();
 
     $out = array();
 
     if ($atom) {
       if ($atom->getProperty('final')) {
         $out[] = 'final';
       }
 
       if ($atom->getProperty('abstract')) {
         $out[] = 'abstract';
       }
 
       if ($atom->getProperty('access')) {
         $out[] = $atom->getProperty('access');
       }
 
       if ($atom->getProperty('static')) {
         $out[] = 'static';
       }
     }
 
     switch ($symbol->getType()) {
       case DivinerAtom::TYPE_CLASS:
       case DivinerAtom::TYPE_INTERFACE:
         $out[] = $symbol->getType();
         break;
       case DivinerAtom::TYPE_FUNCTION:
         switch ($atom->getLanguage()) {
           case 'php':
             $out[] = $symbol->getType();
             break;
         }
         break;
       case DivinerAtom::TYPE_METHOD:
         switch ($atom->getLanguage()) {
           case 'php':
             $out[] = DivinerAtom::TYPE_FUNCTION;
             break;
         }
         break;
     }
 
     $anchor = null;
     switch ($symbol->getType()) {
       case DivinerAtom::TYPE_METHOD:
         $anchor = $symbol->getType().'/'.$symbol->getName();
         break;
       default:
         break;
     }
 
     $out[] = phutil_tag(
       $anchor ? 'a' : 'span',
       array(
         'class' => 'diviner-atom-signature-name',
         'href' => $anchor ? '#'.$anchor : null,
         'name' => $is_link ? null : $anchor,
       ),
       $symbol->getName());
 
     $out = phutil_implode_html(' ', $out);
 
     if ($atom) {
       $parameters = $atom->getProperty('parameters');
       if ($parameters !== null) {
         $pout = array();
         foreach ($parameters as $parameter) {
           $pout[] = idx($parameter, 'name', '...');
         }
         $out = array($out, '('.implode(', ', $pout).')');
       }
     }
 
     return phutil_tag(
       'span',
       array(
         'class' => 'diviner-atom-signature',
       ),
       $out);
   }
 
   private function buildParametersAndReturn(array $symbols) {
     assert_instances_of($symbols, 'DivinerLiveSymbol');
 
     $symbols = array_reverse($symbols);
     $out = array();
 
     $collected_parameters = null;
     foreach ($symbols as $symbol) {
       $parameters = $symbol->getAtom()->getProperty('parameters');
       if ($parameters !== null) {
         if ($collected_parameters === null) {
           $collected_parameters = array();
         }
         foreach ($parameters as $key => $parameter) {
           if (isset($collected_parameters[$key])) {
             $collected_parameters[$key] += $parameter;
           } else {
             $collected_parameters[$key] = $parameter;
           }
         }
       }
     }
 
     if (nonempty($parameters)) {
       $out[] = id(new DivinerParameterTableView())
         ->setHeader(pht('Parameters'))
         ->setParameters($parameters);
     }
 
     $collected_return = null;
     foreach ($symbols as $symbol) {
       $return = $symbol->getAtom()->getProperty('return');
       if ($return) {
         if ($collected_return) {
           $collected_return += $return;
         } else {
           $collected_return = $return;
         }
       }
     }
 
     if (nonempty($return)) {
       $out[] = id(new DivinerReturnTableView())
         ->setHeader(pht('Return'))
         ->setReturn($collected_return);
     }
 
     return $out;
   }
 
   private function renderDocumentationText(
     DivinerLiveSymbol $symbol,
     PhabricatorMarkupEngine $engine) {
 
     $field = 'default';
     $content = $engine->getOutput($symbol, $field);
 
     if (strlen(trim($symbol->getMarkupText($field)))) {
       $content = phutil_tag(
         'div',
         array(
           'class' => 'phabricator-remarkup diviner-remarkup-section',
         ),
         $content);
     } else {
       $atom = $symbol->getAtom();
       $content = phutil_tag(
         'div',
         array(
           'class' => 'diviner-message-not-documented',
         ),
         DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType()));
     }
 
     return $content;
   }
 
   private function renderMethodDocumentationText(
     DivinerLiveSymbol $parent,
     array $spec,
     PhabricatorMarkupEngine $engine) {
 
     $symbols = array_values($spec['atoms']);
     $implementations = array_values($spec['implementations']);
 
     $field = 'default';
 
     $out = array();
     foreach ($symbols as $key => $symbol) {
       $impl = $implementations[$key];
       if ($impl !== $parent) {
         if (!strlen(trim($symbol->getMarkupText($field)))) {
           continue;
         }
       }
 
       $doc = $this->renderDocumentationText($symbol, $engine);
 
       if (($impl !== $parent) || $out) {
         $where = id(new PHUIBoxView())
           ->addClass('diviner-method-implementation-header')
           ->appendChild($impl->getName());
         $doc = array($where, $doc);
 
         if ($impl !== $parent) {
           $doc = phutil_tag(
             'div',
             array(
               'class' => 'diviner-method-implementation-inherited',
             ),
             $doc);
         }
       }
 
       $out[] = $doc;
     }
 
     // If we only have inherited implementations but none have documentation,
     // render the last one here so we get the "this thing has no documentation"
     // element.
     if (!$out) {
       $out[] = $this->renderDocumentationText($symbol, $engine);
     }
 
     return $out;
   }
 
 }
diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php
index 74b2a8f3d..1aa8790ff 100644
--- a/src/applications/diviner/controller/DivinerBookController.php
+++ b/src/applications/diviner/controller/DivinerBookController.php
@@ -1,128 +1,126 @@
 <?php
 
 final class DivinerBookController extends DivinerController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $book_name = $request->getURIData('book');
 
     $book = id(new DivinerBookQuery())
       ->setViewer($viewer)
       ->withNames(array($book_name))
       ->needRepositories(true)
       ->executeOne();
 
     if (!$book) {
       return new Aphront404Response();
     }
 
     $actions = $this->buildActionView($viewer, $book);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumbs->addTextCrumb(
       $book->getShortTitle(),
       '/book/'.$book->getName().'/');
 
     $header = id(new PHUIHeaderView())
       ->setHeader($book->getTitle())
       ->setUser($viewer)
       ->setPolicyObject($book)
       ->setEpoch($book->getDateModified())
       ->setActionList($actions);
 
     // TODO: This could probably look better.
     if ($book->getRepositoryPHID()) {
       $header->addTag(
         id(new PHUITagView())
           ->setType(PHUITagView::TYPE_STATE)
           ->setBackgroundColor(PHUITagView::COLOR_BLUE)
           ->setName($book->getRepository()->getMonogram()));
     }
 
     $document = new PHUIDocumentViewPro();
     $document->setHeader($header);
     $document->addClass('diviner-view');
 
     $atoms = id(new DivinerAtomQuery())
       ->setViewer($viewer)
       ->withBookPHIDs(array($book->getPHID()))
       ->withGhosts(false)
       ->withIsDocumentable(true)
       ->execute();
 
     $atoms = msort($atoms, 'getSortKey');
 
     $group_spec = $book->getConfig('groups');
     if (!is_array($group_spec)) {
       $group_spec = array();
     }
 
     $groups = mgroup($atoms, 'getGroupName');
     $groups = array_select_keys($groups, array_keys($group_spec)) + $groups;
     if (isset($groups[''])) {
       $no_group = $groups[''];
       unset($groups['']);
       $groups[''] = $no_group;
     }
 
     $out = array();
     foreach ($groups as $group => $atoms) {
       $group_name = $book->getGroupName($group);
       if (!strlen($group_name)) {
         $group_name = pht('Free Radicals');
       }
       $section = id(new DivinerSectionView())
         ->setHeader($group_name);
       $section->addContent($this->renderAtomList($atoms));
       $out[] = $section;
     }
 
     $preface = $book->getPreface();
     $preface_view = null;
     if (strlen($preface)) {
       $preface_view = new PHUIRemarkupView($viewer, $preface);
     }
 
     $document->appendChild($preface_view);
     $document->appendChild($out);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    return $this->newPage()
+      ->setTitle($book->getTitle())
+      ->setCrumbs($crumbs)
+      ->appendChild(array(
         $document,
-      ),
-      array(
-        'title' => $book->getTitle(),
       ));
   }
 
   private function buildActionView(
     PhabricatorUser $user,
     DivinerLiveBook $book) {
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $user,
       $book,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $action_view = id(new PhabricatorActionListView())
       ->setUser($user)
       ->setObject($book);
 
     $action_view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Book'))
         ->setIcon('fa-pencil')
         ->setHref('/book/'.$book->getName().'/edit/')
         ->setDisabled(!$can_edit));
 
     return $action_view;
   }
 
 }
diff --git a/src/applications/diviner/controller/DivinerBookEditController.php b/src/applications/diviner/controller/DivinerBookEditController.php
index b5c3e2dea..2b1f90579 100644
--- a/src/applications/diviner/controller/DivinerBookEditController.php
+++ b/src/applications/diviner/controller/DivinerBookEditController.php
@@ -1,127 +1,136 @@
 <?php
 
 final class DivinerBookEditController extends DivinerController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $book_name = $request->getURIData('book');
 
     $book = id(new DivinerBookQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->needProjectPHIDs(true)
       ->withNames(array($book_name))
       ->executeOne();
 
     if (!$book) {
       return new Aphront404Response();
     }
 
     $view_uri = '/book/'.$book->getName().'/';
 
     if ($request->isFormPost()) {
       $v_projects = $request->getArr('projectPHIDs');
       $v_view     = $request->getStr('viewPolicy');
       $v_edit     = $request->getStr('editPolicy');
 
       $xactions = array();
       $xactions[] = id(new DivinerLiveBookTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue(
           'edge:type',
           PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
         ->setNewValue(
           array(
             '=' => array_fuse($v_projects),
           ));
       $xactions[] = id(new DivinerLiveBookTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($v_view);
       $xactions[] = id(new DivinerLiveBookTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
         ->setNewValue($v_edit);
 
       id(new DivinerLiveBookEditor())
         ->setContinueOnNoEffect(true)
         ->setContentSourceFromRequest($request)
         ->setActor($viewer)
         ->applyTransactions($book, $xactions);
 
       return id(new AphrontRedirectResponse())->setURI($view_uri);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Basics'));
+    $crumbs->setBorder(true);
 
-    $title = pht('Edit %s', $book->getTitle());
+    $title = pht('Edit Book: %s', $book->getTitle());
+    $header_icon = 'fa-pencil';
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($book)
       ->execute();
     $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
     $edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new PhabricatorProjectDatasource())
           ->setName('projectPHIDs')
           ->setLabel(pht('Projects'))
           ->setValue($book->getProjectPHIDs()))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new DiffusionRepositoryDatasource())
           ->setName('repositoryPHIDs')
           ->setLabel(pht('Repository'))
           ->setDisableBehavior(true)
           ->setLimit(1)
           ->setValue($book->getRepositoryPHID()
             ? array($book->getRepositoryPHID())
             : null))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($book)
           ->setCapability($view_capability)
           ->setPolicies($policies)
           ->setCaption($book->describeAutomaticCapability($view_capability)))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($book)
           ->setCapability($edit_capability)
           ->setPolicies($policies)
           ->setCaption($book->describeAutomaticCapability($edit_capability)))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save'))
           ->addCancelButton($view_uri));
 
-    $object_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Book'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $timeline = $this->buildTransactionTimeline(
       $book,
       new DivinerLiveBookTransactionQuery());
     $timeline->setShouldTerminate(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $object_box,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $box,
         $timeline,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php
index b9e165fc1..1bedd65b0 100644
--- a/src/applications/diviner/controller/DivinerFindController.php
+++ b/src/applications/diviner/controller/DivinerFindController.php
@@ -1,94 +1,95 @@
 <?php
 
 final class DivinerFindController extends DivinerController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $book_name  = $request->getStr('book');
     $query_text = $request->getStr('name');
 
     $book = null;
     if ($book_name) {
       $book = id(new DivinerBookQuery())
         ->setViewer($viewer)
         ->withNames(array($book_name))
         ->executeOne();
 
       if (!$book) {
         return new Aphront404Response();
       }
     }
 
     $query = id(new DivinerAtomQuery())
       ->setViewer($viewer);
 
     if ($book) {
       $query->withBookPHIDs(array($book->getPHID()));
     }
 
     $context = $request->getStr('context');
     if (strlen($context)) {
       $query->withContexts(array($context));
     }
 
     $type = $request->getStr('type');
     if (strlen($type)) {
       $query->withTypes(array($type));
     }
 
     $query->withGhosts(false);
     $query->withIsDocumentable(true);
 
     $name_query = clone $query;
 
     $name_query->withNames(
       array(
         $query_text,
         // TODO: This could probably be more smartly normalized in the DB,
         // but just fake it for now.
         phutil_utf8_strtolower($query_text),
       ));
 
     $atoms = $name_query->execute();
 
     if (!$atoms) {
       $title_query = clone $query;
       $title_query->withTitles(array($query_text));
       $atoms = $title_query->execute();
     }
 
     $not_found_uri = $this->getApplicationURI();
 
     if (!$atoms) {
       $dialog = id(new AphrontDialogView())
         ->setUser($viewer)
         ->setTitle(pht('Documentation Not Found'))
         ->appendChild(
           pht(
             'Unable to find the specified documentation. '.
             'You may have followed a bad or outdated link.'))
         ->addCancelButton($not_found_uri, pht('Read More Documentation'));
 
       return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     if (count($atoms) == 1 && $request->getBool('jump')) {
       $atom_uri = head($atoms)->getURI();
       return id(new AphrontRedirectResponse())->setURI($atom_uri);
     }
 
     $list = $this->renderAtomList($atoms);
 
-    return $this->buildApplicationPage(
-      $list,
-      array(
-        'title' => array(pht('Find'), pht('"%s"', $query_text)),
+    return $this->newPage()
+      ->setTitle(array(pht('Find'), pht('"%s"', $query_text)))
+      ->appendChild(array(
+        $list,
       ));
+
   }
 
 }
diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php
index e85769060..e7d179c07 100644
--- a/src/applications/diviner/controller/DivinerMainController.php
+++ b/src/applications/diviner/controller/DivinerMainController.php
@@ -1,77 +1,75 @@
 <?php
 
 final class DivinerMainController extends DivinerController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $books = id(new DivinerBookQuery())
       ->setViewer($viewer)
       ->execute();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumbs->addTextCrumb(pht('Books'));
 
     $query_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setHref($this->getApplicationURI('query/'))
       ->setText(pht('Advanced Search'))
       ->setIcon('fa-search');
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Documentation Books'))
       ->addActionLink($query_button);
 
     $document = new PHUIDocumentViewPro();
     $document->setHeader($header);
     $document->addClass('diviner-view');
 
     if ($books) {
       $books = msort($books, 'getTitle');
       $list = array();
       foreach ($books as $book) {
         $item = id(new DivinerBookItemView())
           ->setTitle($book->getTitle())
           ->setHref('/book/'.$book->getName().'/')
           ->setSubtitle($book->getPreface());
         $list[] = $item;
       }
       $list = id(new PHUIBoxView())
         ->addPadding(PHUI::PADDING_MEDIUM_TOP)
         ->appendChild($list);
 
       $document->appendChild($list);
     } else {
       $text = pht(
         "(NOTE) **Looking for Phabricator documentation?** ".
         "If you're looking for help and information about Phabricator, ".
         "you can [[https://secure.phabricator.com/diviner/ | ".
         "browse the public Phabricator documentation]] on the live site.\n\n".
         "Diviner is the documentation generator used to build the ".
         "Phabricator documentation.\n\n".
         "You haven't generated any Diviner documentation books yet, so ".
         "there's nothing to show here. If you'd like to generate your own ".
         "local copy of the Phabricator documentation and have it appear ".
         "here, run this command:\n\n".
         "  %s\n\n",
         'phabricator/ $ ./bin/diviner generate');
 
       $text = new PHUIRemarkupView($viewer, $text);
       $document->appendChild($text);
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    return $this->newPage()
+      ->setTitle(pht('Documentation Books'))
+      ->setCrumbs($crumbs)
+      ->appendChild(array(
         $document,
-      ),
-      array(
-        'title' => pht('Documentation Books'),
       ));
   }
 }
diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php
index c6a8e25fd..1d79f52c1 100644
--- a/src/applications/drydock/controller/DrydockConsoleController.php
+++ b/src/applications/drydock/controller/DrydockConsoleController.php
@@ -1,80 +1,86 @@
 <?php
 
 final class DrydockConsoleController extends DrydockController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function buildSideNavView() {
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
 
     // These are only used on mobile.
 
     $nav->addFilter('blueprint', pht('Blueprints'));
     $nav->addFilter('resource', pht('Resources'));
     $nav->addFilter('lease', pht('Leases'));
     $nav->addFilter('operation', pht('Repository Operations'));
 
     $nav->selectFilter(null);
 
     return $nav;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $menu = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Blueprints'))
         ->setIcon('fa-map-o')
         ->setHref($this->getApplicationURI('blueprint/'))
         ->addAttribute(
           pht(
             'Configure blueprints so Drydock can build resources, like '.
             'hosts and working copies.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Resources'))
         ->setIcon('fa-map')
         ->setHref($this->getApplicationURI('resource/'))
         ->addAttribute(
           pht('View and manage resources Drydock has built, like hosts.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Leases'))
         ->setIcon('fa-link')
         ->setHref($this->getApplicationURI('lease/'))
         ->addAttribute(pht('Manage leases on resources.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Repository Operations'))
         ->setIcon('fa-fighter-jet')
         ->setHref($this->getApplicationURI('operation/'))
         ->addAttribute(pht('Review the repository operation queue.')));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Console'));
+    $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Drydock Console'))
       ->setObjectList($menu);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title'  => pht('Drydock Console'),
-      ));
+    $title = pht('Drydock Console');
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-truck');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php
index c6d38791f..5f35c91bb 100644
--- a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php
+++ b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php
@@ -1,59 +1,52 @@
 <?php
 
 final class DrydockRepositoryOperationStatusController
   extends DrydockRepositoryOperationController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $operation = id(new DrydockRepositoryOperationQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$operation) {
       return new Aphront404Response();
     }
 
     $id = $operation->getID();
 
     $status_view = id(new DrydockRepositoryOperationStatusView())
       ->setUser($viewer)
       ->setOperation($operation);
 
     if ($request->isAjax()) {
       $payload = array(
         'markup' => $status_view->renderUnderwayState(),
         'isUnderway' => $operation->isUnderway(),
       );
 
       return id(new AphrontAjaxResponse())
         ->setContent($payload);
     }
 
     $title = pht('Repository Operation %d', $id);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Operations'),
       $this->getApplicationURI('operation/'));
     $crumbs->addTextCrumb($title);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $status_view,
-      ),
-      array(
-        'title' => array(
-          $title,
-          pht('Status'),
-        ),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('Status'))
+      ->setCrumbs($crumbs)
+      ->appendChild($status_view);
   }
 
 }
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php
index 18f769cc4..16df8fca6 100644
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/controller/PhabricatorFactChartController.php
@@ -1,90 +1,89 @@
 <?php
 
 final class PhabricatorFactChartController extends PhabricatorFactController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $table = new PhabricatorFactRaw();
     $conn_r = $table->establishConnection('r');
     $table_name = $table->getTableName();
 
     $series = $request->getStr('y1');
 
     $specs = PhabricatorFactSpec::newSpecsForFactTypes(
       PhabricatorFactEngine::loadAllEngines(),
       array($series));
     $spec = idx($specs, $series);
 
     $data = queryfx_all(
       $conn_r,
       'SELECT valueX, epoch FROM %T WHERE factType = %s ORDER BY epoch ASC',
       $table_name,
       $series);
 
     $points = array();
     $sum = 0;
     foreach ($data as $key => $row) {
       $sum += (int)$row['valueX'];
       $points[(int)$row['epoch']] = $sum;
     }
 
     if (!$points) {
       throw new Exception('No data to show!');
     }
 
     // Limit amount of data passed to browser.
     $count = count($points);
     $limit = 2000;
     if ($count > $limit) {
       $i = 0;
       $every = ceil($count / $limit);
       foreach ($points as $epoch => $sum) {
         $i++;
         if ($i % $every && $i != $count) {
           unset($points[$epoch]);
         }
       }
     }
 
     $x = array_keys($points);
     $y = array_values($points);
 
     $id = celerity_generate_unique_node_id();
     $chart = phutil_tag(
       'div',
       array(
         'id' => $id,
         'style' => 'background: #ffffff; '.
                    'height: 480px; ',
       ),
       '');
 
     require_celerity_resource('d3');
 
     Javelin::initBehavior('line-chart', array(
       'hardpoint' => $id,
       'x' => array($x),
       'y' => array($y),
       'xformat' => 'epoch',
       'colors' => array('#0000ff'),
     ));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Count of %s', $spec->getName()))
       ->appendChild($chart);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Chart'));
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('Chart'),
-      ));
+    $title = pht('Chart');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($box);
+
   }
 
 }
diff --git a/src/applications/fact/controller/PhabricatorFactHomeController.php b/src/applications/fact/controller/PhabricatorFactHomeController.php
index 8e5c9511e..f4eeca038 100644
--- a/src/applications/fact/controller/PhabricatorFactHomeController.php
+++ b/src/applications/fact/controller/PhabricatorFactHomeController.php
@@ -1,125 +1,126 @@
 <?php
 
 final class PhabricatorFactHomeController extends PhabricatorFactController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     if ($request->isFormPost()) {
       $uri = new PhutilURI('/fact/chart/');
       $uri->setQueryParam('y1', $request->getStr('y1'));
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     $types = array(
       '+N:*',
       '+N:DREV',
       'updated',
     );
 
     $engines = PhabricatorFactEngine::loadAllEngines();
     $specs = PhabricatorFactSpec::newSpecsForFactTypes($engines, $types);
 
     $facts = id(new PhabricatorFactAggregate())->loadAllWhere(
       'factType IN (%Ls)',
       $types);
 
     $rows = array();
     foreach ($facts as $fact) {
       $spec = $specs[$fact->getFactType()];
 
       $name = $spec->getName();
       $value = $spec->formatValueForDisplay($viewer, $fact->getValueX());
 
       $rows[] = array($name, $value);
     }
 
     $table = new AphrontTableView($rows);
     $table->setHeaders(
       array(
         pht('Fact'),
         pht('Value'),
       ));
     $table->setColumnClasses(
       array(
         'wide',
         'n',
       ));
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Facts'));
     $panel->setTable($table);
 
     $chart_form = $this->buildChartForm();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Home'));
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $title = pht('Facts');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild(array(
         $chart_form,
         $panel,
-      ),
-      array(
-        'title' => pht('Facts'),
       ));
+
   }
 
   private function buildChartForm() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $table = new PhabricatorFactRaw();
     $conn_r = $table->establishConnection('r');
     $table_name = $table->getTableName();
 
     $facts = queryfx_all(
       $conn_r,
       'SELECT DISTINCT factType from %T',
       $table_name);
 
     $specs = PhabricatorFactSpec::newSpecsForFactTypes(
       PhabricatorFactEngine::loadAllEngines(),
       ipull($facts, 'factType'));
 
     $options = array();
     foreach ($specs as $spec) {
       if ($spec->getUnit() == PhabricatorFactSpec::UNIT_COUNT) {
         $options[$spec->getType()] = $spec->getName();
       }
     }
 
     if (!$options) {
       return id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
         ->setTitle(pht('No Chartable Facts'))
         ->appendChild(phutil_tag(
           'p',
           array(),
           pht('There are no facts that can be plotted yet.')));
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel(pht('Y-Axis'))
           ->setName('y1')
           ->setOptions($options))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Plot Chart')));
 
     $panel = new PHUIObjectBoxView();
     $panel->setForm($form);
     $panel->setHeaderText(pht('Plot Chart'));
 
     return $panel;
   }
 
 }
diff --git a/src/applications/feed/controller/PhabricatorFeedDetailController.php b/src/applications/feed/controller/PhabricatorFeedDetailController.php
index 4fac10d1a..f136d0120 100644
--- a/src/applications/feed/controller/PhabricatorFeedDetailController.php
+++ b/src/applications/feed/controller/PhabricatorFeedDetailController.php
@@ -1,44 +1,38 @@
 <?php
 
 final class PhabricatorFeedDetailController extends PhabricatorFeedController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $story = id(new PhabricatorFeedQuery())
       ->setViewer($viewer)
       ->withChronologicalKeys(array($id))
       ->executeOne();
     if (!$story) {
       return new Aphront404Response();
     }
 
     if ($request->getStr('text')) {
       $text = $story->renderText();
       return id(new AphrontPlainTextResponse())->setContent($text);
     }
 
     $feed = array($story);
     $builder = new PhabricatorFeedBuilder($feed);
     $builder->setUser($viewer);
     $feed_view = $builder->buildView();
 
     $title = pht('Story');
 
-    $feed_view = phutil_tag_div('phabricator-feed-frame', $feed_view);
-
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $feed_view,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($feed_view);
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php
index 1eaafdbad..6a4536d94 100644
--- a/src/applications/files/controller/PhabricatorFileComposeController.php
+++ b/src/applications/files/controller/PhabricatorFileComposeController.php
@@ -1,222 +1,219 @@
 <?php
 
 final class PhabricatorFileComposeController
   extends PhabricatorFileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $color_map = PhabricatorFilesComposeIconBuiltinFile::getAllColors();
     $icon_map = $this->getIconMap();
 
     if ($request->isFormPost()) {
       $project_phid = $request->getStr('projectPHID');
       if ($project_phid) {
         $project = id(new PhabricatorProjectQuery())
           ->setViewer($viewer)
           ->withPHIDs(array($project_phid))
           ->requireCapabilities(
             array(
               PhabricatorPolicyCapability::CAN_VIEW,
               PhabricatorPolicyCapability::CAN_EDIT,
             ))
           ->executeOne();
         if (!$project) {
           return new Aphront404Response();
         }
       }
 
       $icon = $request->getStr('icon');
       $color = $request->getStr('color');
 
       $composer = id(new PhabricatorFilesComposeIconBuiltinFile())
         ->setIcon($icon)
         ->setColor($color);
 
       $data = $composer->loadBuiltinFileData();
 
       $file = PhabricatorFile::buildFromFileDataOrHash(
         $data,
         array(
           'name' => $composer->getBuiltinDisplayName(),
           'profile' => true,
           'canCDN' => true,
         ));
 
       if ($project_phid) {
         $edit_uri = '/project/manage/'.$project->getID().'/';
 
         $xactions = array();
         $xactions[] = id(new PhabricatorProjectTransaction())
           ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE)
           ->setNewValue($file->getPHID());
 
         $editor = id(new PhabricatorProjectTransactionEditor())
           ->setActor($viewer)
           ->setContentSourceFromRequest($request)
           ->setContinueOnMissingFields(true)
           ->setContinueOnNoEffect(true);
 
         $editor->applyTransactions($project, $xactions);
 
         return id(new AphrontRedirectResponse())->setURI($edit_uri);
       } else {
         $content = array(
           'phid' => $file->getPHID(),
         );
 
         return id(new AphrontAjaxResponse())->setContent($content);
       }
     }
 
     $value_color = head_key($color_map);
     $value_icon = head_key($icon_map);
 
     require_celerity_resource('people-profile-css');
 
     $buttons = array();
     foreach ($color_map as $color => $info) {
       $quip = idx($info, 'quip');
 
       $buttons[] = javelin_tag(
         'button',
         array(
           'class' => 'grey profile-image-button',
           'sigil' => 'has-tooltip compose-select-color',
           'style' => 'margin: 0 8px 8px 0',
           'meta' => array(
             'color' => $color,
             'tip' => $quip,
           ),
         ),
         id(new PHUIIconView())
           ->addClass('compose-background-'.$color));
     }
 
 
     $icons = array();
     foreach ($icon_map as $icon => $spec) {
       $quip = idx($spec, 'quip');
 
       $icons[] = javelin_tag(
         'button',
         array(
           'class' => 'grey profile-image-button',
           'sigil' => 'has-tooltip compose-select-icon',
           'style' => 'margin: 0 8px 8px 0',
           'meta' => array(
             'icon' => $icon,
             'tip' => $quip,
           ),
         ),
         id(new PHUIIconView())
           ->setIcon($icon)
           ->addClass('compose-icon-bg'));
     }
 
     $dialog_id = celerity_generate_unique_node_id();
     $color_input_id = celerity_generate_unique_node_id();
     $icon_input_id = celerity_generate_unique_node_id();
     $preview_id = celerity_generate_unique_node_id();
 
     $preview = id(new PHUIIconView())
       ->setID($preview_id)
       ->addClass('compose-background-'.$value_color)
       ->setIcon($value_icon)
       ->addClass('compose-icon-bg');
 
     $color_input = javelin_tag(
       'input',
       array(
         'type' => 'hidden',
         'name' => 'color',
         'value' => $value_color,
         'id' => $color_input_id,
       ));
 
     $icon_input = javelin_tag(
       'input',
       array(
         'type' => 'hidden',
         'name' => 'icon',
         'value' => $value_icon,
         'id' => $icon_input_id,
       ));
 
     Javelin::initBehavior('phabricator-tooltips');
     Javelin::initBehavior(
       'icon-composer',
       array(
         'dialogID' => $dialog_id,
         'colorInputID' => $color_input_id,
         'iconInputID' => $icon_input_id,
         'previewID' => $preview_id,
         'defaultColor' => $value_color,
         'defaultIcon' => $value_icon,
       ));
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setFormID($dialog_id)
       ->setClass('compose-dialog')
       ->setTitle(pht('Compose Image'))
       ->appendChild(
         phutil_tag(
           'div',
           array(
             'class' => 'compose-header',
           ),
           pht('Choose Background Color')))
       ->appendChild($buttons)
       ->appendChild(
         phutil_tag(
           'div',
           array(
             'class' => 'compose-header',
           ),
           pht('Choose Icon')))
       ->appendChild($icons)
       ->appendChild(
         phutil_tag(
           'div',
           array(
             'class' => 'compose-header',
           ),
           pht('Preview')))
       ->appendChild($preview)
       ->appendChild($color_input)
       ->appendChild($icon_input)
       ->addCancelButton('/')
       ->addSubmitButton(pht('Save Image'));
-
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
   private function getIconMap() {
     $icon_map = PhabricatorFilesComposeIconBuiltinFile::getAllIcons();
 
     $first = array(
       'fa-briefcase',
       'fa-tags',
       'fa-folder',
       'fa-group',
       'fa-bug',
       'fa-trash-o',
       'fa-calendar',
       'fa-flag-checkered',
       'fa-envelope',
       'fa-truck',
       'fa-lock',
       'fa-umbrella',
       'fa-cloud',
       'fa-building',
       'fa-credit-card',
       'fa-flask',
     );
 
     $icon_map = array_select_keys($icon_map, $first) + $icon_map;
 
     return $icon_map;
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php
index 848560f09..dfae99d20 100644
--- a/src/applications/files/controller/PhabricatorFileDataController.php
+++ b/src/applications/files/controller/PhabricatorFileDataController.php
@@ -1,231 +1,179 @@
 <?php
 
 final class PhabricatorFileDataController extends PhabricatorFileController {
 
   private $phid;
   private $key;
-  private $token;
   private $file;
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $this->phid = $request->getURIData('phid');
     $this->key = $request->getURIData('key');
-    $this->token = $request->getURIData('token');
 
     $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
     $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
     $alt_uri = new PhutilURI($alt);
     $alt_domain = $alt_uri->getDomain();
     $req_domain = $request->getHost();
     $main_domain = id(new PhutilURI($base_uri))->getDomain();
 
-    $cache_response = true;
 
-    if (empty($alt) || $main_domain == $alt_domain) {
-      // Alternate files domain isn't configured or it's set
-      // to the same as the default domain
-
-      $response = $this->loadFile($viewer);
-      if ($response) {
-        return $response;
-      }
-      $file = $this->getFile();
-
-      // when the file is not CDNable, don't allow cache
-      $cache_response = $file->getCanCDN();
+    if (!strlen($alt) || $main_domain == $alt_domain) {
+      // No alternate domain.
+      $should_redirect = false;
+      $use_viewer = $viewer;
+      $is_alternate_domain = false;
     } else if ($req_domain != $alt_domain) {
-      // Alternate domain is configured but this request isn't using it
+      // Alternate domain, but this request is on the main domain.
+      $should_redirect = true;
+      $use_viewer = $viewer;
+      $is_alternate_domain = false;
+    } else {
+      // Alternate domain, and on the alternate domain.
+      $should_redirect = false;
+      $use_viewer = PhabricatorUser::getOmnipotentUser();
+      $is_alternate_domain = true;
+    }
 
-      $response = $this->loadFile($viewer);
-      if ($response) {
-        return $response;
-      }
-      $file = $this->getFile();
+    $response = $this->loadFile($use_viewer);
+    if ($response) {
+      return $response;
+    }
 
-      // if the user can see the file, generate a token;
-      // redirect to the alt domain with the token;
-      $token_uri = $file->getCDNURIWithToken();
-      $token_uri = new PhutilURI($token_uri);
-      $token_uri = $this->addURIParameters($token_uri);
+    $file = $this->getFile();
 
+    if ($should_redirect) {
       return id(new AphrontRedirectResponse())
         ->setIsExternal(true)
-        ->setURI($token_uri);
-
-    } else {
-      // We are using the alternate domain. We don't have authentication
-      // on this domain, so we bypass policy checks when loading the file.
-
-      $bypass_policies = PhabricatorUser::getOmnipotentUser();
-      $response = $this->loadFile($bypass_policies);
-      if ($response) {
-        return $response;
-      }
-      $file = $this->getFile();
-
-      $acquire_token_uri = id(new PhutilURI($file->getViewURI()))
-        ->setDomain($main_domain);
-      $acquire_token_uri = $this->addURIParameters($acquire_token_uri);
-
-      if ($this->token) {
-        // validate the token, if it is valid, continue
-        $validated_token = $file->validateOneTimeToken($this->token);
-
-        if (!$validated_token) {
-          $dialog = $this->newDialog()
-            ->setShortTitle(pht('Expired File'))
-            ->setTitle(pht('File Link Has Expired'))
-            ->appendParagraph(
-              pht(
-                'The link you followed to view this file is invalid or '.
-                'expired.'))
-            ->appendParagraph(
-              pht(
-                'Continue to generate a new link to the file. You may be '.
-                'required to log in.'))
-            ->addCancelButton(
-              $acquire_token_uri,
-              pht('Continue'));
-
-          // Build an explicit response so we can respond with HTTP/403 instead
-          // of HTTP/200.
-          $response = id(new AphrontDialogResponse())
-            ->setDialog($dialog)
-            ->setHTTPResponseCode(403);
-
-          return $response;
-        }
-        // return the file data without cache headers
-        $cache_response = false;
-      } else if (!$file->getCanCDN()) {
-        // file cannot be served via cdn, and no token given
-        // redirect to the main domain to aquire a token
-
-        // This is marked as an "external" URI because it is fully qualified.
-        return id(new AphrontRedirectResponse())
-          ->setIsExternal(true)
-          ->setURI($acquire_token_uri);
-      }
+        ->setURI($file->getCDNURI());
     }
 
     $response = new AphrontFileResponse();
-    if ($cache_response) {
-      $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
-    }
+    $response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
+    $response->setCanCDN($file->getCanCDN());
 
     $begin = null;
     $end = null;
 
     // NOTE: It's important to accept "Range" requests when playing audio.
     // If we don't, Safari has difficulty figuring out how long sounds are
     // and glitches when trying to loop them. In particular, Safari sends
     // an initial request for bytes 0-1 of the audio file, and things go south
     // if we can't respond with a 206 Partial Content.
     $range = $request->getHTTPHeader('range');
     if ($range) {
       $matches = null;
       if (preg_match('/^bytes=(\d+)-(\d+)$/', $range, $matches)) {
         // Note that the "Range" header specifies bytes differently than
         // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1).
         $begin = (int)$matches[1];
         $end = (int)$matches[2] + 1;
 
         $response->setHTTPResponseCode(206);
         $response->setRange($begin, ($end - 1));
       }
-    } else if (isset($validated_token)) {
-      // We set this on the response, and the response deletes it after the
-      // transfer completes. This allows transfers to be resumed, in theory.
-      $response->setTemporaryFileToken($validated_token);
     }
 
     $is_viewable = $file->isViewableInBrowser();
     $force_download = $request->getExists('download');
 
+    $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
+    $is_lfs = ($request_type == 'git-lfs');
+
     if ($is_viewable && !$force_download) {
       $response->setMimeType($file->getViewableMimeType());
     } else {
-      if (!$request->isHTTPPost() && !$alt_domain) {
+      if (!$request->isHTTPPost() && !$is_alternate_domain && !$is_lfs) {
         // NOTE: Require POST to download files from the primary domain. We'd
         // rather go full-bore and do a real CSRF check, but can't currently
         // authenticate users on the file domain. This should blunt any
         // attacks based on iframes, script tags, applet tags, etc., at least.
         // Send the user to the "info" page if they're using some other method.
 
         // This is marked as "external" because it is fully qualified.
         return id(new AphrontRedirectResponse())
           ->setIsExternal(true)
           ->setURI(PhabricatorEnv::getProductionURI($file->getBestURI()));
       }
       $response->setMimeType($file->getMimeType());
       $response->setDownload($file->getName());
     }
 
     $iterator = $file->getFileDataIterator($begin, $end);
 
     $response->setContentLength($file->getByteSize());
     $response->setContentIterator($iterator);
 
     return $response;
   }
 
-  /**
-   * Add passthrough parameters to the URI so they aren't lost when we
-   * redirect to acquire tokens.
-   */
-  private function addURIParameters(PhutilURI $uri) {
-    $request = $this->getRequest();
-
-    if ($request->getBool('download')) {
-      $uri->setQueryParam('download', 1);
-    }
-
-    return $uri;
-  }
-
   private function loadFile(PhabricatorUser $viewer) {
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($this->phid))
       ->executeOne();
 
     if (!$file) {
       return new Aphront404Response();
     }
 
+    // We may be on the CDN domain, so we need to use a fully-qualified URI
+    // here to make sure we end up back on the main domain.
+    $info_uri = PhabricatorEnv::getURI($file->getInfoURI());
+
+
     if (!$file->validateSecretKey($this->key)) {
-      return new Aphront403Response();
+      $dialog = $this->newDialog()
+        ->setTitle(pht('Invalid Authorization'))
+        ->appendParagraph(
+          pht(
+            'The link you followed to access this file is no longer '.
+            'valid. The visibility of the file may have changed after '.
+            'the link was generated.'))
+        ->appendParagraph(
+          pht(
+            'You can continue to the file detail page to get more '.
+            'information and attempt to access the file.'))
+        ->addCancelButton($info_uri, pht('Continue'));
+
+      return id(new AphrontDialogResponse())
+        ->setDialog($dialog)
+        ->setHTTPResponseCode(404);
     }
 
     if ($file->getIsPartial()) {
-      // We may be on the CDN domain, so we need to use a fully-qualified URI
-      // here to make sure we end up back on the main domain.
-      $info_uri = PhabricatorEnv::getURI($file->getInfoURI());
-
-      return $this->newDialog()
+      $dialog = $this->newDialog()
         ->setTitle(pht('Partial Upload'))
         ->appendParagraph(
           pht(
             'This file has only been partially uploaded. It must be '.
             'uploaded completely before you can download it.'))
-        ->addCancelButton($info_uri);
+        ->appendParagraph(
+          pht(
+            'You can continue to the file detail page to monitor the '.
+            'upload progress of the file.'))
+        ->addCancelButton($info_uri, pht('Continue'));
+
+      return id(new AphrontDialogResponse())
+        ->setDialog($dialog)
+        ->setHTTPResponseCode(404);
     }
 
     $this->file = $file;
 
     return null;
   }
 
   private function getFile() {
     if (!$this->file) {
       throw new PhutilInvalidStateException('loadFile');
     }
     return $this->file;
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php
index a07fe2e91..acca7c9b1 100644
--- a/src/applications/files/controller/PhabricatorFileDeleteController.php
+++ b/src/applications/files/controller/PhabricatorFileDeleteController.php
@@ -1,45 +1,42 @@
 <?php
 
 final class PhabricatorFileDeleteController extends PhabricatorFileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$file) {
       return new Aphront404Response();
     }
 
     if (($viewer->getPHID() != $file->getAuthorPHID()) &&
         (!$viewer->getIsAdmin())) {
       return new Aphront403Response();
     }
 
     if ($request->isFormPost()) {
       $file->delete();
       return id(new AphrontRedirectResponse())->setURI('/file/');
     }
 
-    $dialog = new AphrontDialogView();
-    $dialog->setUser($viewer);
-    $dialog->setTitle(pht('Really delete file?'));
-    $dialog->appendChild(hsprintf(
+    return $this->newDialog()
+      ->setTitle(pht('Really delete file?'))
+      ->appendChild(hsprintf(
       '<p>%s</p>',
       pht(
-        "Permanently delete '%s'? This action can not be undone.",
-        $file->getName())));
-    $dialog->addSubmitButton(pht('Delete'));
-    $dialog->addCancelButton($file->getInfoURI());
-
-    return id(new AphrontDialogResponse())->setDialog($dialog);
+        'Permanently delete "%s"? This action can not be undone.',
+        $file->getName())))
+        ->addSubmitButton(pht('Delete'))
+        ->addCancelButton($file->getInfoURI());
   }
 }
diff --git a/src/applications/files/controller/PhabricatorFileEditController.php b/src/applications/files/controller/PhabricatorFileEditController.php
index 9c416b588..e1b34afd7 100644
--- a/src/applications/files/controller/PhabricatorFileEditController.php
+++ b/src/applications/files/controller/PhabricatorFileEditController.php
@@ -1,106 +1,114 @@
 <?php
 
 final class PhabricatorFileEditController extends PhabricatorFileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$file) {
       return new Aphront404Response();
     }
 
-    $title = pht('Edit %s', $file->getName());
+    $title = pht('Edit File: %s', $file->getName());
     $file_name = $file->getName();
+    $header_icon = 'fa-pencil';
     $view_uri = '/'.$file->getMonogram();
     $error_name = true;
     $validation_exception = null;
 
     if ($request->isFormPost()) {
       $can_view = $request->getStr('canView');
       $file_name = $request->getStr('name');
       $errors = array();
 
       $type_name = PhabricatorFileTransaction::TYPE_NAME;
 
       $xactions = array();
 
       $xactions[] = id(new PhabricatorFileTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($can_view);
 
       $xactions[] = id(new PhabricatorFileTransaction())
         ->setTransactionType(PhabricatorFileTransaction::TYPE_NAME)
         ->setNewValue($file_name);
 
       $editor = id(new PhabricatorFileEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($file, $xactions);
         return id(new AphrontRedirectResponse())->setURI($view_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
         $error_name = $ex->getShortMessage($type_name);
 
         $file->setViewPolicy($can_view);
       }
     }
 
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($file)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
        ->appendChild(
         id(new AphrontFormTextControl())
         ->setName('name')
         ->setValue($file_name)
         ->setLabel(pht('Name'))
         ->setError($error_name))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicyObject($file)
           ->setPolicies($policies)
           ->setName('canView'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($view_uri)
           ->setValue(pht('Save Changes')));
 
     $crumbs = $this->buildApplicationCrumbs()
       ->addTextCrumb($file->getMonogram(), $view_uri)
-      ->addTextCrumb(pht('Edit'));
+      ->addTextCrumb(pht('Edit'))
+      ->setBorder(true);
 
-    $object_box = id(new PHUIObjectBoxView())
+    $box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $object_box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php
index 2731b7f4b..66050b697 100644
--- a/src/applications/files/controller/PhabricatorFileInfoController.php
+++ b/src/applications/files/controller/PhabricatorFileInfoController.php
@@ -1,373 +1,377 @@
 <?php
 
 final class PhabricatorFileInfoController extends PhabricatorFileController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $phid = $request->getURIData('phid');
 
     if ($phid) {
       $file = id(new PhabricatorFileQuery())
         ->setViewer($viewer)
         ->withPHIDs(array($phid))
         ->executeOne();
 
       if (!$file) {
         return new Aphront404Response();
       }
       return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
     }
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$file) {
       return new Aphront404Response();
     }
 
     $phid = $file->getPHID();
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setPolicyObject($file)
-      ->setHeader($file->getName());
+      ->setHeader($file->getName())
+      ->setHeaderIcon('fa-file-o');
 
     $ttl = $file->getTTL();
     if ($ttl !== null) {
       $ttl_tag = id(new PHUITagView())
         ->setType(PHUITagView::TYPE_STATE)
         ->setBackgroundColor(PHUITagView::COLOR_YELLOW)
         ->setName(pht('Temporary'));
       $header->addTag($ttl_tag);
     }
 
     $partial = $file->getIsPartial();
     if ($partial) {
       $partial_tag = id(new PHUITagView())
         ->setType(PHUITagView::TYPE_STATE)
         ->setBackgroundColor(PHUITagView::COLOR_ORANGE)
         ->setName(pht('Partial Upload'));
       $header->addTag($partial_tag);
     }
 
-    $actions = $this->buildActionView($file);
+    $curtain = $this->buildCurtainView($file);
     $timeline = $this->buildTransactionView($file);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       'F'.$file->getID(),
       $this->getApplicationURI("/info/{$phid}/"));
+    $crumbs->setBorder(true);
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeader($header);
+      ->setHeaderText(pht('File'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
-    $this->buildPropertyViews($object_box, $file, $actions);
+    $this->buildPropertyViews($object_box, $file);
+    $title = $file->getName();
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
         $object_box,
         $timeline,
-      ),
-      array(
-        'title' => $file->getName(),
-        'pageObjects' => array($file->getPHID()),
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->setPageObjectPHIDs(array($file->getPHID()))
+      ->appendChild($view);
+
   }
 
   private function buildTransactionView(PhabricatorFile $file) {
     $viewer = $this->getViewer();
 
     $timeline = $this->buildTransactionTimeline(
       $file,
       new PhabricatorFileTransactionQuery());
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     $add_comment_header = $is_serious
       ? pht('Add Comment')
       : pht('Question File Integrity');
 
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $file->getPHID());
 
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($file->getPHID())
       ->setDraft($draft)
       ->setHeaderText($add_comment_header)
       ->setAction($this->getApplicationURI('/comment/'.$file->getID().'/'))
       ->setSubmitButtonName(pht('Add Comment'));
 
     return array(
       $timeline,
       $add_comment_form,
     );
   }
 
-  private function buildActionView(PhabricatorFile $file) {
+  private function buildCurtainView(PhabricatorFile $file) {
     $viewer = $this->getViewer();
 
     $id = $file->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $file,
       PhabricatorPolicyCapability::CAN_EDIT);
 
-    $view = id(new PhabricatorActionListView())
-      ->setUser($viewer)
-      ->setObject($file);
+    $curtain = $this->newCurtainView($file);
 
     $can_download = !$file->getIsPartial();
 
     if ($file->isViewableInBrowser()) {
-      $view->addAction(
+      $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('View File'))
           ->setIcon('fa-file-o')
           ->setHref($file->getViewURI())
           ->setDisabled(!$can_download)
           ->setWorkflow(!$can_download));
     } else {
-      $view->addAction(
+      $curtain->addAction(
         id(new PhabricatorActionView())
           ->setUser($viewer)
           ->setRenderAsForm($can_download)
           ->setDownload($can_download)
           ->setName(pht('Download File'))
           ->setIcon('fa-download')
           ->setHref($file->getViewURI())
           ->setDisabled(!$can_download)
           ->setWorkflow(!$can_download));
     }
 
-    $view->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit File'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("/edit/{$id}/"))
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit));
 
-    $view->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Delete File'))
         ->setIcon('fa-times')
         ->setHref($this->getApplicationURI("/delete/{$id}/"))
         ->setWorkflow(true)
         ->setDisabled(!$can_edit));
 
-    $view->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View Transforms'))
         ->setIcon('fa-crop')
         ->setHref($this->getApplicationURI("/transforms/{$id}/")));
 
-    return $view;
+    return $curtain;
   }
 
   private function buildPropertyViews(
     PHUIObjectBoxView $box,
-    PhabricatorFile $file,
-    PhabricatorActionListView $actions) {
+    PhabricatorFile $file) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $properties = id(new PHUIPropertyListView());
-    $properties->setActionList($actions);
     $box->addPropertyList($properties, pht('Details'));
 
     if ($file->getAuthorPHID()) {
       $properties->addProperty(
         pht('Author'),
         $viewer->renderHandle($file->getAuthorPHID()));
     }
 
     $properties->addProperty(
       pht('Created'),
       phabricator_datetime($file->getDateCreated(), $viewer));
 
 
     $finfo = id(new PHUIPropertyListView());
     $box->addPropertyList($finfo, pht('File Info'));
 
     $finfo->addProperty(
       pht('Size'),
       phutil_format_bytes($file->getByteSize()));
 
     $finfo->addProperty(
       pht('Mime Type'),
       $file->getMimeType());
 
     $width = $file->getImageWidth();
     if ($width) {
       $finfo->addProperty(
         pht('Width'),
         pht('%s px', new PhutilNumber($width)));
     }
 
     $height = $file->getImageHeight();
     if ($height) {
       $finfo->addProperty(
         pht('Height'),
         pht('%s px', new PhutilNumber($height)));
     }
 
     $is_image = $file->isViewableImage();
     if ($is_image) {
       $image_string = pht('Yes');
       $cache_string = $file->getCanCDN() ? pht('Yes') : pht('No');
     } else {
       $image_string = pht('No');
       $cache_string = pht('Not Applicable');
     }
 
     $finfo->addProperty(pht('Viewable Image'), $image_string);
     $finfo->addProperty(pht('Cacheable'), $cache_string);
 
     $builtin = $file->getBuiltinName();
     if ($builtin === null) {
       $builtin_string = pht('No');
     } else {
       $builtin_string = $builtin;
     }
 
     $finfo->addProperty(pht('Builtin'), $builtin_string);
 
     $is_profile = $file->getIsProfileImage()
       ? pht('Yes')
       : pht('No');
 
     $finfo->addProperty(pht('Profile'), $is_profile);
 
     $storage_properties = new PHUIPropertyListView();
     $box->addPropertyList($storage_properties, pht('Storage'));
 
     $storage_properties->addProperty(
       pht('Engine'),
       $file->getStorageEngine());
 
     $storage_properties->addProperty(
       pht('Format'),
       $file->getStorageFormat());
 
     $storage_properties->addProperty(
       pht('Handle'),
       $file->getStorageHandle());
 
 
     $phids = $file->getObjectPHIDs();
     if ($phids) {
       $attached = new PHUIPropertyListView();
       $box->addPropertyList($attached, pht('Attached'));
 
       $attached->addProperty(
         pht('Attached To'),
         $viewer->renderHandleList($phids));
     }
 
     if ($file->isViewableImage()) {
       $image = phutil_tag(
         'img',
         array(
           'src' => $file->getViewURI(),
           'class' => 'phui-property-list-image',
         ));
 
       $linked_image = phutil_tag(
         'a',
         array(
           'href' => $file->getViewURI(),
         ),
         $image);
 
       $media = id(new PHUIPropertyListView())
         ->addImageContent($linked_image);
 
       $box->addPropertyList($media);
     } else if ($file->isAudio()) {
       $audio = phutil_tag(
         'audio',
         array(
           'controls' => 'controls',
           'class' => 'phui-property-list-audio',
         ),
         phutil_tag(
           'source',
           array(
             'src' => $file->getViewURI(),
             'type' => $file->getMimeType(),
           )));
       $media = id(new PHUIPropertyListView())
         ->addImageContent($audio);
 
       $box->addPropertyList($media);
     }
 
     $engine = null;
     try {
       $engine = $file->instantiateStorageEngine();
     } catch (Exception $ex) {
       // Don't bother raising this anywhere for now.
     }
 
     if ($engine) {
       if ($engine->isChunkEngine()) {
         $chunkinfo = new PHUIPropertyListView();
         $box->addPropertyList($chunkinfo, pht('Chunks'));
 
         $chunks = id(new PhabricatorFileChunkQuery())
           ->setViewer($viewer)
           ->withChunkHandles(array($file->getStorageHandle()))
           ->execute();
         $chunks = msort($chunks, 'getByteStart');
 
         $rows = array();
         $completed = array();
         foreach ($chunks as $chunk) {
           $is_complete = $chunk->getDataFilePHID();
 
           $rows[] = array(
             $chunk->getByteStart(),
             $chunk->getByteEnd(),
             ($is_complete ? pht('Yes') : pht('No')),
           );
 
           if ($is_complete) {
             $completed[] = $chunk;
           }
         }
 
         $table = id(new AphrontTableView($rows))
           ->setHeaders(
             array(
               pht('Offset'),
               pht('End'),
               pht('Complete'),
             ))
           ->setColumnClasses(
             array(
               '',
               '',
               'wide',
             ));
 
         $chunkinfo->addProperty(
           pht('Total Chunks'),
           count($chunks));
 
         $chunkinfo->addProperty(
           pht('Completed Chunks'),
           count($completed));
 
         $chunkinfo->addRawContent($table);
       }
     }
 
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php
index 767254ad3..026f60320 100644
--- a/src/applications/files/controller/PhabricatorFileTransformListController.php
+++ b/src/applications/files/controller/PhabricatorFileTransformListController.php
@@ -1,138 +1,147 @@
 <?php
 
 final class PhabricatorFileTransformListController
   extends PhabricatorFileController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$file) {
       return new Aphront404Response();
     }
 
     $monogram = $file->getMonogram();
 
     $xdst = id(new PhabricatorTransformedFile())->loadAllWhere(
       'transformedPHID = %s',
       $file->getPHID());
 
     $dst_rows = array();
     foreach ($xdst as $source) {
       $dst_rows[] = array(
         $source->getTransform(),
         $viewer->renderHandle($source->getOriginalPHID()),
       );
     }
     $dst_table = id(new AphrontTableView($dst_rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Source'),
         ))
       ->setColumnClasses(
         array(
           '',
           'wide',
         ))
       ->setNoDataString(
         pht(
           'This file was not created by transforming another file.'));
 
     $xsrc = id(new PhabricatorTransformedFile())->loadAllWhere(
       'originalPHID = %s',
       $file->getPHID());
     $xsrc = mpull($xsrc, 'getTransformedPHID', 'getTransform');
 
     $src_rows = array();
     $xforms = PhabricatorFileTransform::getAllTransforms();
     foreach ($xforms as $xform) {
       $dst_phid = idx($xsrc, $xform->getTransformKey());
 
       if ($xform->canApplyTransform($file)) {
         $can_apply = pht('Yes');
 
         $view_href = $file->getURIForTransform($xform);
         $view_href = new PhutilURI($view_href);
         $view_href->setQueryParam('regenerate', 'true');
 
         $view_text = pht('Regenerate');
 
         $view_link = phutil_tag(
           'a',
           array(
             'class' => 'small grey button',
             'href' => $view_href,
           ),
           $view_text);
       } else {
         $can_apply = phutil_tag('em', array(), pht('No'));
         $view_link = phutil_tag('em', array(), pht('None'));
       }
 
       if ($dst_phid) {
         $dst_link = $viewer->renderHandle($dst_phid);
       } else {
         $dst_link = phutil_tag('em', array(), pht('None'));
       }
 
       $src_rows[] = array(
         $xform->getTransformName(),
         $xform->getTransformKey(),
         $can_apply,
         $dst_link,
         $view_link,
       );
     }
 
     $src_table = id(new AphrontTableView($src_rows))
       ->setHeaders(
         array(
           pht('Name'),
           pht('Key'),
           pht('Supported'),
           pht('Transform'),
           pht('View'),
         ))
       ->setColumnClasses(
         array(
           'wide',
           '',
           '',
           '',
           'action',
         ));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($monogram, '/'.$monogram);
     $crumbs->addTextCrumb(pht('Transforms'));
+    $crumbs->setBorder(true);
 
     $dst_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('File Sources'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($dst_table);
 
     $src_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Available Transforms'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($src_table);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $title = pht('%s Transforms', $file->getName());
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-arrows-alt');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $dst_box,
         $src_box,
-      ),
-      array(
-        'title' => array(
-          pht('%s %s', $monogram, $file->getName()),
-          pht('Tranforms'),
-        ),
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 }
diff --git a/src/applications/files/controller/PhabricatorFileUploadController.php b/src/applications/files/controller/PhabricatorFileUploadController.php
index a02b298f2..3be0ca396 100644
--- a/src/applications/files/controller/PhabricatorFileUploadController.php
+++ b/src/applications/files/controller/PhabricatorFileUploadController.php
@@ -1,107 +1,115 @@
 <?php
 
 final class PhabricatorFileUploadController extends PhabricatorFileController {
 
   public function isGlobalDragAndDropUploadEnabled() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $file = PhabricatorFile::initializeNewFile();
 
     $e_file = true;
     $errors = array();
     if ($request->isFormPost()) {
       $view_policy = $request->getStr('viewPolicy');
 
       if (!$request->getFileExists('file')) {
         $e_file = pht('Required');
         $errors[] = pht('You must select a file to upload.');
       } else {
         $file = PhabricatorFile::newFromPHPUpload(
           idx($_FILES, 'file'),
           array(
             'name'        => $request->getStr('name'),
             'authorPHID'  => $viewer->getPHID(),
             'viewPolicy'  => $view_policy,
             'isExplicitUpload' => true,
           ));
       }
 
       if (!$errors) {
         return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
       }
 
       $file->setViewPolicy($view_policy);
     }
 
     $support_id = celerity_generate_unique_node_id();
     $instructions = id(new AphrontFormMarkupControl())
       ->setControlID($support_id)
       ->setControlStyle('display: none')
       ->setValue(hsprintf(
         '<br /><br /><strong>%s</strong> %s<br /><br />',
         pht('Drag and Drop:'),
         pht(
           'You can also upload files by dragging and dropping them from your '.
           'desktop onto this page or the Phabricator home page.')));
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($file)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setEncType('multipart/form-data')
       ->appendChild(
         id(new AphrontFormFileControl())
           ->setLabel(pht('File'))
           ->setName('file')
           ->setError($e_file))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Name'))
           ->setName('name')
           ->setValue($request->getStr('name')))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicyObject($file)
           ->setPolicies($policies)
           ->setName('viewPolicy'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Upload'))
           ->addCancelButton('/file/'))
       ->appendChild($instructions);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Upload'), $request->getRequestURI());
+    $crumbs->setBorder(true);
 
     $title = pht('Upload File');
 
     $global_upload = id(new PhabricatorGlobalUploadTargetView())
       ->setUser($viewer)
       ->setShowIfSupportedID($support_id);
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('File'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-upload');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $form_box,
         $global_upload,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php
index dd22caa74..cf13f4d69 100644
--- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php
+++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php
@@ -1,19 +1,17 @@
 <?php
 
 final class PhabricatorFileUploadDialogController
   extends PhabricatorFileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setTitle(pht('Upload File'))
       ->appendChild(pht(
         'To add files, drag and drop them into the comment text area.'))
       ->addCancelButton('/', pht('Close'));
 
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
index 7f57db02b..ecb0e4fd0 100644
--- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
+++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
@@ -1,232 +1,244 @@
 <?php
 
 final class PhabricatorEmbedFileRemarkupRule
   extends PhabricatorObjectRemarkupRule {
 
   const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids';
 
   protected function getObjectNamePrefix() {
     return 'F';
   }
 
   protected function loadObjects(array $ids) {
     $engine = $this->getEngine();
 
     $viewer = $engine->getConfig('viewer');
     $objects = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withIDs($ids)
+      ->needTransforms(
+        array(
+          PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW,
+        ))
       ->execute();
 
     $phids_key = self::KEY_EMBED_FILE_PHIDS;
     $phids = $engine->getTextMetadata($phids_key, array());
     foreach (mpull($objects, 'getPHID') as $phid) {
       $phids[] = $phid;
     }
     $engine->setTextMetadata($phids_key, $phids);
 
     return $objects;
   }
 
   protected function renderObjectEmbed(
     $object,
     PhabricatorObjectHandle $handle,
     $options) {
 
     $options = $this->getFileOptions($options) + array(
       'name' => $object->getName(),
     );
 
     $is_viewable_image = $object->isViewableImage();
     $is_audio = $object->isAudio();
     $force_link = ($options['layout'] == 'link');
 
     $options['viewable'] = ($is_viewable_image || $is_audio);
 
     if ($is_viewable_image && !$force_link) {
       return $this->renderImageFile($object, $handle, $options);
     } else if ($is_audio && !$force_link) {
       return $this->renderAudioFile($object, $handle, $options);
     } else {
       return $this->renderFileLink($object, $handle, $options);
     }
   }
 
   private function getFileOptions($option_string) {
     $options = array(
       'size'    => null,
       'layout'  => 'left',
       'float'   => false,
       'width'   => null,
       'height'  => null,
       'alt' => null,
     );
 
     if ($option_string) {
       $option_string = trim($option_string, ', ');
       $parser = new PhutilSimpleOptions();
       $options = $parser->parse($option_string) + $options;
     }
 
     return $options;
   }
 
   private function renderImageFile(
     PhabricatorFile $file,
     PhabricatorObjectHandle $handle,
     array $options) {
 
     require_celerity_resource('lightbox-attachment-css');
 
     $attrs = array();
     $image_class = 'phabricator-remarkup-embed-image';
 
     $use_size = true;
     if (!$options['size']) {
       $width = $this->parseDimension($options['width']);
       $height = $this->parseDimension($options['height']);
       if ($width || $height) {
         $use_size = false;
         $attrs += array(
           'src' => $file->getBestURI(),
           'width' => $width,
           'height' => $height,
         );
       }
     }
 
     if ($use_size) {
       switch ((string)$options['size']) {
         case 'full':
           $attrs += array(
             'src' => $file->getBestURI(),
             'height' => $file->getImageHeight(),
             'width' => $file->getImageWidth(),
           );
           $image_class = 'phabricator-remarkup-embed-image-full';
           break;
         case 'thumb':
         default:
           $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;
           $xform = PhabricatorFileTransform::getTransformByKey($preview_key);
-          $attrs['src'] = $file->getURIForTransform($xform);
+
+          $existing_xform = $file->getTransform($preview_key);
+          if ($existing_xform) {
+            $xform_uri = $existing_xform->getCDNURI();
+          } else {
+            $xform_uri = $file->getURIForTransform($xform);
+          }
+
+          $attrs['src'] = $xform_uri;
 
           $dimensions = $xform->getTransformedDimensions($file);
           if ($dimensions) {
             list($x, $y) = $dimensions;
             $attrs['width'] = $x;
             $attrs['height'] = $y;
           }
           break;
       }
     }
 
     if (isset($options['alt'])) {
       $attrs['alt'] = $options['alt'];
     }
 
     $img = phutil_tag('img', $attrs);
 
     $embed = javelin_tag(
       'a',
       array(
         'href'        => $file->getBestURI(),
         'class'       => $image_class,
         'sigil'       => 'lightboxable',
         'meta'        => array(
           'phid'     => $file->getPHID(),
           'uri'      => $file->getBestURI(),
           'dUri'     => $file->getDownloadURI(),
           'viewable' => true,
         ),
       ),
       $img);
 
     switch ($options['layout']) {
       case 'right':
       case 'center':
       case 'inline':
       case 'left':
         $layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
         break;
       default:
         $layout_class = 'phabricator-remarkup-embed-layout-left';
         break;
     }
 
     if ($options['float']) {
       switch ($options['layout']) {
         case 'center':
         case 'inline':
           break;
         case 'right':
           $layout_class .= ' phabricator-remarkup-embed-float-right';
           break;
         case 'left':
         default:
           $layout_class .= ' phabricator-remarkup-embed-float-left';
           break;
       }
     }
 
     return phutil_tag(
       ($options['layout'] == 'inline' ? 'span' : 'div'),
       array(
         'class' => $layout_class,
       ),
       $embed);
   }
 
   private function renderAudioFile(
     PhabricatorFile $file,
     PhabricatorObjectHandle $handle,
     array $options) {
 
     if (idx($options, 'autoplay')) {
       $preload = 'auto';
       $autoplay = 'autoplay';
     } else {
       $preload = 'none';
       $autoplay = null;
     }
 
     return $this->newTag(
       'audio',
       array(
         'controls' => 'controls',
         'preload' => $preload,
         'autoplay' => $autoplay,
         'loop' => idx($options, 'loop') ? 'loop' : null,
       ),
       $this->newTag(
         'source',
         array(
           'src' => $file->getBestURI(),
           'type' => $file->getMimeType(),
         )));
   }
 
   private function renderFileLink(
     PhabricatorFile $file,
     PhabricatorObjectHandle $handle,
     array $options) {
 
     return id(new PhabricatorFileLinkView())
       ->setFilePHID($file->getPHID())
       ->setFileName($this->assertFlatText($options['name']))
       ->setFileDownloadURI($file->getDownloadURI())
       ->setFileViewURI($file->getBestURI())
       ->setFileViewable((bool)$options['viewable']);
   }
 
   private function parseDimension($string) {
     $string = trim($string);
 
     if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) {
       return $string;
     }
 
     return null;
   }
 
 }
diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php
index ba7215606..03446d915 100644
--- a/src/applications/files/query/PhabricatorFileQuery.php
+++ b/src/applications/files/query/PhabricatorFileQuery.php
@@ -1,344 +1,388 @@
 <?php
 
 final class PhabricatorFileQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $phids;
   private $authorPHIDs;
   private $explicitUploads;
   private $transforms;
   private $dateCreatedAfter;
   private $dateCreatedBefore;
   private $contentHashes;
   private $minLength;
   private $maxLength;
   private $names;
   private $isPartial;
+  private $needTransforms;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withAuthorPHIDs(array $phids) {
     $this->authorPHIDs = $phids;
     return $this;
   }
 
   public function withDateCreatedBefore($date_created_before) {
     $this->dateCreatedBefore = $date_created_before;
     return $this;
   }
 
   public function withDateCreatedAfter($date_created_after) {
     $this->dateCreatedAfter = $date_created_after;
     return $this;
   }
 
   public function withContentHashes(array $content_hashes) {
     $this->contentHashes = $content_hashes;
     return $this;
   }
 
   /**
    * Select files which are transformations of some other file. For example,
    * you can use this query to find previously generated thumbnails of an image
    * file.
    *
    * As a parameter, provide a list of transformation specifications. Each
    * specification is a dictionary with the keys `originalPHID` and `transform`.
    * The `originalPHID` is the PHID of the original file (the file which was
    * transformed) and the `transform` is the name of the transform to query
    * for. If you pass `true` as the `transform`, all transformations of the
    * file will be selected.
    *
    * For example:
    *
    *   array(
    *     array(
    *       'originalPHID' => 'PHID-FILE-aaaa',
    *       'transform'    => 'sepia',
    *     ),
    *     array(
    *       'originalPHID' => 'PHID-FILE-bbbb',
    *       'transform'    => true,
    *     ),
    *   )
    *
    * This selects the `"sepia"` transformation of the file with PHID
    * `PHID-FILE-aaaa` and all transformations of the file with PHID
    * `PHID-FILE-bbbb`.
    *
    * @param list<dict>  List of transform specifications, described above.
    * @return this
    */
   public function withTransforms(array $specs) {
     foreach ($specs as $spec) {
       if (!is_array($spec) ||
           empty($spec['originalPHID']) ||
           empty($spec['transform'])) {
         throw new Exception(
           pht(
             "Transform specification must be a dictionary with keys ".
             "'%s' and '%s'!",
             'originalPHID',
             'transform'));
       }
     }
 
     $this->transforms = $specs;
     return $this;
   }
 
   public function withLengthBetween($min, $max) {
     $this->minLength = $min;
     $this->maxLength = $max;
     return $this;
   }
 
   public function withNames(array $names) {
     $this->names = $names;
     return $this;
   }
 
   public function withIsPartial($partial) {
     $this->isPartial = $partial;
     return $this;
   }
 
   public function showOnlyExplicitUploads($explicit_uploads) {
     $this->explicitUploads = $explicit_uploads;
     return $this;
   }
 
+  public function needTransforms(array $transforms) {
+    $this->needTransforms = $transforms;
+    return $this;
+  }
+
   public function newResultObject() {
     return new PhabricatorFile();
   }
 
   protected function loadPage() {
     $files = $this->loadStandardPage(new PhabricatorFile());
 
     if (!$files) {
       return $files;
     }
 
     // We need to load attached objects to perform policy checks for files.
     // First, load the edges.
 
     $edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
     $file_phids = mpull($files, 'getPHID');
     $edges = id(new PhabricatorEdgeQuery())
       ->withSourcePHIDs($file_phids)
       ->withEdgeTypes(array($edge_type))
       ->execute();
 
     $object_phids = array();
     foreach ($files as $file) {
       $phids = array_keys($edges[$file->getPHID()][$edge_type]);
       $file->attachObjectPHIDs($phids);
 
       if ($file->getIsProfileImage()) {
         // If this is a profile image, don't bother loading related files.
         // It will always be visible, and we can get into trouble if we try
         // to load objects and end up stuck in a cycle. See T8478.
         continue;
       }
 
       foreach ($phids as $phid) {
         $object_phids[$phid] = true;
       }
     }
 
     // If this file is a transform of another file, load that file too. If you
     // can see the original file, you can see the thumbnail.
 
     // TODO: It might be nice to put this directly on PhabricatorFile and remove
     // the PhabricatorTransformedFile table, which would be a little simpler.
 
     $xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
       'transformedPHID IN (%Ls)',
       $file_phids);
     $xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');
     foreach ($xform_phids as $derived_phid => $original_phid) {
       $object_phids[$original_phid] = true;
     }
 
     $object_phids = array_keys($object_phids);
 
     // Now, load the objects.
 
     $objects = array();
     if ($object_phids) {
       // NOTE: We're explicitly turning policy exceptions off, since the rule
       // here is "you can see the file if you can see ANY associated object".
       // Without this explicit flag, we'll incorrectly throw unless you can
       // see ALL associated objects.
 
       $objects = id(new PhabricatorObjectQuery())
         ->setParentQuery($this)
         ->setViewer($this->getViewer())
         ->withPHIDs($object_phids)
         ->setRaisePolicyExceptions(false)
         ->execute();
       $objects = mpull($objects, null, 'getPHID');
     }
 
     foreach ($files as $file) {
       $file_objects = array_select_keys($objects, $file->getObjectPHIDs());
       $file->attachObjects($file_objects);
     }
 
     foreach ($files as $key => $file) {
       $original_phid = idx($xform_phids, $file->getPHID());
       if ($original_phid == PhabricatorPHIDConstants::PHID_VOID) {
         // This is a special case for builtin files, which are handled
         // oddly.
         $original = null;
       } else if ($original_phid) {
         $original = idx($objects, $original_phid);
         if (!$original) {
           // If the viewer can't see the original file, also prevent them from
           // seeing the transformed file.
           $this->didRejectResult($file);
           unset($files[$key]);
           continue;
         }
       } else {
         $original = null;
       }
       $file->attachOriginalFile($original);
     }
 
     return $files;
   }
 
+  protected function didFilterPage(array $files) {
+    $xform_keys = $this->needTransforms;
+    if ($xform_keys !== null) {
+      $xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
+        'originalPHID IN (%Ls) AND transform IN (%Ls)',
+        mpull($files, 'getPHID'),
+        $xform_keys);
+
+      if ($xforms) {
+        $xfiles = id(new PhabricatorFile())->loadAllWhere(
+          'phid IN (%Ls)',
+          mpull($xforms, 'getTransformedPHID'));
+        $xfiles = mpull($xfiles, null, 'getPHID');
+      }
+
+      $xform_map = array();
+      foreach ($xforms as $xform) {
+        $xfile = idx($xfiles, $xform->getTransformedPHID());
+        if (!$xfile) {
+          continue;
+        }
+        $original_phid = $xform->getOriginalPHID();
+        $xform_key = $xform->getTransform();
+        $xform_map[$original_phid][$xform_key] = $xfile;
+      }
+
+      $default_xforms = array_fill_keys($xform_keys, null);
+
+      foreach ($files as $file) {
+        $file_xforms = idx($xform_map, $file->getPHID(), array());
+        $file_xforms += $default_xforms;
+        $file->attachTransforms($file_xforms);
+      }
+    }
+
+    return $files;
+  }
+
   protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
     $joins = parent::buildJoinClauseParts($conn);
 
     if ($this->transforms) {
       $joins[] = qsprintf(
         $conn,
         'JOIN %T t ON t.transformedPHID = f.phid',
         id(new PhabricatorTransformedFile())->getTableName());
     }
 
     return $joins;
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'f.id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'f.phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->authorPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'f.authorPHID IN (%Ls)',
         $this->authorPHIDs);
     }
 
     if ($this->explicitUploads !== null) {
       $where[] = qsprintf(
         $conn,
         'f.isExplicitUpload = %d',
         (int)$this->explicitUploads);
     }
 
     if ($this->transforms !== null) {
       $clauses = array();
       foreach ($this->transforms as $transform) {
         if ($transform['transform'] === true) {
           $clauses[] = qsprintf(
             $conn,
             '(t.originalPHID = %s)',
             $transform['originalPHID']);
         } else {
           $clauses[] = qsprintf(
             $conn,
             '(t.originalPHID = %s AND t.transform = %s)',
             $transform['originalPHID'],
             $transform['transform']);
         }
       }
       $where[] = qsprintf($conn, '(%Q)', implode(') OR (', $clauses));
     }
 
     if ($this->dateCreatedAfter !== null) {
       $where[] = qsprintf(
         $conn,
         'f.dateCreated >= %d',
         $this->dateCreatedAfter);
     }
 
     if ($this->dateCreatedBefore !== null) {
       $where[] = qsprintf(
         $conn,
         'f.dateCreated <= %d',
         $this->dateCreatedBefore);
     }
 
     if ($this->contentHashes !== null) {
       $where[] = qsprintf(
         $conn,
         'f.contentHash IN (%Ls)',
         $this->contentHashes);
     }
 
     if ($this->minLength !== null) {
       $where[] = qsprintf(
         $conn,
         'byteSize >= %d',
         $this->minLength);
     }
 
     if ($this->maxLength !== null) {
       $where[] = qsprintf(
         $conn,
         'byteSize <= %d',
         $this->maxLength);
     }
 
     if ($this->names !== null) {
       $where[] = qsprintf(
         $conn,
         'name in (%Ls)',
         $this->names);
     }
 
     if ($this->isPartial !== null) {
       $where[] = qsprintf(
         $conn,
         'isPartial = %d',
         (int)$this->isPartial);
     }
 
     return $where;
   }
 
   protected function getPrimaryTableAlias() {
     return 'f';
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorFilesApplication';
   }
 
 }
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
index f1d4c11bd..ff2a4d27a 100644
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -1,1375 +1,1341 @@
 <?php
 
 /**
  * Parameters
  * ==========
  *
  * When creating a new file using a method like @{method:newFromFileData}, these
  * parameters are supported:
  *
  *   | name | Human readable filename.
  *   | authorPHID | User PHID of uploader.
  *   | ttl | Temporary file lifetime, in seconds.
  *   | viewPolicy | File visibility policy.
  *   | isExplicitUpload | Used to show users files they explicitly uploaded.
  *   | canCDN | Allows the file to be cached and delivered over a CDN.
  *   | mime-type | Optional, explicit file MIME type.
  *   | builtin | Optional filename, identifies this as a builtin.
  *
  */
 final class PhabricatorFile extends PhabricatorFileDAO
   implements
     PhabricatorApplicationTransactionInterface,
     PhabricatorTokenReceiverInterface,
     PhabricatorSubscribableInterface,
     PhabricatorFlaggableInterface,
     PhabricatorPolicyInterface,
     PhabricatorDestructibleInterface {
 
   const STORAGE_FORMAT_RAW  = 'raw';
 
   const METADATA_IMAGE_WIDTH  = 'width';
   const METADATA_IMAGE_HEIGHT = 'height';
   const METADATA_CAN_CDN = 'canCDN';
   const METADATA_BUILTIN = 'builtin';
   const METADATA_PARTIAL = 'partial';
   const METADATA_PROFILE = 'profile';
 
   protected $name;
   protected $mimeType;
   protected $byteSize;
   protected $authorPHID;
   protected $secretKey;
   protected $contentHash;
   protected $metadata = array();
   protected $mailKey;
 
   protected $storageEngine;
   protected $storageFormat;
   protected $storageHandle;
 
   protected $ttl;
   protected $isExplicitUpload = 1;
   protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
   protected $isPartial = 0;
 
   private $objects = self::ATTACHABLE;
   private $objectPHIDs = self::ATTACHABLE;
   private $originalFile = self::ATTACHABLE;
+  private $transforms = self::ATTACHABLE;
 
   public static function initializeNewFile() {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withClasses(array('PhabricatorFilesApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(
       FilesDefaultViewCapability::CAPABILITY);
 
     return id(new PhabricatorFile())
       ->setViewPolicy($view_policy)
       ->setIsPartial(0)
       ->attachOriginalFile(null)
       ->attachObjects(array())
       ->attachObjectPHIDs(array());
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'metadata' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'text255?',
         'mimeType' => 'text255?',
         'byteSize' => 'uint64',
         'storageEngine' => 'text32',
         'storageFormat' => 'text32',
         'storageHandle' => 'text255',
         'authorPHID' => 'phid?',
         'secretKey' => 'bytes20?',
         'contentHash' => 'bytes40?',
         'ttl' => 'epoch?',
         'isExplicitUpload' => 'bool?',
         'mailKey' => 'bytes20',
         'isPartial' => 'bool',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'authorPHID' => array(
           'columns' => array('authorPHID'),
         ),
         'contentHash' => array(
           'columns' => array('contentHash'),
         ),
         'key_ttl' => array(
           'columns' => array('ttl'),
         ),
         'key_dateCreated' => array(
           'columns' => array('dateCreated'),
         ),
         'key_partial' => array(
           'columns' => array('authorPHID', 'isPartial'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorFileFilePHIDType::TYPECONST);
   }
 
   public function save() {
     if (!$this->getSecretKey()) {
       $this->setSecretKey($this->generateSecretKey());
     }
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
   public function getMonogram() {
     return 'F'.$this->getID();
   }
 
+  public function scrambleSecret() {
+    return $this->setSecretKey($this->generateSecretKey());
+  }
+
   public static function readUploadedFileData($spec) {
     if (!$spec) {
       throw new Exception(pht('No file was uploaded!'));
     }
 
     $err = idx($spec, 'error');
     if ($err) {
       throw new PhabricatorFileUploadException($err);
     }
 
     $tmp_name = idx($spec, 'tmp_name');
     $is_valid = @is_uploaded_file($tmp_name);
     if (!$is_valid) {
       throw new Exception(pht('File is not an uploaded file.'));
     }
 
     $file_data = Filesystem::readFile($tmp_name);
     $file_size = idx($spec, 'size');
 
     if (strlen($file_data) != $file_size) {
       throw new Exception(pht('File size disagrees with uploaded size.'));
     }
 
     return $file_data;
   }
 
   public static function newFromPHPUpload($spec, array $params = array()) {
     $file_data = self::readUploadedFileData($spec);
 
     $file_name = nonempty(
       idx($params, 'name'),
       idx($spec,   'name'));
     $params = array(
       'name' => $file_name,
     ) + $params;
 
     return self::newFromFileData($file_data, $params);
   }
 
   public static function newFromXHRUpload($data, array $params = array()) {
     return self::newFromFileData($data, $params);
   }
 
 
   /**
    * Given a block of data, try to load an existing file with the same content
    * if one exists. If it does not, build a new file.
    *
    * This method is generally used when we have some piece of semi-trusted data
    * like a diff or a file from a repository that we want to show to the user.
    * We can't just dump it out because it may be dangerous for any number of
    * reasons; instead, we need to serve it through the File abstraction so it
    * ends up on the CDN domain if one is configured and so on. However, if we
    * simply wrote a new file every time we'd potentially end up with a lot
    * of redundant data in file storage.
    *
    * To solve these problems, we use file storage as a cache and reuse the
    * same file again if we've previously written it.
    *
    * NOTE: This method unguards writes.
    *
    * @param string  Raw file data.
    * @param dict    Dictionary of file information.
    */
   public static function buildFromFileDataOrHash(
     $data,
     array $params = array()) {
 
     $file = id(new PhabricatorFile())->loadOneWhere(
       'name = %s AND contentHash = %s LIMIT 1',
       idx($params, 'name'),
       self::hashFileContent($data));
 
     if (!$file) {
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file = self::newFromFileData($data, $params);
       unset($unguarded);
     }
 
     return $file;
   }
 
   public static function newFileFromContentHash($hash, array $params) {
     // Check to see if a file with same contentHash exist
     $file = id(new PhabricatorFile())->loadOneWhere(
       'contentHash = %s LIMIT 1',
       $hash);
 
     if ($file) {
       // copy storageEngine, storageHandle, storageFormat
       $copy_of_storage_engine = $file->getStorageEngine();
       $copy_of_storage_handle = $file->getStorageHandle();
       $copy_of_storage_format = $file->getStorageFormat();
       $copy_of_byte_size = $file->getByteSize();
       $copy_of_mime_type = $file->getMimeType();
 
       $new_file = self::initializeNewFile();
 
       $new_file->setByteSize($copy_of_byte_size);
 
       $new_file->setContentHash($hash);
       $new_file->setStorageEngine($copy_of_storage_engine);
       $new_file->setStorageHandle($copy_of_storage_handle);
       $new_file->setStorageFormat($copy_of_storage_format);
       $new_file->setMimeType($copy_of_mime_type);
       $new_file->copyDimensions($file);
 
       $new_file->readPropertiesFromParameters($params);
 
       $new_file->save();
 
       return $new_file;
     }
 
     return $file;
   }
 
   public static function newChunkedFile(
     PhabricatorFileStorageEngine $engine,
     $length,
     array $params) {
 
     $file = self::initializeNewFile();
 
     $file->setByteSize($length);
 
     // TODO: We might be able to test the first chunk in order to figure
     // this out more reliably, since MIME detection usually examines headers.
     // However, enormous files are probably always either actually raw data
     // or reasonable to treat like raw data.
     $file->setMimeType('application/octet-stream');
 
     $chunked_hash = idx($params, 'chunkedHash');
     if ($chunked_hash) {
       $file->setContentHash($chunked_hash);
     } else {
       // See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some
       // discussion of this.
       $seed = Filesystem::readRandomBytes(64);
       $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput(
         $seed);
       $file->setContentHash($hash);
     }
 
     $file->setStorageEngine($engine->getEngineIdentifier());
     $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
     $file->setStorageFormat(self::STORAGE_FORMAT_RAW);
     $file->setIsPartial(1);
 
     $file->readPropertiesFromParameters($params);
 
     return $file;
   }
 
   private static function buildFromFileData($data, array $params = array()) {
 
     if (isset($params['storageEngines'])) {
       $engines = $params['storageEngines'];
     } else {
       $size = strlen($data);
       $engines = PhabricatorFileStorageEngine::loadStorageEngines($size);
 
       if (!$engines) {
         throw new Exception(
           pht(
             'No configured storage engine can store this file. See '.
             '"Configuring File Storage" in the documentation for '.
             'information on configuring storage engines.'));
       }
     }
 
     assert_instances_of($engines, 'PhabricatorFileStorageEngine');
     if (!$engines) {
       throw new Exception(pht('No valid storage engines are available!'));
     }
 
     $file = self::initializeNewFile();
 
     $data_handle = null;
     $engine_identifier = null;
     $exceptions = array();
     foreach ($engines as $engine) {
       $engine_class = get_class($engine);
       try {
         list($engine_identifier, $data_handle) = $file->writeToEngine(
           $engine,
           $data,
           $params);
 
         // We stored the file somewhere so stop trying to write it to other
         // places.
         break;
       } catch (PhabricatorFileStorageConfigurationException $ex) {
         // If an engine is outright misconfigured (or misimplemented), raise
         // that immediately since it probably needs attention.
         throw $ex;
       } catch (Exception $ex) {
         phlog($ex);
 
         // If an engine doesn't work, keep trying all the other valid engines
         // in case something else works.
         $exceptions[$engine_class] = $ex;
       }
     }
 
     if (!$data_handle) {
       throw new PhutilAggregateException(
         pht('All storage engines failed to write file:'),
         $exceptions);
     }
 
     $file->setByteSize(strlen($data));
     $file->setContentHash(self::hashFileContent($data));
 
     $file->setStorageEngine($engine_identifier);
     $file->setStorageHandle($data_handle);
 
     // TODO: This is probably YAGNI, but allows for us to do encryption or
     // compression later if we want.
     $file->setStorageFormat(self::STORAGE_FORMAT_RAW);
 
     $file->readPropertiesFromParameters($params);
 
     if (!$file->getMimeType()) {
       $tmp = new TempFile();
       Filesystem::writeFile($tmp, $data);
       $file->setMimeType(Filesystem::getMimeType($tmp));
     }
 
     try {
       $file->updateDimensions(false);
     } catch (Exception $ex) {
       // Do nothing
     }
 
     $file->save();
 
     return $file;
   }
 
   public static function newFromFileData($data, array $params = array()) {
     $hash = self::hashFileContent($data);
     $file = self::newFileFromContentHash($hash, $params);
 
     if ($file) {
       return $file;
     }
 
     return self::buildFromFileData($data, $params);
   }
 
   public function migrateToEngine(PhabricatorFileStorageEngine $engine) {
     if (!$this->getID() || !$this->getStorageHandle()) {
       throw new Exception(
         pht("You can not migrate a file which hasn't yet been saved."));
     }
 
     $data = $this->loadFileData();
     $params = array(
       'name' => $this->getName(),
     );
 
     list($new_identifier, $new_handle) = $this->writeToEngine(
       $engine,
       $data,
       $params);
 
     $old_engine = $this->instantiateStorageEngine();
     $old_identifier = $this->getStorageEngine();
     $old_handle = $this->getStorageHandle();
 
     $this->setStorageEngine($new_identifier);
     $this->setStorageHandle($new_handle);
     $this->save();
 
     $this->deleteFileDataIfUnused(
       $old_engine,
       $old_identifier,
       $old_handle);
 
     return $this;
   }
 
   private function writeToEngine(
     PhabricatorFileStorageEngine $engine,
     $data,
     array $params) {
 
     $engine_class = get_class($engine);
 
     $data_handle = $engine->writeFile($data, $params);
 
     if (!$data_handle || strlen($data_handle) > 255) {
       // This indicates an improperly implemented storage engine.
       throw new PhabricatorFileStorageConfigurationException(
         pht(
           "Storage engine '%s' executed %s but did not return a valid ".
           "handle ('%s') to the data: it must be nonempty and no longer ".
           "than 255 characters.",
           $engine_class,
           'writeFile()',
           $data_handle));
     }
 
     $engine_identifier = $engine->getEngineIdentifier();
     if (!$engine_identifier || strlen($engine_identifier) > 32) {
       throw new PhabricatorFileStorageConfigurationException(
         pht(
           "Storage engine '%s' returned an improper engine identifier '{%s}': ".
           "it must be nonempty and no longer than 32 characters.",
           $engine_class,
           $engine_identifier));
     }
 
     return array($engine_identifier, $data_handle);
   }
 
 
   /**
    * Download a remote resource over HTTP and save the response body as a file.
    *
    * This method respects `security.outbound-blacklist`, and protects against
    * HTTP redirection (by manually following "Location" headers and verifying
    * each destination). It does not protect against DNS rebinding. See
    * discussion in T6755.
    */
   public static function newFromFileDownload($uri, array $params = array()) {
     $timeout = 5;
 
     $redirects = array();
     $current = $uri;
     while (true) {
       try {
         if (count($redirects) > 10) {
           throw new Exception(
             pht('Too many redirects trying to fetch remote URI.'));
         }
 
         $resolved = PhabricatorEnv::requireValidRemoteURIForFetch(
           $current,
           array(
             'http',
             'https',
           ));
 
         list($resolved_uri, $resolved_domain) = $resolved;
 
         $current = new PhutilURI($current);
         if ($current->getProtocol() == 'http') {
           // For HTTP, we can use a pre-resolved URI to defuse DNS rebinding.
           $fetch_uri = $resolved_uri;
           $fetch_host = $resolved_domain;
         } else {
           // For HTTPS, we can't: cURL won't verify the SSL certificate if
           // the domain has been replaced with an IP. But internal services
           // presumably will not have valid certificates for rebindable
           // domain names on attacker-controlled domains, so the DNS rebinding
           // attack should generally not be possible anyway.
           $fetch_uri = $current;
           $fetch_host = null;
         }
 
         $future = id(new HTTPSFuture($fetch_uri))
           ->setFollowLocation(false)
           ->setTimeout($timeout);
 
         if ($fetch_host !== null) {
           $future->addHeader('Host', $fetch_host);
         }
 
         list($status, $body, $headers) = $future->resolve();
 
         if ($status->isRedirect()) {
           // This is an HTTP 3XX status, so look for a "Location" header.
           $location = null;
           foreach ($headers as $header) {
             list($name, $value) = $header;
             if (phutil_utf8_strtolower($name) == 'location') {
               $location = $value;
               break;
             }
           }
 
           // HTTP 3XX status with no "Location" header, just treat this like
           // a normal HTTP error.
           if ($location === null) {
             throw $status;
           }
 
           if (isset($redirects[$location])) {
             throw new Exception(
               pht('Encountered loop while following redirects.'));
           }
 
           $redirects[$location] = $location;
           $current = $location;
           // We'll fall off the bottom and go try this URI now.
         } else if ($status->isError()) {
           // This is something other than an HTTP 2XX or HTTP 3XX status, so
           // just bail out.
           throw $status;
         } else {
           // This is HTTP 2XX, so use the response body to save the
           // file data.
           $params = $params + array(
             'name' => basename($uri),
           );
 
           return self::newFromFileData($body, $params);
         }
       } catch (Exception $ex) {
         if ($redirects) {
           throw new PhutilProxyException(
             pht(
               'Failed to fetch remote URI "%s" after following %s redirect(s) '.
               '(%s): %s',
               $uri,
               phutil_count($redirects),
               implode(' > ', array_keys($redirects)),
               $ex->getMessage()),
             $ex);
         } else {
           throw $ex;
         }
       }
     }
   }
 
   public static function normalizeFileName($file_name) {
     $pattern = "@[\\x00-\\x19#%&+!~'\$\"\/=\\\\?<> ]+@";
     $file_name = preg_replace($pattern, '_', $file_name);
     $file_name = preg_replace('@_+@', '_', $file_name);
     $file_name = trim($file_name, '_');
 
     $disallowed_filenames = array(
       '.'  => 'dot',
       '..' => 'dotdot',
       ''   => 'file',
     );
     $file_name = idx($disallowed_filenames, $file_name, $file_name);
 
     return $file_name;
   }
 
   public function delete() {
     // We want to delete all the rows which mark this file as the transformation
     // of some other file (since we're getting rid of it). We also delete all
     // the transformations of this file, so that a user who deletes an image
     // doesn't need to separately hunt down and delete a bunch of thumbnails and
     // resizes of it.
 
     $outbound_xforms = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms(
         array(
           array(
             'originalPHID' => $this->getPHID(),
             'transform'    => true,
           ),
         ))
       ->execute();
 
     foreach ($outbound_xforms as $outbound_xform) {
       $outbound_xform->delete();
     }
 
     $inbound_xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
       'transformedPHID = %s',
       $this->getPHID());
 
     $this->openTransaction();
       foreach ($inbound_xforms as $inbound_xform) {
         $inbound_xform->delete();
       }
       $ret = parent::delete();
     $this->saveTransaction();
 
     $this->deleteFileDataIfUnused(
       $this->instantiateStorageEngine(),
       $this->getStorageEngine(),
       $this->getStorageHandle());
 
     return $ret;
   }
 
 
   /**
    * Destroy stored file data if there are no remaining files which reference
    * it.
    */
   public function deleteFileDataIfUnused(
     PhabricatorFileStorageEngine $engine,
     $engine_identifier,
     $handle) {
 
     // Check to see if any files are using storage.
     $usage = id(new PhabricatorFile())->loadAllWhere(
       'storageEngine = %s AND storageHandle = %s LIMIT 1',
       $engine_identifier,
       $handle);
 
     // If there are no files using the storage, destroy the actual storage.
     if (!$usage) {
       try {
         $engine->deleteFile($handle);
       } catch (Exception $ex) {
         // In the worst case, we're leaving some data stranded in a storage
         // engine, which is not a big deal.
         phlog($ex);
       }
     }
   }
 
 
   public static function hashFileContent($data) {
     return sha1($data);
   }
 
   public function loadFileData() {
 
     $engine = $this->instantiateStorageEngine();
     $data = $engine->readFile($this->getStorageHandle());
 
     switch ($this->getStorageFormat()) {
       case self::STORAGE_FORMAT_RAW:
         $data = $data;
         break;
       default:
         throw new Exception(pht('Unknown storage format.'));
     }
 
     return $data;
   }
 
 
   /**
    * Return an iterable which emits file content bytes.
    *
    * @param int Offset for the start of data.
    * @param int Offset for the end of data.
    * @return Iterable Iterable object which emits requested data.
    */
   public function getFileDataIterator($begin = null, $end = null) {
     $engine = $this->instantiateStorageEngine();
     return $engine->getFileDataIterator($this, $begin, $end);
   }
 
 
   public function getViewURI() {
     if (!$this->getPHID()) {
       throw new Exception(
         pht('You must save a file before you can generate a view URI.'));
     }
 
-    return $this->getCDNURI(null);
+    return $this->getCDNURI();
   }
 
-  private function getCDNURI($token) {
+  public function getCDNURI() {
     $name = self::normalizeFileName($this->getName());
     $name = phutil_escape_uri($name);
 
     $parts = array();
     $parts[] = 'file';
     $parts[] = 'data';
 
     // If this is an instanced install, add the instance identifier to the URI.
     // Instanced configurations behind a CDN may not be able to control the
     // request domain used by the CDN (as with AWS CloudFront). Embedding the
     // instance identity in the path allows us to distinguish between requests
     // originating from different instances but served through the same CDN.
     $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
     if (strlen($instance)) {
       $parts[] = '@'.$instance;
     }
 
     $parts[] = $this->getSecretKey();
     $parts[] = $this->getPHID();
-    if ($token) {
-      $parts[] = $token;
-    }
     $parts[] = $name;
 
     $path = '/'.implode('/', $parts);
 
     // If this file is only partially uploaded, we're just going to return a
     // local URI to make sure that Ajax works, since the page is inevitably
     // going to give us an error back.
     if ($this->getIsPartial()) {
       return PhabricatorEnv::getURI($path);
     } else {
       return PhabricatorEnv::getCDNURI($path);
     }
   }
 
-  /**
-   * Get the CDN URI for this file, including a one-time-use security token.
-   *
-   */
-  public function getCDNURIWithToken() {
-    if (!$this->getPHID()) {
-      throw new Exception(
-        pht('You must save a file before you can generate a CDN URI.'));
-    }
-
-    return $this->getCDNURI($this->generateOneTimeToken());
-  }
-
 
   public function getInfoURI() {
     return '/'.$this->getMonogram();
   }
 
   public function getBestURI() {
     if ($this->isViewableInBrowser()) {
       return $this->getViewURI();
     } else {
       return $this->getInfoURI();
     }
   }
 
   public function getDownloadURI() {
     $uri = id(new PhutilURI($this->getViewURI()))
       ->setQueryParam('download', true);
     return (string)$uri;
   }
 
   public function getURIForTransform(PhabricatorFileTransform $transform) {
     return $this->getTransformedURI($transform->getTransformKey());
   }
 
   private function getTransformedURI($transform) {
     $parts = array();
     $parts[] = 'file';
     $parts[] = 'xform';
 
     $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
     if (strlen($instance)) {
       $parts[] = '@'.$instance;
     }
 
     $parts[] = $transform;
     $parts[] = $this->getPHID();
     $parts[] = $this->getSecretKey();
 
     $path = implode('/', $parts);
     $path = $path.'/';
 
     return PhabricatorEnv::getCDNURI($path);
   }
 
   public function isViewableInBrowser() {
     return ($this->getViewableMimeType() !== null);
   }
 
   public function isViewableImage() {
     if (!$this->isViewableInBrowser()) {
       return false;
     }
 
     $mime_map = PhabricatorEnv::getEnvConfig('files.image-mime-types');
     $mime_type = $this->getMimeType();
     return idx($mime_map, $mime_type);
   }
 
   public function isAudio() {
     if (!$this->isViewableInBrowser()) {
       return false;
     }
 
     $mime_map = PhabricatorEnv::getEnvConfig('files.audio-mime-types');
     $mime_type = $this->getMimeType();
     return idx($mime_map, $mime_type);
   }
 
   public function isTransformableImage() {
     // NOTE: The way the 'gd' extension works in PHP is that you can install it
     // with support for only some file types, so it might be able to handle
     // PNG but not JPEG. Try to generate thumbnails for whatever we can. Setup
     // warns you if you don't have complete support.
 
     $matches = null;
     $ok = preg_match(
       '@^image/(gif|png|jpe?g)@',
       $this->getViewableMimeType(),
       $matches);
     if (!$ok) {
       return false;
     }
 
     switch ($matches[1]) {
       case 'jpg';
       case 'jpeg':
         return function_exists('imagejpeg');
         break;
       case 'png':
         return function_exists('imagepng');
         break;
       case 'gif':
         return function_exists('imagegif');
         break;
       default:
         throw new Exception(pht('Unknown type matched as image MIME type.'));
     }
   }
 
   public static function getTransformableImageFormats() {
     $supported = array();
 
     if (function_exists('imagejpeg')) {
       $supported[] = 'jpg';
     }
 
     if (function_exists('imagepng')) {
       $supported[] = 'png';
     }
 
     if (function_exists('imagegif')) {
       $supported[] = 'gif';
     }
 
     return $supported;
   }
 
   public function instantiateStorageEngine() {
     return self::buildEngine($this->getStorageEngine());
   }
 
   public static function buildEngine($engine_identifier) {
     $engines = self::buildAllEngines();
     foreach ($engines as $engine) {
       if ($engine->getEngineIdentifier() == $engine_identifier) {
         return $engine;
       }
     }
 
     throw new Exception(
       pht(
         "Storage engine '%s' could not be located!",
         $engine_identifier));
   }
 
   public static function buildAllEngines() {
     return id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorFileStorageEngine')
       ->execute();
   }
 
   public function getViewableMimeType() {
     $mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
 
     $mime_type = $this->getMimeType();
     $mime_parts = explode(';', $mime_type);
     $mime_type = trim(reset($mime_parts));
 
     return idx($mime_map, $mime_type);
   }
 
   public function getDisplayIconForMimeType() {
     $mime_map = PhabricatorEnv::getEnvConfig('files.icon-mime-types');
     $mime_type = $this->getMimeType();
     return idx($mime_map, $mime_type, 'fa-file-o');
   }
 
   public function validateSecretKey($key) {
     return ($key == $this->getSecretKey());
   }
 
   public function generateSecretKey() {
     return Filesystem::readRandomCharacters(20);
   }
 
   public function updateDimensions($save = true) {
     if (!$this->isViewableImage()) {
       throw new Exception(pht('This file is not a viewable image.'));
     }
 
     if (!function_exists('imagecreatefromstring')) {
       throw new Exception(pht('Cannot retrieve image information.'));
     }
 
     $data = $this->loadFileData();
 
     $img = imagecreatefromstring($data);
     if ($img === false) {
       throw new Exception(pht('Error when decoding image.'));
     }
 
     $this->metadata[self::METADATA_IMAGE_WIDTH] = imagesx($img);
     $this->metadata[self::METADATA_IMAGE_HEIGHT] = imagesy($img);
 
     if ($save) {
       $this->save();
     }
 
     return $this;
   }
 
   public function copyDimensions(PhabricatorFile $file) {
     $metadata = $file->getMetadata();
     $width = idx($metadata, self::METADATA_IMAGE_WIDTH);
     if ($width) {
       $this->metadata[self::METADATA_IMAGE_WIDTH] = $width;
     }
     $height = idx($metadata, self::METADATA_IMAGE_HEIGHT);
     if ($height) {
       $this->metadata[self::METADATA_IMAGE_HEIGHT] = $height;
     }
 
     return $this;
   }
 
 
   /**
    * Load (or build) the {@class:PhabricatorFile} objects for builtin file
    * resources. The builtin mechanism allows files shipped with Phabricator
    * to be treated like normal files so that APIs do not need to special case
    * things like default images or deleted files.
    *
    * Builtins are located in `resources/builtin/` and identified by their
    * name.
    *
    * @param  PhabricatorUser Viewing user.
    * @param  list<PhabricatorFilesBuiltinFile> List of builtin file specs.
    * @return dict<string, PhabricatorFile> Dictionary of named builtins.
    */
   public static function loadBuiltins(PhabricatorUser $user, array $builtins) {
     $builtins = mpull($builtins, null, 'getBuiltinFileKey');
 
     $specs = array();
     foreach ($builtins as $key => $buitin) {
       $specs[] = array(
         'originalPHID' => PhabricatorPHIDConstants::PHID_VOID,
         'transform'    => $key,
       );
     }
 
     // NOTE: Anyone is allowed to access builtin files.
 
     $files = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms($specs)
       ->execute();
 
     $results = array();
     foreach ($files as $file) {
       $builtin_key = $file->getBuiltinName();
       if ($builtin_key !== null) {
         $results[$builtin_key] = $file;
       }
     }
 
     $build = array();
     foreach ($builtins as $key => $builtin) {
       if (isset($results[$key])) {
         continue;
       }
 
       $data = $builtin->loadBuiltinFileData();
 
       $params = array(
         'name' => $builtin->getBuiltinDisplayName(),
         'ttl'  => time() + (60 * 60 * 24 * 7),
         'canCDN' => true,
         'builtin' => $key,
       );
 
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $file = self::newFromFileData($data, $params);
         $xform = id(new PhabricatorTransformedFile())
           ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID)
           ->setTransform($key)
           ->setTransformedPHID($file->getPHID())
           ->save();
       unset($unguarded);
 
       $file->attachObjectPHIDs(array());
       $file->attachObjects(array());
 
       $results[$key] = $file;
     }
 
     return $results;
   }
 
 
   /**
    * Convenience wrapper for @{method:loadBuiltins}.
    *
    * @param PhabricatorUser   Viewing user.
    * @param string            Single builtin name to load.
    * @return PhabricatorFile  Corresponding builtin file.
    */
   public static function loadBuiltin(PhabricatorUser $user, $name) {
     $builtin = id(new PhabricatorFilesOnDiskBuiltinFile())
       ->setName($name);
 
     $key = $builtin->getBuiltinFileKey();
 
     return idx(self::loadBuiltins($user, array($builtin)), $key);
   }
 
   public function getObjects() {
     return $this->assertAttached($this->objects);
   }
 
   public function attachObjects(array $objects) {
     $this->objects = $objects;
     return $this;
   }
 
   public function getObjectPHIDs() {
     return $this->assertAttached($this->objectPHIDs);
   }
 
   public function attachObjectPHIDs(array $object_phids) {
     $this->objectPHIDs = $object_phids;
     return $this;
   }
 
   public function getOriginalFile() {
     return $this->assertAttached($this->originalFile);
   }
 
   public function attachOriginalFile(PhabricatorFile $file = null) {
     $this->originalFile = $file;
     return $this;
   }
 
   public function getImageHeight() {
     if (!$this->isViewableImage()) {
       return null;
     }
     return idx($this->metadata, self::METADATA_IMAGE_HEIGHT);
   }
 
   public function getImageWidth() {
     if (!$this->isViewableImage()) {
       return null;
     }
     return idx($this->metadata, self::METADATA_IMAGE_WIDTH);
   }
 
   public function getCanCDN() {
     if (!$this->isViewableImage()) {
       return false;
     }
 
     return idx($this->metadata, self::METADATA_CAN_CDN);
   }
 
   public function setCanCDN($can_cdn) {
     $this->metadata[self::METADATA_CAN_CDN] = $can_cdn ? 1 : 0;
     return $this;
   }
 
   public function isBuiltin() {
     return ($this->getBuiltinName() !== null);
   }
 
   public function getBuiltinName() {
     return idx($this->metadata, self::METADATA_BUILTIN);
   }
 
   public function setBuiltinName($name) {
     $this->metadata[self::METADATA_BUILTIN] = $name;
     return $this;
   }
 
   public function getIsProfileImage() {
     return idx($this->metadata, self::METADATA_PROFILE);
   }
 
   public function setIsProfileImage($value) {
     $this->metadata[self::METADATA_PROFILE] = $value;
     return $this;
   }
 
-  protected function generateOneTimeToken() {
-    $key = Filesystem::readRandomCharacters(16);
-    $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE;
-
-    // Save the new secret.
-    $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
-      $token = id(new PhabricatorAuthTemporaryToken())
-        ->setTokenResource($this->getPHID())
-        ->setTokenType($token_type)
-        ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
-        ->setTokenCode(PhabricatorHash::digest($key))
-        ->save();
-    unset($unguarded);
-
-    return $key;
-  }
-
-  public function validateOneTimeToken($token_code) {
-    $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE;
-
-    $token = id(new PhabricatorAuthTemporaryTokenQuery())
-      ->setViewer(PhabricatorUser::getOmnipotentUser())
-      ->withTokenResources(array($this->getPHID()))
-      ->withTokenTypes(array($token_type))
-      ->withExpired(false)
-      ->withTokenCodes(array(PhabricatorHash::digest($token_code)))
-      ->executeOne();
-
-    return $token;
-  }
-
-
   /**
    * Write the policy edge between this file and some object.
    *
    * @param phid Object PHID to attach to.
    * @return this
    */
   public function attachToObject($phid) {
     $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST;
 
     id(new PhabricatorEdgeEditor())
       ->addEdge($phid, $edge_type, $this->getPHID())
       ->save();
 
     return $this;
   }
 
 
   /**
    * Remove the policy edge between this file and some object.
    *
    * @param phid Object PHID to detach from.
    * @return this
    */
   public function detachFromObject($phid) {
     $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST;
 
     id(new PhabricatorEdgeEditor())
       ->removeEdge($phid, $edge_type, $this->getPHID())
       ->save();
 
     return $this;
   }
 
 
   /**
    * Configure a newly created file object according to specified parameters.
    *
    * This method is called both when creating a file from fresh data, and
    * when creating a new file which reuses existing storage.
    *
    * @param map<string, wild>   Bag of parameters, see @{class:PhabricatorFile}
    *  for documentation.
    * @return this
    */
   private function readPropertiesFromParameters(array $params) {
     $file_name = idx($params, 'name');
     $this->setName($file_name);
 
     $author_phid = idx($params, 'authorPHID');
     $this->setAuthorPHID($author_phid);
 
     $file_ttl = idx($params, 'ttl');
     $this->setTtl($file_ttl);
 
     $view_policy = idx($params, 'viewPolicy');
     if ($view_policy) {
       $this->setViewPolicy($params['viewPolicy']);
     }
 
     $is_explicit = (idx($params, 'isExplicitUpload') ? 1 : 0);
     $this->setIsExplicitUpload($is_explicit);
 
     $can_cdn = idx($params, 'canCDN');
     if ($can_cdn) {
       $this->setCanCDN(true);
     }
 
     $builtin = idx($params, 'builtin');
     if ($builtin) {
       $this->setBuiltinName($builtin);
     }
 
     $profile = idx($params, 'profile');
     if ($profile) {
       $this->setIsProfileImage(true);
     }
 
     $mime_type = idx($params, 'mime-type');
     if ($mime_type) {
       $this->setMimeType($mime_type);
     }
 
     return $this;
   }
 
   public function getRedirectResponse() {
     $uri = $this->getBestURI();
 
     // TODO: This is a bit iffy. Sometimes, getBestURI() returns a CDN URI
     // (if the file is a viewable image) and sometimes a local URI (if not).
     // For now, just detect which one we got and configure the response
     // appropriately. In the long run, if this endpoint is served from a CDN
     // domain, we can't issue a local redirect to an info URI (which is not
     // present on the CDN domain). We probably never actually issue local
     // redirects here anyway, since we only ever transform viewable images
     // right now.
 
     $is_external = strlen(id(new PhutilURI($uri))->getDomain());
 
     return id(new AphrontRedirectResponse())
       ->setIsExternal($is_external)
       ->setURI($uri);
   }
 
+  public function attachTransforms(array $map) {
+    $this->transforms = $map;
+    return $this;
+  }
+
+  public function getTransform($key) {
+    return $this->assertAttachedKey($this->transforms, $key);
+  }
+
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorFileEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorFileTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if ($this->isBuiltin()) {
           return PhabricatorPolicies::getMostOpenPolicy();
         }
         if ($this->getIsProfileImage()) {
           return PhabricatorPolicies::getMostOpenPolicy();
         }
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_NOONE;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     $viewer_phid = $viewer->getPHID();
     if ($viewer_phid) {
       if ($this->getAuthorPHID() == $viewer_phid) {
         return true;
       }
     }
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         // If you can see the file this file is a transform of, you can see
         // this file.
         if ($this->getOriginalFile()) {
           return true;
         }
 
         // If you can see any object this file is attached to, you can see
         // the file.
         return (count($this->getObjects()) > 0);
     }
 
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     $out = array();
     $out[] = pht('The user who uploaded a file can always view and edit it.');
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         $out[] = pht(
           'Files attached to objects are visible to users who can view '.
           'those objects.');
         $out[] = pht(
           'Thumbnails are visible only to users who can view the original '.
           'file.');
         break;
     }
 
     return $out;
   }
 
 
 /* -(  PhabricatorSubscribableInterface Implementation  )-------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($this->authorPHID == $phid);
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     return array(
       $this->getAuthorPHID(),
     );
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $this->delete();
     $this->saveTransaction();
   }
 
 }
diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
index f6199c9a0..8aa22c657 100644
--- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
+++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
@@ -1,363 +1,490 @@
 <?php
 
 final class PhabricatorFileTestCase extends PhabricatorTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
+  public function testFileDirectScramble() {
+    // Changes to a file's view policy should scramble the file secret.
+
+    $engine = new PhabricatorTestStorageEngine();
+    $data = Filesystem::readRandomCharacters(64);
+
+    $author = $this->generateNewTestUser();
+
+    $params = array(
+      'name' => 'test.dat',
+      'viewPolicy' => PhabricatorPolicies::POLICY_USER,
+      'authorPHID' => $author->getPHID(),
+      'storageEngines' => array(
+        $engine,
+      ),
+    );
+
+    $file = PhabricatorFile::newFromFileData($data, $params);
+
+    $secret1 = $file->getSecretKey();
+
+    // First, change the name: this should not scramble the secret.
+    $xactions = array();
+    $xactions[] = id(new PhabricatorFileTransaction())
+      ->setTransactionType(PhabricatorFileTransaction::TYPE_NAME)
+      ->setNewValue('test.dat2');
+
+    $engine = id(new PhabricatorFileEditor())
+      ->setActor($author)
+      ->setContentSource($this->newContentSource())
+      ->applyTransactions($file, $xactions);
+
+    $file = $file->reload();
+
+    $secret2 = $file->getSecretKey();
+
+    $this->assertEqual(
+      $secret1,
+      $secret2,
+      pht('No secret scramble on non-policy edit.'));
+
+    // Now, change the view policy. This should scramble the secret.
+    $xactions = array();
+    $xactions[] = id(new PhabricatorFileTransaction())
+      ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
+      ->setNewValue($author->getPHID());
+
+    $engine = id(new PhabricatorFileEditor())
+      ->setActor($author)
+      ->setContentSource($this->newContentSource())
+      ->applyTransactions($file, $xactions);
+
+    $file = $file->reload();
+    $secret3 = $file->getSecretKey();
+
+    $this->assertTrue(
+      ($secret1 !== $secret3),
+      pht('Changing file view policy should scramble secret.'));
+  }
+
+  public function testFileIndirectScramble() {
+    // When a file is attached to an object like a task and the task view
+    // policy changes, the file secret should be scrambled. This invalidates
+    // old URIs if tasks get locked down.
+
+    $engine = new PhabricatorTestStorageEngine();
+    $data = Filesystem::readRandomCharacters(64);
+
+    $author = $this->generateNewTestUser();
+
+    $params = array(
+      'name' => 'test.dat',
+      'viewPolicy' => $author->getPHID(),
+      'authorPHID' => $author->getPHID(),
+      'storageEngines' => array(
+        $engine,
+      ),
+    );
+
+    $file = PhabricatorFile::newFromFileData($data, $params);
+    $secret1 = $file->getSecretKey();
+
+    $task = ManiphestTask::initializeNewTask($author);
+
+    $xactions = array();
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
+      ->setNewValue(pht('File Scramble Test Task'));
+
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
+      ->setNewValue('{'.$file->getMonogram().'}');
+
+    id(new ManiphestTransactionEditor())
+      ->setActor($author)
+      ->setContentSource($this->newContentSource())
+      ->applyTransactions($task, $xactions);
+
+    $file = $file->reload();
+    $secret2 = $file->getSecretKey();
+
+    $this->assertEqual(
+      $secret1,
+      $secret2,
+      pht(
+        'File policy should not scramble when attached to '.
+        'newly created object.'));
+
+    $xactions = array();
+    $xactions[] = id(new ManiphestTransaction())
+      ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
+      ->setNewValue($author->getPHID());
+
+    id(new ManiphestTransactionEditor())
+      ->setActor($author)
+      ->setContentSource($this->newContentSource())
+      ->applyTransactions($task, $xactions);
+
+    $file = $file->reload();
+    $secret3 = $file->getSecretKey();
+
+    $this->assertTrue(
+      ($secret1 !== $secret3),
+      pht('Changing attached object view policy should scramble secret.'));
+  }
+
+
   public function testFileVisibility() {
     $engine = new PhabricatorTestStorageEngine();
     $data = Filesystem::readRandomCharacters(64);
 
     $author = $this->generateNewTestUser();
     $viewer = $this->generateNewTestUser();
     $users = array($author, $viewer);
 
     $params = array(
       'name' => 'test.dat',
       'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
       'authorPHID' => $author->getPHID(),
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $file = PhabricatorFile::newFromFileData($data, $params);
     $filter = new PhabricatorPolicyFilter();
 
     // Test bare file policies.
     $this->assertEqual(
       array(
         true,
         false,
       ),
       $this->canViewFile($users, $file),
       pht('File Visibility'));
 
     // Create an object and test object policies.
 
     $object = ManiphestTask::initializeNewTask($author);
     $object->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy());
     $object->save();
 
     $this->assertTrue(
       $filter->hasCapability(
         $author,
         $object,
         PhabricatorPolicyCapability::CAN_VIEW),
       pht('Object Visible to Author'));
 
     $this->assertTrue(
       $filter->hasCapability(
         $viewer,
         $object,
         PhabricatorPolicyCapability::CAN_VIEW),
       pht('Object Visible to Others'));
 
     // Attach the file to the object and test that the association opens a
     // policy exception for the non-author viewer.
 
     $file->attachToObject($object->getPHID());
 
     // Test the attached file's visibility.
     $this->assertEqual(
       array(
         true,
         true,
       ),
       $this->canViewFile($users, $file),
       pht('Attached File Visibility'));
 
     // Create a "thumbnail" of the original file.
     $params = array(
       'name' => 'test.thumb.dat',
       'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $xform = PhabricatorFile::newFromFileData($data, $params);
 
     id(new PhabricatorTransformedFile())
       ->setOriginalPHID($file->getPHID())
       ->setTransform('test-thumb')
       ->setTransformedPHID($xform->getPHID())
       ->save();
 
     // Test the thumbnail's visibility.
     $this->assertEqual(
       array(
         true,
         true,
       ),
       $this->canViewFile($users, $xform),
       pht('Attached Thumbnail Visibility'));
 
     // Detach the object and make sure it affects the thumbnail.
     $file->detachFromObject($object->getPHID());
 
     // Test the detached thumbnail's visibility.
     $this->assertEqual(
       array(
         true,
         false,
       ),
       $this->canViewFile($users, $xform),
       pht('Detached Thumbnail Visibility'));
   }
 
   private function canViewFile(array $users, PhabricatorFile $file) {
     $results = array();
     foreach ($users as $user) {
       $results[] = (bool)id(new PhabricatorFileQuery())
         ->setViewer($user)
         ->withPHIDs(array($file->getPHID()))
         ->execute();
     }
     return $results;
   }
 
   public function testFileStorageReadWrite() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
 
     $params = array(
       'name' => 'test.dat',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $file = PhabricatorFile::newFromFileData($data, $params);
 
     // Test that the storage engine worked, and was the target of the write. We
     // don't actually care what the data is (future changes may compress or
     // encrypt it), just that it exists in the test storage engine.
     $engine->readFile($file->getStorageHandle());
 
     // Now test that we get the same data back out.
     $this->assertEqual($data, $file->loadFileData());
   }
 
   public function testFileStorageUploadDifferentFiles() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
     $other_data = Filesystem::readRandomCharacters(64);
 
     $params = array(
       'name' => 'test.dat',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $first_file = PhabricatorFile::newFromFileData($data, $params);
 
     $second_file = PhabricatorFile::newFromFileData($other_data, $params);
 
     // Test that the second file uses  different storage handle from
     // the first file.
     $first_handle = $first_file->getStorageHandle();
     $second_handle = $second_file->getStorageHandle();
 
     $this->assertTrue($first_handle != $second_handle);
   }
 
   public function testFileStorageUploadSameFile() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
 
     $params = array(
       'name' => 'test.dat',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $first_file = PhabricatorFile::newFromFileData($data, $params);
 
     $second_file = PhabricatorFile::newFromFileData($data, $params);
 
     // Test that the second file uses the same storage handle as
     // the first file.
     $handle = $first_file->getStorageHandle();
     $second_handle = $second_file->getStorageHandle();
 
     $this->assertEqual($handle, $second_handle);
   }
 
   public function testFileStorageDelete() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
 
     $params = array(
       'name' => 'test.dat',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $file = PhabricatorFile::newFromFileData($data, $params);
     $handle = $file->getStorageHandle();
     $file->delete();
 
     $caught = null;
     try {
       $engine->readFile($handle);
     } catch (Exception $ex) {
       $caught = $ex;
     }
 
     $this->assertTrue($caught instanceof Exception);
   }
 
   public function testFileStorageDeleteSharedHandle() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
 
     $params = array(
       'name' => 'test.dat',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $first_file = PhabricatorFile::newFromFileData($data, $params);
     $second_file = PhabricatorFile::newFromFileData($data, $params);
     $first_file->delete();
 
     $this->assertEqual($data, $second_file->loadFileData());
   }
 
   public function testReadWriteTtlFiles() {
     $engine = new PhabricatorTestStorageEngine();
 
     $data = Filesystem::readRandomCharacters(64);
 
     $ttl = (time() + 60 * 60 * 24);
 
     $params = array(
       'name' => 'test.dat',
       'ttl'  => ($ttl),
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $file = PhabricatorFile::newFromFileData($data, $params);
     $this->assertEqual($ttl, $file->getTTL());
   }
 
   public function testFileTransformDelete() {
     // We want to test that a file deletes all its inbound transformation
     // records and outbound transformed derivatives when it is deleted.
 
     // First, we create a chain of transforms, A -> B -> C.
 
     $engine = new PhabricatorTestStorageEngine();
 
     $params = array(
       'name' => 'test.txt',
       'storageEngines' => array(
         $engine,
       ),
     );
 
     $a = PhabricatorFile::newFromFileData('a', $params);
     $b = PhabricatorFile::newFromFileData('b', $params);
     $c = PhabricatorFile::newFromFileData('c', $params);
 
     id(new PhabricatorTransformedFile())
       ->setOriginalPHID($a->getPHID())
       ->setTransform('test:a->b')
       ->setTransformedPHID($b->getPHID())
       ->save();
 
     id(new PhabricatorTransformedFile())
       ->setOriginalPHID($b->getPHID())
       ->setTransform('test:b->c')
       ->setTransformedPHID($c->getPHID())
       ->save();
 
     // Now, verify that A -> B and B -> C exist.
 
     $xform_a = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms(
         array(
           array(
             'originalPHID' => $a->getPHID(),
             'transform'    => true,
           ),
         ))
       ->execute();
 
     $this->assertEqual(1, count($xform_a));
     $this->assertEqual($b->getPHID(), head($xform_a)->getPHID());
 
     $xform_b = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms(
         array(
           array(
             'originalPHID' => $b->getPHID(),
             'transform'    => true,
           ),
         ))
       ->execute();
 
     $this->assertEqual(1, count($xform_b));
     $this->assertEqual($c->getPHID(), head($xform_b)->getPHID());
 
     // Delete "B".
 
     $b->delete();
 
     // Now, verify that the A -> B and B -> C links are gone.
 
     $xform_a = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms(
         array(
           array(
             'originalPHID' => $a->getPHID(),
             'transform'    => true,
           ),
         ))
       ->execute();
 
     $this->assertEqual(0, count($xform_a));
 
     $xform_b = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withTransforms(
         array(
           array(
             'originalPHID' => $b->getPHID(),
             'transform'    => true,
           ),
         ))
       ->execute();
 
     $this->assertEqual(0, count($xform_b));
 
     // Also verify that C has been deleted.
 
     $alternate_c = id(new PhabricatorFileQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withPHIDs(array($c->getPHID()))
       ->execute();
 
     $this->assertEqual(array(), $alternate_c);
   }
 
 }
diff --git a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php
deleted file mode 100644
index 73b5cd8c0..000000000
--- a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-final class PhabricatorFileAccessTemporaryTokenType
-  extends PhabricatorAuthTemporaryTokenType {
-
-  const TOKENTYPE = 'file:onetime';
-
-  public function getTokenTypeDisplayName() {
-    return pht('File Access');
-  }
-
-  public function getTokenReadableTypeName(
-    PhabricatorAuthTemporaryToken $token) {
-    return pht('File Access Token');
-  }
-
-}
diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php
index 75f7d338c..354d686bd 100644
--- a/src/applications/fund/controller/FundInitiativeEditController.php
+++ b/src/applications/fund/controller/FundInitiativeEditController.php
@@ -1,249 +1,256 @@
 <?php
 
 final class FundInitiativeEditController
   extends FundController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if ($id) {
       $initiative = id(new FundInitiativeQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$initiative) {
         return new Aphront404Response();
       }
       $is_new = false;
     } else {
       $initiative = FundInitiative::initializeNewInitiative($viewer);
       $is_new = true;
     }
 
     if ($is_new) {
       $title = pht('Create Initiative');
       $button_text = pht('Create Initiative');
       $cancel_uri = $this->getApplicationURI();
+      $header_icon = 'fa-plus-square';
     } else {
       $title = pht(
-        'Edit %s %s',
-        $initiative->getMonogram(),
+        'Edit Initiative: %s',
         $initiative->getName());
       $button_text = pht('Save Changes');
       $cancel_uri = '/'.$initiative->getMonogram();
+      $header_icon = 'fa-pencil';
     }
 
     $e_name = true;
     $v_name = $initiative->getName();
 
     $e_merchant = null;
     $v_merchant = $initiative->getMerchantPHID();
 
     $v_desc = $initiative->getDescription();
     $v_risk = $initiative->getRisks();
 
     if ($is_new) {
       $v_projects = array();
     } else {
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $initiative->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
     }
 
     $validation_exception = null;
     if ($request->isFormPost()) {
       $v_name = $request->getStr('name');
       $v_desc = $request->getStr('description');
       $v_risk = $request->getStr('risks');
       $v_view = $request->getStr('viewPolicy');
       $v_edit = $request->getStr('editPolicy');
       $v_merchant = $request->getStr('merchantPHID');
       $v_projects = $request->getArr('projects');
 
       $type_name = FundInitiativeTransaction::TYPE_NAME;
       $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION;
       $type_risk = FundInitiativeTransaction::TYPE_RISKS;
       $type_merchant = FundInitiativeTransaction::TYPE_MERCHANT;
       $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
       $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
 
       $xactions = array();
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_name)
         ->setNewValue($v_name);
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_desc)
         ->setNewValue($v_desc);
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_risk)
         ->setNewValue($v_risk);
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_merchant)
         ->setNewValue($v_merchant);
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_view)
         ->setNewValue($v_view);
 
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType($type_edit)
         ->setNewValue($v_edit);
 
       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
       $xactions[] = id(new FundInitiativeTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $proj_edge_type)
         ->setNewValue(array('=' => array_fuse($v_projects)));
 
       $editor = id(new FundInitiativeEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($initiative, $xactions);
 
         return id(new AphrontRedirectResponse())
           ->setURI('/'.$initiative->getMonogram());
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
         $e_name = $ex->getShortMessage($type_name);
         $e_merchant = $ex->getShortMessage($type_merchant);
 
         $initiative->setViewPolicy($v_view);
         $initiative->setEditPolicy($v_edit);
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($initiative)
       ->execute();
 
     $merchants = id(new PhortuneMerchantQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->execute();
 
     $merchant_options = array();
     foreach ($merchants as $merchant) {
       $merchant_options[$merchant->getPHID()] = pht(
         'Merchant %d %s',
         $merchant->getID(),
         $merchant->getName());
     }
 
     if ($v_merchant && empty($merchant_options[$v_merchant])) {
       $merchant_options = array(
         $v_merchant => pht('(Restricted Merchant)'),
       ) + $merchant_options;
     }
 
     if (!$merchant_options) {
       return $this->newDialog()
         ->setTitle(pht('No Valid Phortune Merchant Accounts'))
         ->appendParagraph(
           pht(
             'You do not control any merchant accounts which can receive '.
             'payments from this initiative. When you create an initiative, '.
             'you need to specify a merchant account where funds will be paid '.
             'to.'))
         ->appendParagraph(
           pht(
             'Create a merchant account in the Phortune application before '.
             'creating an initiative in Fund.'))
         ->addCancelButton($this->getApplicationURI());
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setName('name')
           ->setLabel(pht('Name'))
           ->setValue($v_name)
           ->setError($e_name))
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setName('merchantPHID')
           ->setLabel(pht('Pay To Merchant'))
           ->setValue($v_merchant)
           ->setError($e_merchant)
           ->setOptions($merchant_options))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setUser($viewer)
           ->setName('description')
           ->setLabel(pht('Description'))
           ->setValue($v_desc))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setUser($viewer)
           ->setName('risks')
           ->setLabel(pht('Risks/Challenges'))
           ->setValue($v_risk))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Projects'))
           ->setName('projects')
           ->setValue($v_projects)
           ->setDatasource(new PhabricatorProjectDatasource()))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($initiative)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($initiative)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue($button_text)
           ->addCancelButton($cancel_uri));
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($is_new) {
       $crumbs->addTextCrumb(pht('Create Initiative'));
     } else {
       $crumbs->addTextCrumb(
         $initiative->getMonogram(),
         '/'.$initiative->getMonogram());
       $crumbs->addTextCrumb(pht('Edit'));
     }
+    $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
       ->setValidationException($validation_exception)
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Initiative'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php
index 18b123bf3..4960e7aa3 100644
--- a/src/applications/fund/controller/FundInitiativeViewController.php
+++ b/src/applications/fund/controller/FundInitiativeViewController.php
@@ -1,190 +1,190 @@
 <?php
 
 final class FundInitiativeViewController
   extends FundController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $initiative = id(new FundInitiativeQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$initiative) {
       return new Aphront404Response();
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($initiative->getMonogram());
     $crumbs->setBorder(true);
 
     $title = pht(
       '%s %s',
       $initiative->getMonogram(),
       $initiative->getName());
 
     if ($initiative->isClosed()) {
       $status_icon = 'fa-times';
       $status_color = 'bluegrey';
     } else {
       $status_icon = 'fa-check';
       $status_color = 'bluegrey';
     }
     $status_name = idx(
       FundInitiative::getStatusNameMap(),
       $initiative->getStatus());
 
     $header = id(new PHUIHeaderView())
       ->setHeader($initiative->getName())
       ->setUser($viewer)
       ->setPolicyObject($initiative)
       ->setStatus($status_icon, $status_color, $status_name)
       ->setHeaderIcon('fa-heart');
 
     $curtain = $this->buildCurtain($initiative);
     $details = $this->buildPropertySectionView($initiative);
 
     $timeline = $this->buildTransactionTimeline(
       $initiative,
       new FundInitiativeTransactionQuery());
 
     $add_comment = $this->buildCommentForm($initiative);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $timeline,
         $add_comment,
       ))
-      ->addPropertySection(pht('DETAILS'), $details);
+      ->addPropertySection(pht('Details'), $details);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($initiative->getPHID()))
       ->appendChild($view);
   }
 
   private function buildPropertySectionView(FundInitiative $initiative) {
     $viewer = $this->getRequest()->getUser();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $owner_phid = $initiative->getOwnerPHID();
     $merchant_phid = $initiative->getMerchantPHID();
 
     $view->addProperty(
       pht('Owner'),
       $viewer->renderHandle($owner_phid));
 
     $view->addProperty(
       pht('Payable to Merchant'),
       $viewer->renderHandle($merchant_phid));
 
     $view->addProperty(
       pht('Total Funding'),
       $initiative->getTotalAsCurrency()->formatForDisplay());
 
     $description = $initiative->getDescription();
     if (strlen($description)) {
       $description = new PHUIRemarkupView($viewer, $description);
       $view->addSectionHeader(
         pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
       $view->addTextContent($description);
     }
 
     $risks = $initiative->getRisks();
     if (strlen($risks)) {
       $risks = new PHUIRemarkupView($viewer, $risks);
       $view->addSectionHeader(
         pht('Risks/Challenges'), 'fa-ambulance');
       $view->addTextContent($risks);
     }
 
     return $view;
   }
 
   private function buildCurtain(FundInitiative $initiative) {
     $viewer = $this->getViewer();
 
     $id = $initiative->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $initiative,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($initiative);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Initiative'))
         ->setIcon('fa-pencil')
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit)
         ->setHref($this->getApplicationURI("/edit/{$id}/")));
 
     if ($initiative->isClosed()) {
       $close_name = pht('Reopen Initiative');
       $close_icon = 'fa-check';
     } else {
       $close_name = pht('Close Initiative');
       $close_icon = 'fa-times';
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($close_name)
         ->setIcon($close_icon)
         ->setDisabled(!$can_edit)
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI("/close/{$id}/")));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Back Initiative'))
         ->setIcon('fa-money')
         ->setDisabled($initiative->isClosed())
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI("/back/{$id}/")));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View Backers'))
         ->setIcon('fa-bank')
         ->setHref($this->getApplicationURI("/backers/{$id}/")));
 
     return $curtain;
   }
 
   private function buildCommentForm(FundInitiative $initiative) {
     $viewer = $this->getViewer();
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     $add_comment_header = $is_serious
       ? pht('Add Comment')
       : pht('Add Liquidity');
 
     $draft = PhabricatorDraft::newFromUserAndKey(
       $viewer, $initiative->getPHID());
 
     return id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($initiative->getPHID())
       ->setDraft($draft)
       ->setHeaderText($add_comment_header)
       ->setAction(
         $this->getApplicationURI('/comment/'.$initiative->getID().'/'))
       ->setSubmitButtonName(pht('Add Comment'));
   }
 
 
 }
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
index 320e94d64..6b0e7c1e0 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -1,643 +1,643 @@
 <?php
 
 final class HarbormasterBuildViewController
   extends HarbormasterController {
 
   public function handleRequest(AphrontRequest $request) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $id = $request->getURIData('id');
     $generation = $request->getInt('g');
 
     $build = id(new HarbormasterBuildQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$build) {
       return new Aphront404Response();
     }
 
     require_celerity_resource('harbormaster-css');
 
     $title = pht('Build %d', $id);
 
     $page_header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
       ->setPolicyObject($build)
       ->setHeaderIcon('fa-cubes');
 
     if ($build->isRestarting()) {
       $page_header->setStatus(
         'fa-exclamation-triangle', 'red', pht('Restarting'));
     } else if ($build->isPausing()) {
       $page_header->setStatus(
         'fa-exclamation-triangle', 'red', pht('Pausing'));
     } else if ($build->isResuming()) {
       $page_header->setStatus(
         'fa-exclamation-triangle', 'red', pht('Resuming'));
     } else if ($build->isAborting()) {
       $page_header->setStatus(
         'fa-exclamation-triangle', 'red', pht('Aborting'));
     }
 
     $curtain = $this->buildCurtainView($build);
     $properties = $this->buildPropertyList($build);
 
     $crumbs = $this->buildApplicationCrumbs();
     $this->addBuildableCrumb($crumbs, $build->getBuildable());
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     if ($generation === null || $generation > $build->getBuildGeneration() ||
       $generation < 0) {
       $generation = $build->getBuildGeneration();
     }
 
     $build_targets = id(new HarbormasterBuildTargetQuery())
       ->setViewer($viewer)
       ->needBuildSteps(true)
       ->withBuildPHIDs(array($build->getPHID()))
       ->withBuildGenerations(array($generation))
       ->execute();
 
     if ($build_targets) {
       $messages = id(new HarbormasterBuildMessageQuery())
         ->setViewer($viewer)
         ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
         ->execute();
       $messages = mgroup($messages, 'getBuildTargetPHID');
     } else {
       $messages = array();
     }
 
     if ($build_targets) {
       $artifacts = id(new HarbormasterBuildArtifactQuery())
         ->setViewer($viewer)
         ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
         ->execute();
       $artifacts = msort($artifacts, 'getArtifactKey');
       $artifacts = mgroup($artifacts, 'getBuildTargetPHID');
     } else {
       $artifacts = array();
     }
 
 
     $targets = array();
     foreach ($build_targets as $build_target) {
       $header = id(new PHUIHeaderView())
         ->setHeader($build_target->getName())
         ->setUser($viewer)
         ->setHeaderIcon('fa-bullseye');
 
       $target_box = id(new PHUIObjectBoxView())
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->setHeader($header);
 
       $property_list = new PHUIPropertyListView();
 
       $target_artifacts = idx($artifacts, $build_target->getPHID(), array());
 
       $links = array();
       $type_uri = HarbormasterURIArtifact::ARTIFACTCONST;
       foreach ($target_artifacts as $artifact) {
         if ($artifact->getArtifactType() == $type_uri) {
           $impl = $artifact->getArtifactImplementation();
           if ($impl->isExternalLink()) {
             $links[] = $impl->renderLink();
           }
         }
       }
 
       if ($links) {
         $links = phutil_implode_html(phutil_tag('br'), $links);
         $property_list->addProperty(
           pht('External Link'),
           $links);
       }
 
       $status_view = new PHUIStatusListView();
       $item = new PHUIStatusItemView();
 
       $status = $build_target->getTargetStatus();
       $status_name =
         HarbormasterBuildTarget::getBuildTargetStatusName($status);
       $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
       $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
 
       $item->setTarget($status_name);
       $item->setIcon($icon, $color);
       $status_view->addItem($item);
 
       $when = array();
       $started = $build_target->getDateStarted();
       $now = PhabricatorTime::getNow();
       if ($started) {
         $ended = $build_target->getDateCompleted();
         if ($ended) {
           $when[] = pht(
             'Completed at %s',
             phabricator_datetime($started, $viewer));
 
           $duration = ($ended - $started);
           if ($duration) {
             $when[] = pht(
               'Built for %s',
               phutil_format_relative_time_detailed($duration));
           } else {
             $when[] = pht('Built instantly');
           }
         } else {
           $when[] = pht(
             'Started at %s',
             phabricator_datetime($started, $viewer));
           $duration = ($now - $started);
           if ($duration) {
             $when[] = pht(
               'Running for %s',
               phutil_format_relative_time_detailed($duration));
           }
         }
       } else {
         $created = $build_target->getDateCreated();
         $when[] = pht(
           'Queued at %s',
           phabricator_datetime($started, $viewer));
         $duration = ($now - $created);
         if ($duration) {
           $when[] = pht(
             'Waiting for %s',
             phutil_format_relative_time_detailed($duration));
         }
       }
 
       $property_list->addProperty(
         pht('When'),
         phutil_implode_html(" \xC2\xB7 ", $when));
 
       $property_list->addProperty(pht('Status'), $status_view);
 
       $target_box->addPropertyList($property_list, pht('Overview'));
 
       $step = $build_target->getBuildStep();
 
       if ($step) {
         $description = $step->getDescription();
         if ($description) {
           $description = new PHUIRemarkupView($viewer, $description);
           $property_list->addSectionHeader(
             pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
           $property_list->addTextContent($description);
         }
       } else {
         $target_box->setFormErrors(
           array(
             pht(
               'This build step has since been deleted on the build plan.  '.
               'Some information may be omitted.'),
           ));
       }
 
       $details = $build_target->getDetails();
       $property_list = new PHUIPropertyListView();
       foreach ($details as $key => $value) {
         $property_list->addProperty($key, $value);
       }
       $target_box->addPropertyList($property_list, pht('Configuration'));
 
       $variables = $build_target->getVariables();
       $property_list = new PHUIPropertyListView();
       $property_list->addRawContent($this->buildProperties($variables));
       $target_box->addPropertyList($property_list, pht('Variables'));
 
       $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
       $property_list = new PHUIPropertyListView();
       $property_list->addRawContent($artifacts_tab);
       $target_box->addPropertyList($property_list, pht('Artifacts'));
 
       $build_messages = idx($messages, $build_target->getPHID(), array());
       $property_list = new PHUIPropertyListView();
       $property_list->addRawContent($this->buildMessages($build_messages));
       $target_box->addPropertyList($property_list, pht('Messages'));
 
       $property_list = new PHUIPropertyListView();
       $property_list->addProperty(
         pht('Build Target ID'),
         $build_target->getID());
       $property_list->addProperty(
         pht('Build Target PHID'),
         $build_target->getPHID());
       $target_box->addPropertyList($property_list, pht('Metadata'));
 
       $targets[] = $target_box;
 
       $targets[] = $this->buildLog($build, $build_target);
     }
 
     $timeline = $this->buildTransactionTimeline(
       $build,
       new HarbormasterBuildTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($page_header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $properties,
         $targets,
         $timeline,
       ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
 
   }
 
   private function buildArtifacts(
     HarbormasterBuildTarget $build_target,
     array $artifacts) {
     $viewer = $this->getViewer();
 
     $rows = array();
     foreach ($artifacts as $artifact) {
       $impl = $artifact->getArtifactImplementation();
 
       if ($impl) {
         $summary = $impl->renderArtifactSummary($viewer);
         $type_name = $impl->getArtifactTypeName();
       } else {
         $summary = pht('<Unknown Artifact Type>');
         $type_name = $artifact->getType();
       }
 
       $rows[] = array(
         $artifact->getArtifactKey(),
         $type_name,
         $summary,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setNoDataString(pht('This target has no associated artifacts.'))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Type'),
           pht('Summary'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           '',
           'wide',
         ));
 
     return $table;
   }
 
   private function buildLog(
     HarbormasterBuild $build,
     HarbormasterBuildTarget $build_target) {
 
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $limit = $request->getInt('l', 25);
 
     $logs = id(new HarbormasterBuildLogQuery())
       ->setViewer($viewer)
       ->withBuildTargetPHIDs(array($build_target->getPHID()))
       ->execute();
 
     $empty_logs = array();
 
     $log_boxes = array();
     foreach ($logs as $log) {
       $start = 1;
       $lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
       if ($limit !== 0) {
         $start = count($lines) - $limit;
         if ($start >= 1) {
           $lines = array_slice($lines, -$limit, $limit);
         } else {
           $start = 1;
         }
       }
 
       $id = null;
       $is_empty = false;
       if (count($lines) === 1 && trim($lines[0]) === '') {
         // Prevent Harbormaster from showing empty build logs.
         $id = celerity_generate_unique_node_id();
         $empty_logs[] = $id;
         $is_empty = true;
       }
 
       $log_view = new ShellLogView();
       $log_view->setLines($lines);
       $log_view->setStart($start);
 
       $header = id(new PHUIHeaderView())
         ->setHeader(pht(
           'Build Log %d (%s - %s)',
           $log->getID(),
           $log->getLogSource(),
           $log->getLogType()))
         ->setSubheader($this->createLogHeader($build, $log))
         ->setUser($viewer);
 
       $log_box = id(new PHUIObjectBoxView())
         ->setHeader($header)
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->setForm($log_view);
 
       if ($is_empty) {
         $log_box = phutil_tag(
           'div',
           array(
             'style' => 'display: none',
             'id' => $id,
           ),
           $log_box);
       }
 
       $log_boxes[] = $log_box;
     }
 
     if ($empty_logs) {
       $hide_id = celerity_generate_unique_node_id();
 
       Javelin::initBehavior('phabricator-reveal-content');
 
       $expand = phutil_tag(
         'div',
         array(
           'id' => $hide_id,
           'class' => 'harbormaster-empty-logs-are-hidden',
         ),
         array(
           pht(
             '%s empty logs are hidden.',
             phutil_count($empty_logs)),
           ' ',
           javelin_tag(
             'a',
             array(
               'href' => '#',
               'sigil' => 'reveal-content',
               'meta' => array(
                 'showIDs' => $empty_logs,
                 'hideIDs' => array($hide_id),
               ),
             ),
             pht('Show all logs.')),
         ));
 
       array_unshift($log_boxes, $expand);
     }
 
     return $log_boxes;
   }
 
   private function createLogHeader($build, $log) {
     $request = $this->getRequest();
     $limit = $request->getInt('l', 25);
 
     $lines_25 = $this->getApplicationURI('/build/'.$build->getID().'/?l=25');
     $lines_50 = $this->getApplicationURI('/build/'.$build->getID().'/?l=50');
     $lines_100 =
       $this->getApplicationURI('/build/'.$build->getID().'/?l=100');
     $lines_0 = $this->getApplicationURI('/build/'.$build->getID().'/?l=0');
 
     $link_25 = phutil_tag('a', array('href' => $lines_25), pht('25'));
     $link_50 = phutil_tag('a', array('href' => $lines_50), pht('50'));
     $link_100 = phutil_tag('a', array('href' => $lines_100), pht('100'));
     $link_0 = phutil_tag('a', array('href' => $lines_0), pht('Unlimited'));
 
     if ($limit === 25) {
       $link_25 = phutil_tag('strong', array(), $link_25);
     } else if ($limit === 50) {
       $link_50 = phutil_tag('strong', array(), $link_50);
     } else if ($limit === 100) {
       $link_100 = phutil_tag('strong', array(), $link_100);
     } else if ($limit === 0) {
       $link_0 = phutil_tag('strong', array(), $link_0);
     }
 
     return phutil_tag(
       'span',
       array(),
       array(
         $link_25,
         ' - ',
         $link_50,
         ' - ',
         $link_100,
         ' - ',
         $link_0,
         ' Lines',
       ));
   }
 
   private function buildCurtainView(HarbormasterBuild $build) {
     $viewer = $this->getViewer();
     $id = $build->getID();
 
     $curtain = $this->newCurtainView($build);
 
     $can_restart =
       $build->canRestartBuild() &&
       $build->canIssueCommand(
         $viewer,
         HarbormasterBuildCommand::COMMAND_RESTART);
 
     $can_pause =
       $build->canPauseBuild() &&
       $build->canIssueCommand(
         $viewer,
         HarbormasterBuildCommand::COMMAND_PAUSE);
 
     $can_resume =
       $build->canResumeBuild() &&
       $build->canIssueCommand(
         $viewer,
         HarbormasterBuildCommand::COMMAND_RESUME);
 
     $can_abort =
       $build->canAbortBuild() &&
       $build->canIssueCommand(
         $viewer,
         HarbormasterBuildCommand::COMMAND_ABORT);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Restart Build'))
         ->setIcon('fa-repeat')
         ->setHref($this->getApplicationURI('/build/restart/'.$id.'/'))
         ->setDisabled(!$can_restart)
         ->setWorkflow(true));
 
     if ($build->canResumeBuild()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Resume Build'))
           ->setIcon('fa-play')
           ->setHref($this->getApplicationURI('/build/resume/'.$id.'/'))
           ->setDisabled(!$can_resume)
           ->setWorkflow(true));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Pause Build'))
           ->setIcon('fa-pause')
           ->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
           ->setDisabled(!$can_pause)
           ->setWorkflow(true));
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Abort Build'))
         ->setIcon('fa-exclamation-triangle')
         ->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
         ->setDisabled(!$can_abort)
         ->setWorkflow(true));
 
     return $curtain;
   }
 
   private function buildPropertyList(HarbormasterBuild $build) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $handles = id(new PhabricatorHandleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array(
         $build->getBuildablePHID(),
         $build->getBuildPlanPHID(),
       ))
       ->execute();
 
     $properties->addProperty(
       pht('Buildable'),
       $handles[$build->getBuildablePHID()]->renderLink());
 
     $properties->addProperty(
       pht('Build Plan'),
       $handles[$build->getBuildPlanPHID()]->renderLink());
 
     $properties->addProperty(
       pht('Restarts'),
       $build->getBuildGeneration());
 
     $properties->addProperty(
       pht('Status'),
       $this->getStatus($build));
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('PROPERTIES'))
+      ->setHeaderText(pht('Properties'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
 
   }
 
   private function getStatus(HarbormasterBuild $build) {
     $status_view = new PHUIStatusListView();
 
     $item = new PHUIStatusItemView();
 
     if ($build->isPausing()) {
       $status_name = pht('Pausing');
       $icon = PHUIStatusItemView::ICON_RIGHT;
       $color = 'dark';
     } else {
       $status = $build->getBuildStatus();
       $status_name =
         HarbormasterBuild::getBuildStatusName($status);
       $icon = HarbormasterBuild::getBuildStatusIcon($status);
       $color = HarbormasterBuild::getBuildStatusColor($status);
     }
 
     $item->setTarget($status_name);
     $item->setIcon($icon, $color);
     $status_view->addItem($item);
 
     return $status_view;
   }
 
   private function buildMessages(array $messages) {
     $viewer = $this->getRequest()->getUser();
 
     if ($messages) {
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs(mpull($messages, 'getAuthorPHID'))
         ->execute();
     } else {
       $handles = array();
     }
 
     $rows = array();
     foreach ($messages as $message) {
       $rows[] = array(
         $message->getID(),
         $handles[$message->getAuthorPHID()]->renderLink(),
         $message->getType(),
         $message->getIsConsumed() ? pht('Consumed') : null,
         phabricator_datetime($message->getDateCreated(), $viewer),
       );
     }
 
     $table = new AphrontTableView($rows);
     $table->setNoDataString(pht('No messages for this build target.'));
     $table->setHeaders(
       array(
         pht('ID'),
         pht('From'),
         pht('Type'),
         pht('Consumed'),
         pht('Received'),
       ));
     $table->setColumnClasses(
       array(
         '',
         '',
         'wide',
         '',
         'date',
       ));
 
     return $table;
   }
 
   private function buildProperties(array $properties) {
     ksort($properties);
 
     $rows = array();
     foreach ($properties as $key => $value) {
       $rows[] = array(
         $key,
         $value,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Value'),
         ))
       ->setColumnClasses(
         array(
           'pri right',
           'wide',
         ));
 
     return $table;
   }
 
 }
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
index fe124367d..390ff2ec3 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -1,352 +1,352 @@
 <?php
 
 final class HarbormasterBuildableViewController
   extends HarbormasterController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $buildable = id(new HarbormasterBuildableQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$buildable) {
       return new Aphront404Response();
     }
 
     $id = $buildable->getID();
 
     // Pull builds and build targets.
     $builds = id(new HarbormasterBuildQuery())
       ->setViewer($viewer)
       ->withBuildablePHIDs(array($buildable->getPHID()))
       ->needBuildTargets(true)
       ->execute();
 
     list($lint, $unit) = $this->renderLintAndUnit($buildable, $builds);
 
     $buildable->attachBuilds($builds);
     $object = $buildable->getBuildableObject();
 
     $build_list = $this->buildBuildList($buildable);
 
     $title = pht('Buildable %d', $id);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
       ->setPolicyObject($buildable)
       ->setHeaderIcon('fa-recycle');
 
     $timeline = $this->buildTransactionTimeline(
       $buildable,
       new HarbormasterBuildableTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $curtain = $this->buildCurtainView($buildable);
     $properties = $this->buildPropertyList($buildable);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($buildable->getMonogram());
     $crumbs->setBorder(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $properties,
         $lint,
         $unit,
         $build_list,
         $timeline,
       ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
 
   }
 
   private function buildCurtainView(HarbormasterBuildable $buildable) {
     $viewer = $this->getViewer();
     $id = $buildable->getID();
 
     $curtain = $this->newCurtainView($buildable);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $buildable,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $can_restart = false;
     $can_resume = false;
     $can_pause = false;
     $can_abort = false;
 
     $command_restart = HarbormasterBuildCommand::COMMAND_RESTART;
     $command_resume = HarbormasterBuildCommand::COMMAND_RESUME;
     $command_pause = HarbormasterBuildCommand::COMMAND_PAUSE;
     $command_abort = HarbormasterBuildCommand::COMMAND_ABORT;
 
     foreach ($buildable->getBuilds() as $build) {
       if ($build->canRestartBuild()) {
         if ($build->canIssueCommand($viewer, $command_restart)) {
           $can_restart = true;
         }
       }
       if ($build->canResumeBuild()) {
         if ($build->canIssueCommand($viewer, $command_resume)) {
           $can_resume = true;
         }
       }
       if ($build->canPauseBuild()) {
         if ($build->canIssueCommand($viewer, $command_pause)) {
           $can_pause = true;
         }
       }
       if ($build->canAbortBuild()) {
         if ($build->canIssueCommand($viewer, $command_abort)) {
           $can_abort = true;
         }
       }
     }
 
     $restart_uri = "buildable/{$id}/restart/";
     $pause_uri = "buildable/{$id}/pause/";
     $resume_uri = "buildable/{$id}/resume/";
     $abort_uri = "buildable/{$id}/abort/";
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-repeat')
         ->setName(pht('Restart All Builds'))
         ->setHref($this->getApplicationURI($restart_uri))
         ->setWorkflow(true)
         ->setDisabled(!$can_restart || !$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pause')
         ->setName(pht('Pause All Builds'))
         ->setHref($this->getApplicationURI($pause_uri))
         ->setWorkflow(true)
         ->setDisabled(!$can_pause || !$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-play')
         ->setName(pht('Resume All Builds'))
         ->setHref($this->getApplicationURI($resume_uri))
         ->setWorkflow(true)
         ->setDisabled(!$can_resume || !$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-exclamation-triangle')
         ->setName(pht('Abort All Builds'))
         ->setHref($this->getApplicationURI($abort_uri))
         ->setWorkflow(true)
         ->setDisabled(!$can_abort || !$can_edit));
 
     return $curtain;
   }
 
   private function buildPropertyList(HarbormasterBuildable $buildable) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $container_phid = $buildable->getContainerPHID();
     $buildable_phid = $buildable->getBuildablePHID();
 
     if ($container_phid) {
       $properties->addProperty(
         pht('Container'),
         $viewer->renderHandle($container_phid));
     }
 
     $properties->addProperty(
       pht('Buildable'),
       $viewer->renderHandle($buildable_phid));
 
     $properties->addProperty(
       pht('Origin'),
       $buildable->getIsManualBuildable()
         ? pht('Manual Buildable')
         : pht('Automatic Buildable'));
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('PROPERTIES'))
+      ->setHeaderText(pht('Properties'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
   }
 
   private function buildBuildList(HarbormasterBuildable $buildable) {
     $viewer = $this->getRequest()->getUser();
 
     $build_list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
     foreach ($buildable->getBuilds() as $build) {
       $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/');
       $item = id(new PHUIObjectItemView())
         ->setObjectName(pht('Build %d', $build->getID()))
         ->setHeader($build->getName())
         ->setHref($view_uri);
 
       $status = $build->getBuildStatus();
       $item->setStatusIcon(
         'fa-dot-circle-o '.HarbormasterBuild::getBuildStatusColor($status),
         HarbormasterBuild::getBuildStatusName($status));
 
       $item->addAttribute(HarbormasterBuild::getBuildStatusName($status));
 
       if ($build->isRestarting()) {
         $item->addIcon('fa-repeat', pht('Restarting'));
       } else if ($build->isPausing()) {
         $item->addIcon('fa-pause', pht('Pausing'));
       } else if ($build->isResuming()) {
         $item->addIcon('fa-play', pht('Resuming'));
       }
 
       $build_id = $build->getID();
 
       $restart_uri = "build/restart/{$build_id}/buildable/";
       $resume_uri = "build/resume/{$build_id}/buildable/";
       $pause_uri = "build/pause/{$build_id}/buildable/";
       $abort_uri = "build/abort/{$build_id}/buildable/";
 
       $item->addAction(
         id(new PHUIListItemView())
           ->setIcon('fa-repeat')
           ->setName(pht('Restart'))
           ->setHref($this->getApplicationURI($restart_uri))
           ->setWorkflow(true)
           ->setDisabled(!$build->canRestartBuild()));
 
       if ($build->canResumeBuild()) {
         $item->addAction(
           id(new PHUIListItemView())
             ->setIcon('fa-play')
             ->setName(pht('Resume'))
             ->setHref($this->getApplicationURI($resume_uri))
             ->setWorkflow(true));
       } else {
         $item->addAction(
           id(new PHUIListItemView())
             ->setIcon('fa-pause')
             ->setName(pht('Pause'))
             ->setHref($this->getApplicationURI($pause_uri))
             ->setWorkflow(true)
             ->setDisabled(!$build->canPauseBuild()));
       }
 
       $targets = $build->getBuildTargets();
 
       if ($targets) {
         $target_list = id(new PHUIStatusListView());
         foreach ($targets as $target) {
           $status = $target->getTargetStatus();
           $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
           $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
           $status_name =
             HarbormasterBuildTarget::getBuildTargetStatusName($status);
 
           $name = $target->getName();
 
           $target_list->addItem(
             id(new PHUIStatusItemView())
               ->setIcon($icon, $color, $status_name)
               ->setTarget(pht('Target %d', $target->getID()))
               ->setNote($name));
         }
 
         $target_box = id(new PHUIBoxView())
           ->addPadding(PHUI::PADDING_SMALL)
           ->appendChild($target_list);
 
         $item->appendChild($target_box);
       }
 
       $build_list->addItem($item);
     }
 
     $build_list->setFlush(true);
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Builds'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($build_list);
 
     return $box;
   }
 
   private function renderLintAndUnit(
     HarbormasterBuildable $buildable,
     array $builds) {
 
     $viewer = $this->getViewer();
 
     $targets = array();
     foreach ($builds as $build) {
       foreach ($build->getBuildTargets() as $target) {
         $targets[] = $target;
       }
     }
 
     if (!$targets) {
       return;
     }
 
     $target_phids = mpull($targets, 'getPHID');
 
     $lint_data = id(new HarbormasterBuildLintMessage())->loadAllWhere(
       'buildTargetPHID IN (%Ls)',
       $target_phids);
 
     $unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
       'buildTargetPHID IN (%Ls)',
       $target_phids);
 
     if ($lint_data) {
       $lint_table = id(new HarbormasterLintPropertyView())
         ->setUser($viewer)
         ->setLimit(10)
         ->setLintMessages($lint_data);
 
       $lint_href = $this->getApplicationURI('lint/'.$buildable->getID().'/');
 
       $lint_header = id(new PHUIHeaderView())
         ->setHeader(pht('Lint Messages'))
         ->addActionLink(
           id(new PHUIButtonView())
             ->setTag('a')
             ->setHref($lint_href)
             ->setIcon('fa-list-ul')
             ->setText('View All'));
 
       $lint = id(new PHUIObjectBoxView())
         ->setHeader($lint_header)
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->setTable($lint_table);
     } else {
       $lint = null;
     }
 
     if ($unit_data) {
       $unit = id(new HarbormasterUnitSummaryView())
         ->setBuildable($buildable)
         ->setUnitMessages($unit_data)
         ->setShowViewAll(true)
         ->setLimit(5);
     } else {
       $unit = null;
     }
 
     return array($lint, $unit);
   }
 
 
 
 }
diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php
index 07be537fa..a404b48e8 100644
--- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php
@@ -1,144 +1,144 @@
 <?php
 
 final class HarbormasterStepViewController
   extends HarbormasterPlanController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $step = id(new HarbormasterBuildStepQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$step) {
       return new Aphront404Response();
     }
     $plan = $step->getBuildPlan();
 
     $plan_id = $plan->getID();
     $plan_uri = $this->getApplicationURI("plan/{$plan_id}/");
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $step,
       PhabricatorCustomField::ROLE_VIEW);
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($step);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Plan %d', $plan_id), $plan_uri);
     $crumbs->addTextCrumb(pht('Step %d', $id));
     $crumbs->setBorder(true);
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Build Step %d: %s', $id, $step->getName()))
       ->setHeaderIcon('fa-chevron-circle-right');
 
     $properties = $this->buildPropertyList($step, $field_list);
     $curtain = $this->buildCurtainView($step);
 
     $timeline = $this->buildTransactionTimeline(
       $step,
       new HarbormasterBuildStepTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $properties,
         $timeline,
       ));
 
     return $this->newPage()
       ->setTitle(pht('Step %d', $id))
       ->setCrumbs($crumbs)
       ->appendChild($view);
 
   }
 
   private function buildPropertyList(
     HarbormasterBuildStep $step,
     PhabricatorCustomFieldList $field_list) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     try {
       $implementation = $step->getStepImplementation();
     } catch (Exception $ex) {
       $implementation = null;
     }
 
     if ($implementation) {
       $type = $implementation->getName();
     } else {
       $type = phutil_tag(
         'em',
         array(),
         pht(
           'Invalid Implementation ("%s")!',
           $step->getClassName()));
     }
 
     $view->addProperty(pht('Step Type'), $type);
 
     $view->addProperty(
       pht('Created'),
       phabricator_datetime($step->getDateCreated(), $viewer));
 
     $field_list->appendFieldsToPropertyList(
       $step,
       $viewer,
       $view);
 
     $description = $step->getDescription();
     if (strlen($description)) {
       $view->addSectionHeader(
         pht('Description'),
         PHUIPropertyListView::ICON_SUMMARY);
       $view->addTextContent(
         new PHUIRemarkupView($viewer, $description));
     }
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('PROPERTIES'))
+      ->setHeaderText(pht('Properties'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($view);
   }
 
 
   private function buildCurtainView(HarbormasterBuildStep $step) {
     $viewer = $this->getViewer();
     $id = $step->getID();
 
     $curtain = $this->newCurtainView($step);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $step,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Step'))
         ->setHref($this->getApplicationURI("step/edit/{$id}/"))
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit)
         ->setIcon('fa-pencil'));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Delete Step'))
         ->setHref($this->getApplicationURI("step/delete/{$id}/"))
         ->setWorkflow(true)
         ->setDisabled(!$can_edit)
         ->setIcon('fa-times'));
 
     return $curtain;
   }
 
 
 }
diff --git a/src/applications/help/controller/PhabricatorHelpDocumentationController.php b/src/applications/help/controller/PhabricatorHelpDocumentationController.php
index ce4dc38f4..4983520ba 100644
--- a/src/applications/help/controller/PhabricatorHelpDocumentationController.php
+++ b/src/applications/help/controller/PhabricatorHelpDocumentationController.php
@@ -1,52 +1,48 @@
 <?php
 
 final class PhabricatorHelpDocumentationController
   extends PhabricatorHelpController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $application_class = $request->getURIData('application');
     $application = id(new PhabricatorApplicationQuery())
       ->setViewer($viewer)
       ->withClasses(array($application_class))
       ->executeOne();
     if (!$application) {
       return new Aphront404Response();
     }
 
     $items = $application->getHelpMenuItems($viewer);
     $title = pht('%s Help', $application->getName());
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
     foreach ($items as $item) {
       if ($item->getType() == PHUIListItemView::TYPE_LABEL) {
         continue;
       }
       $list->addItem(
         id(new PHUIObjectItemView())
           ->setHeader($item->getName())
           ->setWorkflow($item->getWorkflow())
           ->setHref($item->getHref()));
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $list,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($list);
   }
 
 
 }
diff --git a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php
index 010e41ffc..22ad0f8d2 100644
--- a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php
+++ b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php
@@ -1,51 +1,47 @@
 <?php
 
 final class PhabricatorHelpEditorProtocolController
   extends PhabricatorHelpController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setMethod('GET')
       ->setSubmitURI('/settings/panel/display/')
       ->setTitle(pht('Unsupported Editor Protocol'))
       ->appendParagraph(
         pht(
           'Your configured editor URI uses an unsupported protocol. Change '.
           'your settings to use a supported protocol, or ask your '.
           'administrator to add support for the chosen protocol by '.
           'configuring: %s',
           phutil_tag('tt', array(), 'uri.allowed-editor-protocols')))
       ->addSubmitButton(pht('Change Settings'))
       ->addCancelButton('/');
-
-    return id(new AphrontDialogResponse())
-      ->setDialog($dialog);
   }
 
   public static function hasAllowedProtocol($uri) {
     $uri = new PhutilURI($uri);
     $editor_protocol = $uri->getProtocol();
     if (!$editor_protocol) {
       // The URI must have a protocol.
       return false;
     }
 
     $allowed_key = 'uri.allowed-editor-protocols';
     $allowed_protocols = PhabricatorEnv::getEnvConfig($allowed_key);
     if (empty($allowed_protocols[$editor_protocol])) {
       // The protocol must be on the allowed protocol whitelist.
       return false;
     }
 
     return true;
   }
 
 
 }
diff --git a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php
index b136265d7..80bd259c4 100644
--- a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php
+++ b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php
@@ -1,70 +1,67 @@
 <?php
 
 final class PhabricatorHelpKeyboardShortcutController
   extends PhabricatorHelpController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $keys = $request->getStr('keys');
     try {
       $keys = phutil_json_decode($keys);
     } catch (PhutilJSONParserException $ex) {
       return new Aphront400Response();
     }
 
     // There have been at least two users asking for a keyboard shortcut to
     // close the dialog, so be explicit that escape works since it isn't
     // terribly discoverable.
     $keys[] = array(
       'keys'        => array('esc'),
       'description' => pht('Close any dialog, including this one.'),
     );
 
     $stroke_map = array(
       'left' => "\xE2\x86\x90",
       'right' => "\xE2\x86\x92",
       'up' => "\xE2\x86\x91",
       'down' => "\xE2\x86\x93",
       'return' => "\xE2\x8F\x8E",
       'tab' => "\xE2\x87\xA5",
       'delete' => "\xE2\x8C\xAB",
     );
 
     $rows = array();
     foreach ($keys as $shortcut) {
       $keystrokes = array();
       foreach ($shortcut['keys'] as $stroke) {
         $stroke = idx($stroke_map, $stroke, $stroke);
         $keystrokes[] = phutil_tag('kbd', array(), $stroke);
       }
       $keystrokes = phutil_implode_html(' or ', $keystrokes);
       $rows[] = phutil_tag(
         'tr',
         array(),
         array(
           phutil_tag('th', array(), $keystrokes),
           phutil_tag('td', array(), $shortcut['description']),
         ));
     }
 
     $table = phutil_tag(
       'table',
       array('class' => 'keyboard-shortcut-help'),
       $rows);
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setTitle(pht('Keyboard Shortcuts'))
       ->appendChild($table)
       ->addCancelButton('#', pht('Close'));
 
-    return id(new AphrontDialogResponse())
-      ->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php
index f5e058d3f..818eb7560 100644
--- a/src/applications/herald/controller/HeraldRuleViewController.php
+++ b/src/applications/herald/controller/HeraldRuleViewController.php
@@ -1,158 +1,158 @@
 <?php
 
 final class HeraldRuleViewController extends HeraldController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $rule = id(new HeraldRuleQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needConditionsAndActions(true)
       ->executeOne();
     if (!$rule) {
       return new Aphront404Response();
     }
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($rule->getName())
       ->setPolicyObject($rule)
       ->setHeaderIcon('fa-bullhorn');
 
     if ($rule->getIsDisabled()) {
       $header->setStatus(
         'fa-ban',
         'red',
         pht('Archived'));
     } else {
       $header->setStatus(
         'fa-check',
         'bluegrey',
         pht('Active'));
     }
 
     $curtain = $this->buildCurtain($rule);
     $details = $this->buildPropertySectionView($rule);
     $description = $this->buildDescriptionView($rule);
 
     $id = $rule->getID();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb("H{$id}");
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $rule,
       new HeraldTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $title = $rule->getName();
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn($timeline)
-      ->addPropertySection(pht('DETAILS'), $details)
-      ->addPropertySection(pht('DESCRIPTION'), $description);
+      ->addPropertySection(pht('Details'), $details)
+      ->addPropertySection(pht('Description'), $description);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildCurtain(HeraldRule $rule) {
     $viewer = $this->getViewer();
 
     $id = $rule->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $rule,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($rule);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Rule'))
         ->setHref($this->getApplicationURI("edit/{$id}/"))
         ->setIcon('fa-pencil')
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     if ($rule->getIsDisabled()) {
       $disable_uri = "disable/{$id}/enable/";
       $disable_icon = 'fa-check';
       $disable_name = pht('Activate Rule');
     } else {
       $disable_uri = "disable/{$id}/disable/";
       $disable_icon = 'fa-ban';
       $disable_name = pht('Archive Rule');
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Disable Rule'))
         ->setHref($this->getApplicationURI($disable_uri))
         ->setIcon($disable_icon)
         ->setName($disable_name)
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
     return $curtain;
   }
 
   private function buildPropertySectionView(
     HeraldRule $rule) {
 
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $view->addProperty(
       pht('Rule Type'),
       idx(HeraldRuleTypeConfig::getRuleTypeMap(), $rule->getRuleType()));
 
     if ($rule->isPersonalRule()) {
       $view->addProperty(
         pht('Author'),
         $viewer->renderHandle($rule->getAuthorPHID()));
     }
 
     $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
     if ($adapter) {
       $view->addProperty(
         pht('Applies To'),
         idx(
           HeraldAdapter::getEnabledAdapterMap($viewer),
           $rule->getContentType()));
 
       if ($rule->isObjectRule()) {
         $view->addProperty(
           pht('Trigger Object'),
           $viewer->renderHandle($rule->getTriggerObjectPHID()));
       }
     }
 
     return $view;
   }
 
   private function buildDescriptionView(HeraldRule $rule) {
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
     if ($adapter) {
       $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule));
       $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer);
       $view->addTextContent($rule_text);
       return $view;
     }
     return null;
   }
 
 }
diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php
index e53c4fac8..950944188 100644
--- a/src/applications/home/controller/PhabricatorHomeMainController.php
+++ b/src/applications/home/controller/PhabricatorHomeMainController.php
@@ -1,422 +1,421 @@
 <?php
 
 final class PhabricatorHomeMainController extends PhabricatorHomeController {
 
   private $minipanels = array();
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function isGlobalDragAndDropUploadEnabled() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $user = $request->getUser();
 
     $dashboard = PhabricatorDashboardInstall::getDashboard(
       $user,
       $user->getPHID(),
       get_class($this->getCurrentApplication()));
 
     if (!$dashboard) {
       $dashboard = PhabricatorDashboardInstall::getDashboard(
         $user,
         PhabricatorHomeApplication::DASHBOARD_DEFAULT,
         get_class($this->getCurrentApplication()));
     }
 
     if ($dashboard) {
       $content = id(new PhabricatorDashboardRenderingEngine())
         ->setViewer($user)
         ->setDashboard($dashboard)
         ->renderDashboard();
     } else {
       $project_query = new PhabricatorProjectQuery();
       $project_query->setViewer($user);
       $project_query->withMemberPHIDs(array($user->getPHID()));
       $projects = $project_query->execute();
 
       $content = $this->buildMainResponse($projects);
     }
 
     if (!$request->getURIData('only')) {
       $nav = $this->buildNav();
       $nav->appendChild(
         array(
           $content,
           id(new PhabricatorGlobalUploadTargetView())->setUser($user),
         ));
       $content = $nav;
     }
 
-    return $this->buildApplicationPage(
-      $content,
-      array(
-        'title' => 'Phabricator',
-      ));
+    return $this->newPage()
+      ->setTitle('Phabricator')
+      ->appendChild($content);
+
   }
 
   private function buildMainResponse(array $projects) {
     assert_instances_of($projects, 'PhabricatorProject');
     $viewer = $this->getRequest()->getUser();
 
     $has_maniphest = PhabricatorApplication::isClassInstalledForViewer(
       'PhabricatorManiphestApplication',
       $viewer);
 
     $has_audit = PhabricatorApplication::isClassInstalledForViewer(
       'PhabricatorAuditApplication',
       $viewer);
 
     $has_differential = PhabricatorApplication::isClassInstalledForViewer(
       'PhabricatorDifferentialApplication',
       $viewer);
 
     if ($has_maniphest) {
       $unbreak_panel = $this->buildUnbreakNowPanel();
       $triage_panel = $this->buildNeedsTriagePanel($projects);
       $tasks_panel = $this->buildTasksPanel();
     } else {
       $unbreak_panel = null;
       $triage_panel = null;
       $tasks_panel = null;
     }
 
     if ($has_audit) {
       $audit_panel = $this->buildAuditPanel();
       $commit_panel = $this->buildCommitPanel();
     } else {
       $audit_panel = null;
       $commit_panel = null;
     }
 
     if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
       $welcome_panel = $this->buildWelcomePanel();
     } else {
       $welcome_panel = null;
     }
 
     if ($has_differential) {
       $revision_panel = $this->buildRevisionPanel();
     } else {
       $revision_panel = null;
     }
 
     $home = phutil_tag(
       'div',
       array(
         'class' => 'homepage-panel',
       ),
       array(
         $welcome_panel,
         $unbreak_panel,
         $triage_panel,
         $revision_panel,
         $tasks_panel,
         $audit_panel,
         $commit_panel,
         $this->minipanels,
       ));
       return $home;
   }
 
   private function buildUnbreakNowPanel() {
     $unbreak_now = PhabricatorEnv::getEnvConfig(
       'maniphest.priorities.unbreak-now');
     if (!$unbreak_now) {
       return null;
     }
 
     $user = $this->getRequest()->getUser();
 
     $task_query = id(new ManiphestTaskQuery())
       ->setViewer($user)
       ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
       ->withPriorities(array($unbreak_now))
       ->needProjectPHIDs(true)
       ->setLimit(10);
 
     $tasks = $task_query->execute();
 
     if (!$tasks) {
       return $this->renderMiniPanel(
         pht('No "Unbreak Now!" Tasks'),
         pht('Nothing appears to be critically broken right now.'));
     }
 
     $href = urisprintf(
       '/maniphest/?statuses=open()&priorities=%s#R',
       $unbreak_now);
     $title = pht('Unbreak Now!');
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->setObjectList($this->buildTaskListView($tasks));
 
     return $panel;
   }
 
   private function buildNeedsTriagePanel(array $projects) {
     assert_instances_of($projects, 'PhabricatorProject');
 
     $needs_triage = PhabricatorEnv::getEnvConfig(
       'maniphest.priorities.needs-triage');
     if (!$needs_triage) {
       return null;
     }
 
     $user = $this->getRequest()->getUser();
     if (!$user->isLoggedIn()) {
       return null;
     }
 
     if ($projects) {
       $task_query = id(new ManiphestTaskQuery())
         ->setViewer($user)
         ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
         ->withPriorities(array($needs_triage))
         ->withEdgeLogicPHIDs(
           PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
           PhabricatorQueryConstraint::OPERATOR_OR,
           mpull($projects, 'getPHID'))
         ->needProjectPHIDs(true)
         ->setLimit(10);
       $tasks = $task_query->execute();
     } else {
       $tasks = array();
     }
 
     if (!$tasks) {
       return $this->renderMiniPanel(
         pht('No "Needs Triage" Tasks'),
         pht('No tasks tagged with projects you are a member of need triage.'));
     }
 
     $title = pht('Needs Triage');
     $href = urisprintf(
       '/maniphest/?statuses=open()&priorities=%s&projects=projects(%s)#R',
       $needs_triage,
       $user->getPHID());
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->setObjectList($this->buildTaskListView($tasks));
 
     return $panel;
   }
 
   private function buildRevisionPanel() {
     $user = $this->getRequest()->getUser();
     $user_phid = $user->getPHID();
 
     $revision_query = id(new DifferentialRevisionQuery())
       ->setViewer($user)
       ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
       ->withResponsibleUsers(array($user_phid))
       ->needRelationships(true)
       ->needFlags(true)
       ->needDrafts(true);
 
     $revisions = $revision_query->execute();
 
     list($blocking, $active) = DifferentialRevisionQuery::splitResponsible(
         $revisions,
         array($user_phid));
 
     if (!$blocking && !$active) {
       return $this->renderMiniPanel(
         pht('No Waiting Revisions'),
         pht('No revisions are waiting on you.'));
     }
 
     $title = pht('Revisions Waiting on You');
     $href = '/differential';
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
 
     $revision_view = id(new DifferentialRevisionListView())
       ->setHighlightAge(true)
       ->setRevisions(array_merge($blocking, $active))
       ->setUser($user);
     $phids = array_merge(
       array($user_phid),
       $revision_view->getRequiredHandlePHIDs());
     $handles = $this->loadViewerHandles($phids);
 
     $revision_view->setHandles($handles);
 
     $list_view = $revision_view->render();
 
     $panel->setObjectList($list_view);
 
     return $panel;
   }
 
   private function buildWelcomePanel() {
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Welcome'));
     $panel->appendChild(
       phutil_safe_html(
         PhabricatorEnv::getEnvConfig('welcome.html')));
 
     return $panel;
   }
 
   private function buildTasksPanel() {
     $user = $this->getRequest()->getUser();
     $user_phid = $user->getPHID();
 
     $task_query = id(new ManiphestTaskQuery())
       ->setViewer($user)
       ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
       ->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY)
       ->withOwners(array($user_phid))
       ->needProjectPHIDs(true)
       ->setLimit(10);
 
     $tasks = $task_query->execute();
 
 
     if (!$tasks) {
       return $this->renderMiniPanel(
         pht('No Assigned Tasks'),
         pht('You have no assigned tasks.'));
     }
 
     $title = pht('Assigned Tasks');
     $href = '/maniphest/query/assigned/';
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->setObjectList($this->buildTaskListView($tasks));
 
     return $panel;
   }
 
   private function buildTaskListView(array $tasks) {
     assert_instances_of($tasks, 'ManiphestTask');
     $user = $this->getRequest()->getUser();
 
     $phids = array_merge(
       array_filter(mpull($tasks, 'getOwnerPHID')),
       array_mergev(mpull($tasks, 'getProjectPHIDs')));
 
     $handles = $this->loadViewerHandles($phids);
 
     $view = new ManiphestTaskListView();
     $view->setTasks($tasks);
     $view->setUser($user);
     $view->setHandles($handles);
 
     return $view;
   }
 
   private function renderSectionHeader($title, $href) {
     $title = phutil_tag(
       'a',
       array(
         'href' => $href,
       ),
       $title);
     $icon = id(new PHUIIconView())
       ->setIcon('fa-search')
       ->setHref($href);
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->addActionItem($icon);
     return $header;
   }
 
   private function renderMiniPanel($title, $body) {
     $panel = new PHUIInfoView();
     $panel->setSeverity(PHUIInfoView::SEVERITY_NODATA);
     $panel->appendChild(
       phutil_tag(
         'p',
         array(
         ),
         array(
           phutil_tag('strong', array(), $title.': '),
           $body,
         )));
     $this->minipanels[] = $panel;
   }
 
   public function buildAuditPanel() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
 
     $query = id(new DiffusionCommitQuery())
       ->setViewer($user)
       ->withNeedsAuditByPHIDs($phids)
       ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)
       ->needAuditRequests(true)
       ->needCommitData(true)
       ->setLimit(10);
 
     $commits = $query->execute();
 
     if (!$commits) {
       return $this->renderMinipanel(
         pht('No Audits'),
         pht('No commits are waiting for you to audit them.'));
     }
 
     $view = id(new PhabricatorAuditListView())
       ->setCommits($commits)
       ->setUser($user);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     $title = pht('Audits');
     $href = '/audit/';
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->setObjectList($view);
 
     return $panel;
   }
 
   public function buildCommitPanel() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $phids = array($user->getPHID());
 
     $query = id(new DiffusionCommitQuery())
       ->setViewer($user)
       ->withAuthorPHIDs($phids)
       ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN)
       ->needCommitData(true)
       ->needAuditRequests(true)
       ->setLimit(10);
 
     $commits = $query->execute();
 
     if (!$commits) {
       return $this->renderMinipanel(
         pht('No Problem Commits'),
         pht('No one has raised concerns with your commits.'));
     }
 
     $view = id(new PhabricatorAuditListView())
       ->setCommits($commits)
       ->setUser($user);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     $title = pht('Problem Commits');
     $href = '/audit/';
     $panel = new PHUIObjectBoxView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->setObjectList($view);
 
     return $panel;
   }
 
 }
diff --git a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php
index 40f9afeb8..6429443ed 100644
--- a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php
+++ b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php
@@ -1,35 +1,47 @@
 <?php
 
 final class PhabricatorHomeQuickCreateController
   extends PhabricatorHomeController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $items = $this->getCurrentApplication()->loadAllQuickCreateItems($viewer);
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     foreach ($items as $item) {
       $list->addItem(
         id(new PHUIObjectItemView())
           ->setHeader($item->getName())
           ->setWorkflow($item->getWorkflow())
           ->setHref($item->getHref()));
     }
 
+    $title = pht('Quick Create');
+
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Quick Create'));
+    $crumbs->setBorder(true);
+
+    $box = id(new PHUIObjectBoxView())
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->setObjectList($list);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-plus-square');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $list,
-      ),
-      array(
-        'title' => pht('Quick Create'),
-      ));
   }
 
 }
diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php
index 84a879a7a..3804885ed 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php
@@ -1,262 +1,272 @@
 <?php
 
 final class LegalpadDocumentEditController extends LegalpadController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if (!$id) {
       $is_create = true;
 
       $this->requireApplicationCapability(
         LegalpadCreateDocumentsCapability::CAPABILITY);
 
       $document = LegalpadDocument::initializeNewDocument($viewer);
       $body = id(new LegalpadDocumentBody())
         ->setCreatorPHID($viewer->getPHID());
       $document->attachDocumentBody($body);
       $document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID);
     } else {
       $is_create = false;
 
       $document = id(new LegalpadDocumentQuery())
         ->setViewer($viewer)
         ->needDocumentBodies(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->withIDs(array($id))
         ->executeOne();
       if (!$document) {
         return new Aphront404Response();
       }
     }
 
     $e_title = true;
     $e_text = true;
 
     $title = $document->getDocumentBody()->getTitle();
     $text = $document->getDocumentBody()->getText();
     $v_signature_type = $document->getSignatureType();
     $v_preamble = $document->getPreamble();
     $v_require_signature = $document->getRequireSignature();
 
     $errors = array();
     $can_view = null;
     $can_edit = null;
     if ($request->isFormPost()) {
 
       $xactions = array();
 
       $title = $request->getStr('title');
       if (!strlen($title)) {
         $e_title = pht('Required');
         $errors[] = pht('The document title may not be blank.');
       } else {
         $xactions[] = id(new LegalpadTransaction())
           ->setTransactionType(LegalpadTransaction::TYPE_TITLE)
           ->setNewValue($title);
       }
 
       $text = $request->getStr('text');
       if (!strlen($text)) {
         $e_text = pht('Required');
         $errors[] = pht('The document may not be blank.');
       } else {
         $xactions[] = id(new LegalpadTransaction())
           ->setTransactionType(LegalpadTransaction::TYPE_TEXT)
           ->setNewValue($text);
       }
 
       $can_view = $request->getStr('can_view');
       $xactions[] = id(new LegalpadTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($can_view);
       $can_edit = $request->getStr('can_edit');
       $xactions[] = id(new LegalpadTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
         ->setNewValue($can_edit);
 
       if ($is_create) {
         $v_signature_type = $request->getStr('signatureType');
         $xactions[] = id(new LegalpadTransaction())
           ->setTransactionType(LegalpadTransaction::TYPE_SIGNATURE_TYPE)
           ->setNewValue($v_signature_type);
       }
 
       $v_preamble = $request->getStr('preamble');
       $xactions[] = id(new LegalpadTransaction())
         ->setTransactionType(LegalpadTransaction::TYPE_PREAMBLE)
         ->setNewValue($v_preamble);
 
       $v_require_signature = $request->getBool('requireSignature', 0);
       if ($v_require_signature) {
         if (!$viewer->getIsAdmin()) {
           $errors[] = pht('Only admins may require signature.');
         }
         $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
         if ($v_signature_type != $individual) {
           $errors[] = pht(
             'Only documents with signature type "individual" may require '.
             'signing to use Phabricator.');
         }
       }
       if ($viewer->getIsAdmin()) {
         $xactions[] = id(new LegalpadTransaction())
           ->setTransactionType(LegalpadTransaction::TYPE_REQUIRE_SIGNATURE)
           ->setNewValue($v_require_signature);
       }
 
       if (!$errors) {
         $editor = id(new LegalpadDocumentEditor())
           ->setContentSourceFromRequest($request)
           ->setContinueOnNoEffect(true)
           ->setActor($viewer);
 
         $xactions = $editor->applyTransactions($document, $xactions);
 
         return id(new AphrontRedirectResponse())
           ->setURI($this->getApplicationURI('view/'.$document->getID()));
       }
     }
 
     if ($errors) {
       // set these to what was specified in the form on post
       $document->setViewPolicy($can_view);
       $document->setEditPolicy($can_edit);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setID('document-title')
         ->setLabel(pht('Title'))
         ->setError($e_title)
         ->setValue($title)
         ->setName('title'));
 
     if ($is_create) {
       $form->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel(pht('Who Should Sign?'))
           ->setName(pht('signatureType'))
           ->setValue($v_signature_type)
           ->setOptions(LegalpadDocument::getSignatureTypeMap()));
       $show_require = true;
       $caption = pht('Applies only to documents individuals sign.');
     } else {
       $form->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Who Should Sign?'))
           ->setValue($document->getSignatureTypeName()));
       $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
       $show_require = $document->getSignatureType() == $individual;
       $caption = null;
     }
 
     if ($show_require) {
       $form
         ->appendChild(
           id(new AphrontFormCheckboxControl())
           ->setDisabled(!$viewer->getIsAdmin())
           ->setLabel(pht('Require Signature'))
           ->addCheckbox(
             'requireSignature',
             'requireSignature',
             pht('Should signing this document be required to use Phabricator?'),
             $v_require_signature)
           ->setCaption($caption));
     }
 
     $form
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setUser($viewer)
           ->setID('preamble')
           ->setLabel(pht('Preamble'))
           ->setValue($v_preamble)
           ->setName('preamble')
           ->setCaption(
             pht('Optional help text for users signing this document.')))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setUser($viewer)
           ->setID('document-text')
           ->setLabel(pht('Document Body'))
           ->setError($e_text)
           ->setValue($text)
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
           ->setName('text'));
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($document)
       ->execute();
 
     $form
       ->appendChild(
         id(new AphrontFormPolicyControl())
         ->setUser($viewer)
         ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
         ->setPolicyObject($document)
         ->setPolicies($policies)
         ->setName('can_view'))
       ->appendChild(
         id(new AphrontFormPolicyControl())
         ->setUser($viewer)
         ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
         ->setPolicyObject($document)
         ->setPolicies($policies)
         ->setName('can_edit'));
 
     $crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
     $submit = new AphrontFormSubmitControl();
     if ($is_create) {
       $submit->setValue(pht('Create Document'));
       $submit->addCancelButton($this->getApplicationURI());
       $title = pht('Create Document');
       $short = pht('Create');
+      $header_icon = 'fa-plus-square';
     } else {
       $submit->setValue(pht('Save Document'));
       $submit->addCancelButton(
           $this->getApplicationURI('view/'.$document->getID()));
-      $title = pht('Edit Document');
+      $title = pht('Edit Document: %s', $document->getTitle());
       $short = pht('Edit');
+      $header_icon = 'fa-pencil';
 
       $crumbs->addTextCrumb(
         $document->getMonogram(),
         $this->getApplicationURI('view/'.$document->getID()));
     }
 
-    $form
-      ->appendChild($submit);
+    $form->appendChild($submit);
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Document'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $crumbs->addTextCrumb($short);
+    $crumbs->setBorder(true);
 
     $preview = id(new PHUIRemarkupPreviewPanel())
       ->setHeader($document->getTitle())
       ->setPreviewURI($this->getApplicationURI('document/preview/'))
       ->setControlID('document-text')
       ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $form_box,
         $preview,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php
index baeefeaf4..1a39f2c14 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php
@@ -1,201 +1,211 @@
 <?php
 
 final class LegalpadDocumentManageController extends LegalpadController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     // NOTE: We require CAN_EDIT to view this page.
 
     $document = id(new LegalpadDocumentQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needDocumentBodies(true)
       ->needContributors(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$document) {
       return new Aphront404Response();
     }
 
     $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
       $document->getPHID());
 
     $document_body = $document->getDocumentBody();
 
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer);
     $engine->addObject(
       $document_body,
       LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $timeline = $this->buildTransactionTimeline(
       $document,
       new LegalpadTransactionQuery(),
       $engine);
 
     $title = $document_body->getTitle();
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
-      ->setPolicyObject($document);
+      ->setPolicyObject($document)
+      ->setHeaderIcon('fa-gavel');
 
-    $actions = $this->buildActionView($document);
-    $properties = $this->buildPropertyView($document, $engine, $actions);
+    $curtain = $this->buildCurtainView($document);
+    $properties = $this->buildPropertyView($document, $engine);
+    $document_view = $this->buildDocumentView($document, $engine);
 
     $comment_form_id = celerity_generate_unique_node_id();
 
     $add_comment = $this->buildAddCommentView($document, $comment_form_id);
 
     $crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
     $crumbs->addTextCrumb(
       $document->getMonogram(),
       '/'.$document->getMonogram());
     $crumbs->addTextCrumb(pht('Manage'));
+    $crumbs->setBorder(true);
 
-    $object_box = id(new PHUIObjectBoxView())
+
+    $view = id(new PHUITwoColumnView())
       ->setHeader($header)
-      ->addPropertyList($properties)
-      ->addPropertyList($this->buildDocument($engine, $document_body));
-
-    $content = array(
-      $crumbs,
-      $object_box,
-      $timeline,
-      $add_comment,
-    );
-
-    return $this->buildApplicationPage(
-      $content,
-      array(
-        'title' => $title,
-        'pageObjects' => array($document->getPHID()),
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
+        $properties,
+        $document_view,
+        $timeline,
+        $add_comment,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->setPageObjectPHIDs(array($document->getPHID()))
+      ->appendChild($view);
   }
 
-  private function buildDocument(
-    PhabricatorMarkupEngine
-    $engine, LegalpadDocumentBody $body) {
+  private function buildDocumentView(
+    LegalpadDocument $document,
+    PhabricatorMarkupEngine $engine) {
 
-    $view = new PHUIPropertyListView();
-    $view->addClass('legalpad');
-    $view->addSectionHeader(
-      pht('Document'), 'fa-file-text-o');
-    $view->addTextContent(
-      $engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT));
+    $viewer = $this->getViewer();
 
-    return $view;
+    $view = id(new PHUIPropertyListView())
+      ->setUser($viewer);
+    $document_body = $document->getDocumentBody();
+    $document_text = $engine->getOutput(
+      $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT);
+
+    $preamble_box = null;
+    if (strlen($document->getPreamble())) {
+      $preamble_text = new PHUIRemarkupView($viewer, $document->getPreamble());
+      $view->addTextContent($preamble_text);
+      $view->addSectionHeader('');
+      $view->addTextContent($document_text);
+    } else {
+      $view->addTextContent($document_text);
+    }
 
+    return id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('DOCUMENT'))
+      ->addPropertyList($view)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
   }
 
-  private function buildActionView(LegalpadDocument $document) {
+  private function buildCurtainView(LegalpadDocument $document) {
     $viewer = $this->getViewer();
 
-    $actions = id(new PhabricatorActionListView())
-      ->setUser($viewer)
-      ->setObject($document);
+    $curtain = $this->newCurtainView($document);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $document,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $doc_id = $document->getID();
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
       ->setIcon('fa-pencil-square')
       ->setName(pht('View/Sign Document'))
       ->setHref('/'.$document->getMonogram()));
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Document'))
         ->setHref($this->getApplicationURI('/edit/'.$doc_id.'/'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
-    $actions->addAction(
+    $curtain->addAction(
       id(new PhabricatorActionView())
       ->setIcon('fa-terminal')
       ->setName(pht('View Signatures'))
       ->setHref($this->getApplicationURI('/signatures/'.$doc_id.'/')));
 
-    return $actions;
+    return $curtain;
   }
 
   private function buildPropertyView(
     LegalpadDocument $document,
-    PhabricatorMarkupEngine $engine,
-    PhabricatorActionListView $actions) {
+    PhabricatorMarkupEngine $engine) {
 
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
-      ->setUser($viewer)
-      ->setObject($document)
-      ->setActionList($actions);
+      ->setUser($viewer);
 
     $properties->addProperty(
       pht('Signature Type'),
       $document->getSignatureTypeName());
 
     $properties->addProperty(
       pht('Last Updated'),
       phabricator_datetime($document->getDateModified(), $viewer));
 
     $properties->addProperty(
       pht('Updated By'),
       $viewer->renderHandle($document->getDocumentBody()->getCreatorPHID()));
 
     $properties->addProperty(
       pht('Versions'),
       $document->getVersions());
 
     if ($document->getContributors()) {
       $properties->addProperty(
         pht('Contributors'),
         $viewer
           ->renderHandleList($document->getContributors())
           ->setAsInline(true));
     }
 
-    $properties->invokeWillRenderEvent();
-
-    return $properties;
+    return id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Properties'))
+      ->addPropertyList($properties)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
   }
 
   private function buildAddCommentView(
     LegalpadDocument $document,
     $comment_form_id) {
     $viewer = $this->getViewer();
 
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $document->getPHID());
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     $title = $is_serious
       ? pht('Add Comment')
       : pht('Debate Legislation');
 
     $form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($document->getPHID())
       ->setFormID($comment_form_id)
       ->setHeaderText($title)
       ->setDraft($draft)
       ->setSubmitButtonName(pht('Add Comment'))
       ->setAction($this->getApplicationURI('/comment/'.$document->getID().'/'))
       ->setRequestURI($this->getRequest()->getRequestURI());
 
     return $form;
 
   }
 
 }
diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
index 00549c1ad..70b750c38 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
@@ -1,697 +1,698 @@
 <?php
 
 final class LegalpadDocumentSignController extends LegalpadController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function shouldAllowLegallyNonCompliantUsers() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $document = id(new LegalpadDocumentQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->needDocumentBodies(true)
       ->executeOne();
     if (!$document) {
       return new Aphront404Response();
     }
 
     $information = $this->readSignerInformation(
       $document,
       $request);
     if ($information instanceof AphrontResponse) {
       return $information;
     }
     list($signer_phid, $signature_data) = $information;
 
     $signature = null;
 
     $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
     $is_individual = ($document->getSignatureType() == $type_individual);
     switch ($document->getSignatureType()) {
       case LegalpadDocument::SIGNATURE_TYPE_NONE:
         // nothing to sign means this should be true
         $has_signed = true;
         // this is a status UI element
         $signed_status = null;
         break;
       case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
         if ($signer_phid) {
           // TODO: This is odd and should probably be adjusted after
           // grey/external accounts work better, but use the omnipotent
           // viewer to check for a signature so we can pick up
           // anonymous/grey signatures.
 
           $signature = id(new LegalpadDocumentSignatureQuery())
             ->setViewer(PhabricatorUser::getOmnipotentUser())
             ->withDocumentPHIDs(array($document->getPHID()))
             ->withSignerPHIDs(array($signer_phid))
             ->executeOne();
 
           if ($signature && !$viewer->isLoggedIn()) {
           return $this->newDialog()
             ->setTitle(pht('Already Signed'))
             ->appendParagraph(pht('You have already signed this document!'))
             ->addCancelButton('/'.$document->getMonogram(), pht('Okay'));
           }
         }
 
         $signed_status = null;
         if (!$signature) {
           $has_signed = false;
           $signature = id(new LegalpadDocumentSignature())
             ->setSignerPHID($signer_phid)
             ->setDocumentPHID($document->getPHID())
             ->setDocumentVersion($document->getVersions());
 
           // If the user is logged in, show a notice that they haven't signed.
           // If they aren't logged in, we can't be as sure, so don't show
           // anything.
           if ($viewer->isLoggedIn()) {
             $signed_status = id(new PHUIInfoView())
               ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
               ->setErrors(
                 array(
                   pht('You have not signed this document yet.'),
                 ));
           }
         } else {
           $has_signed = true;
           $signature_data = $signature->getSignatureData();
 
           // In this case, we know they've signed.
           $signed_at = $signature->getDateCreated();
 
           if ($signature->getIsExemption()) {
             $exemption_phid = $signature->getExemptionPHID();
             $handles = $this->loadViewerHandles(array($exemption_phid));
             $exemption_handle = $handles[$exemption_phid];
 
             $signed_text = pht(
               'You do not need to sign this document. '.
               '%s added a signature exemption for you on %s.',
               $exemption_handle->renderLink(),
               phabricator_datetime($signed_at, $viewer));
           } else {
             $signed_text = pht(
               'You signed this document on %s.',
               phabricator_datetime($signed_at, $viewer));
           }
 
           $signed_status = id(new PHUIInfoView())
             ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
             ->setErrors(array($signed_text));
         }
 
         $field_errors = array(
           'name' => true,
           'email' => true,
           'agree' => true,
         );
         $signature->setSignatureData($signature_data);
         break;
 
       case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
         $signature = id(new LegalpadDocumentSignature())
           ->setDocumentPHID($document->getPHID())
           ->setDocumentVersion($document->getVersions());
 
         if ($viewer->isLoggedIn()) {
           $has_signed = false;
 
           $signed_status = null;
         } else {
           // This just hides the form.
           $has_signed = true;
 
           $login_text = pht(
             'This document requires a corporate signatory. You must log in to '.
             'accept this document on behalf of a company you represent.');
           $signed_status = id(new PHUIInfoView())
             ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
             ->setErrors(array($login_text));
         }
 
         $field_errors = array(
           'name' => true,
           'address' => true,
           'contact.name' => true,
           'email' => true,
         );
         $signature->setSignatureData($signature_data);
         break;
     }
 
     $errors = array();
     if ($request->isFormOrHisecPost() && !$has_signed) {
 
       // Require two-factor auth to sign legal documents.
       if ($viewer->isLoggedIn()) {
         $engine = new PhabricatorAuthSessionEngine();
         $engine->requireHighSecuritySession(
           $viewer,
           $request,
           '/'.$document->getMonogram());
       }
 
       list($form_data, $errors, $field_errors) = $this->readSignatureForm(
         $document,
         $request);
 
       $signature_data = $form_data + $signature_data;
 
       $signature->setSignatureData($signature_data);
       $signature->setSignatureType($document->getSignatureType());
       $signature->setSignerName((string)idx($signature_data, 'name'));
       $signature->setSignerEmail((string)idx($signature_data, 'email'));
 
       $agree = $request->getExists('agree');
       if (!$agree) {
         $errors[] = pht(
           'You must check "I agree to the terms laid forth above."');
         $field_errors['agree'] = pht('Required');
       }
 
       if ($viewer->isLoggedIn() && $is_individual) {
         $verified = LegalpadDocumentSignature::VERIFIED;
       } else {
         $verified = LegalpadDocumentSignature::UNVERIFIED;
       }
       $signature->setVerified($verified);
 
       if (!$errors) {
         $signature->save();
 
         // If the viewer is logged in, signing for themselves, send them to
         // the document page, which will show that they have signed the
         // document. Unless of course they were required to sign the
         // document to use Phabricator; in that case try really hard to
         // re-direct them to where they wanted to go.
         //
         // Otherwise, send them to a completion page.
         if ($viewer->isLoggedIn() && $is_individual) {
           $next_uri = '/'.$document->getMonogram();
           if ($document->getRequireSignature()) {
             $request_uri = $request->getRequestURI();
             $next_uri = (string)$request_uri;
           }
         } else {
           $this->sendVerifySignatureEmail(
             $document,
             $signature);
 
           $next_uri = $this->getApplicationURI('done/');
         }
 
         return id(new AphrontRedirectResponse())->setURI($next_uri);
       }
     }
 
     $document_body = $document->getDocumentBody();
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer);
     $engine->addObject(
       $document_body,
       LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $engine->process();
 
     $document_markup = $engine->getOutput(
       $document_body,
       LegalpadDocumentBody::MARKUP_FIELD_TEXT);
 
     $title = $document_body->getTitle();
 
     $manage_uri = $this->getApplicationURI('view/'.$document->getID().'/');
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $document,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     // Use the last content update as the modified date. We don't want to
     // show that a document like a TOS was "updated" by an incidental change
     // to a field like the preamble or privacy settings which does not acutally
     // affect the content of the agreement.
     $content_updated = $document_body->getDateCreated();
 
     // NOTE: We're avoiding `setPolicyObject()` here so we don't pick up
     // extra UI elements that are unnecessary and clutter the signature page.
     // These details are available on the "Manage" page.
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
       ->setEpoch($content_updated)
       ->addActionLink(
         id(new PHUIButtonView())
           ->setTag('a')
           ->setIcon('fa-pencil')
           ->setText(pht('Manage'))
           ->setHref($manage_uri)
           ->setDisabled(!$can_edit)
           ->setWorkflow(!$can_edit));
 
     $preamble_box = null;
     if (strlen($document->getPreamble())) {
       $preamble_text = new PHUIRemarkupView($viewer, $document->getPreamble());
 
       // NOTE: We're avoiding `setObject()` here so we don't pick up extra UI
       // elements like "Subscribers". This information is available on the
       // "Manage" page, but just clutters up the "Signature" page.
       $preamble = id(new PHUIPropertyListView())
         ->setUser($viewer)
         ->addSectionHeader(pht('Preamble'))
         ->addTextContent($preamble_text);
 
       $preamble_box = new PHUIPropertyGroupView();
       $preamble_box->addPropertyList($preamble);
     }
 
     $content = id(new PHUIDocumentViewPro())
       ->addClass('legalpad')
       ->setHeader($header)
       ->appendChild(
         array(
           $signed_status,
           $preamble_box,
           $document_markup,
         ));
 
     $signature_box = null;
     if (!$has_signed) {
       $error_view = null;
       if ($errors) {
         $error_view = id(new PHUIInfoView())
           ->setErrors($errors);
       }
 
       $signature_form = $this->buildSignatureForm(
         $document,
         $signature,
         $field_errors);
 
       switch ($document->getSignatureType()) {
         default:
           break;
         case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
         case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
           $box = id(new PHUIObjectBoxView())
+            ->addClass('document-sign-box')
             ->setHeaderText(pht('Agree and Sign Document'))
+            ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
             ->setForm($signature_form);
           if ($error_view) {
             $box->setInfoView($error_view);
           }
-          $signature_box = phutil_tag_div('phui-document-view-pro-box', $box);
+          $signature_box = phutil_tag_div(
+            'phui-document-view-pro-box plt', $box);
           break;
       }
 
 
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumbs->addTextCrumb($document->getMonogram());
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->setPageObjectPHIDs(array($document->getPHID()))
+      ->appendChild(array(
         $content,
         $signature_box,
-      ),
-      array(
-        'title' => $title,
-        'pageObjects' => array($document->getPHID()),
       ));
   }
 
   private function readSignerInformation(
     LegalpadDocument $document,
     AphrontRequest $request) {
 
     $viewer = $request->getUser();
     $signer_phid = null;
     $signature_data = array();
 
     switch ($document->getSignatureType()) {
       case LegalpadDocument::SIGNATURE_TYPE_NONE:
         break;
       case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
         if ($viewer->isLoggedIn()) {
           $signer_phid = $viewer->getPHID();
           $signature_data = array(
             'name' => $viewer->getRealName(),
             'email' => $viewer->loadPrimaryEmailAddress(),
           );
         } else if ($request->isFormPost()) {
           $email = new PhutilEmailAddress($request->getStr('email'));
           if (strlen($email->getDomainName())) {
             $email_obj = id(new PhabricatorUserEmail())
               ->loadOneWhere('address = %s', $email->getAddress());
             if ($email_obj) {
               return $this->signInResponse();
             }
             $external_account = id(new PhabricatorExternalAccountQuery())
               ->setViewer($viewer)
               ->withAccountTypes(array('email'))
               ->withAccountDomains(array($email->getDomainName()))
               ->withAccountIDs(array($email->getAddress()))
               ->loadOneOrCreate();
             if ($external_account->getUserPHID()) {
               return $this->signInResponse();
             }
             $signer_phid = $external_account->getPHID();
           }
         }
         break;
       case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
         $signer_phid = $viewer->getPHID();
         if ($signer_phid) {
           $signature_data = array(
             'contact.name' => $viewer->getRealName(),
             'email' => $viewer->loadPrimaryEmailAddress(),
             'actorPHID' => $viewer->getPHID(),
           );
         }
         break;
     }
 
     return array($signer_phid, $signature_data);
   }
 
   private function buildSignatureForm(
     LegalpadDocument $document,
     LegalpadDocumentSignature $signature,
     array $errors) {
 
     $viewer = $this->getRequest()->getUser();
     $data = $signature->getSignatureData();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer);
 
     $signature_type = $document->getSignatureType();
     switch ($signature_type) {
       case LegalpadDocument::SIGNATURE_TYPE_NONE:
         // bail out of here quick
         return;
       case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
         $this->buildIndividualSignatureForm(
           $form,
           $document,
           $signature,
           $errors);
         break;
       case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
         $this->buildCorporateSignatureForm(
           $form,
           $document,
           $signature,
           $errors);
         break;
       default:
         throw new Exception(
           pht(
             'This document has an unknown signature type ("%s").',
             $signature_type));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormCheckboxControl())
           ->setError(idx($errors, 'agree', null))
           ->addCheckbox(
             'agree',
             'agree',
             pht('I agree to the terms laid forth above.'),
             false));
     if ($document->getRequireSignature()) {
       $cancel_uri = '/logout/';
       $cancel_text = pht('Log Out');
     } else {
       $cancel_uri = $this->getApplicationURI();
       $cancel_text = pht('Cancel');
     }
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Sign Document'))
           ->addCancelButton($cancel_uri, $cancel_text));
 
     return $form;
   }
 
   private function buildIndividualSignatureForm(
     AphrontFormView $form,
     LegalpadDocument $document,
     LegalpadDocumentSignature $signature,
     array $errors) {
 
     $data = $signature->getSignatureData();
 
     $form
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Name'))
         ->setValue(idx($data, 'name', ''))
         ->setName('name')
         ->setError(idx($errors, 'name', null)));
 
     $viewer = $this->getRequest()->getUser();
     if (!$viewer->isLoggedIn()) {
       $form->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Email'))
           ->setValue(idx($data, 'email', ''))
           ->setName('email')
           ->setError(idx($errors, 'email', null)));
     }
 
     return $form;
   }
 
   private function buildCorporateSignatureForm(
     AphrontFormView $form,
     LegalpadDocument $document,
     LegalpadDocumentSignature $signature,
     array $errors) {
 
     $data = $signature->getSignatureData();
 
     $form
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Company Name'))
         ->setValue(idx($data, 'name', ''))
         ->setName('name')
         ->setError(idx($errors, 'name', null)))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
         ->setLabel(pht('Company Address'))
         ->setValue(idx($data, 'address', ''))
         ->setName('address')
         ->setError(idx($errors, 'address', null)))
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Contact Name'))
         ->setValue(idx($data, 'contact.name', ''))
         ->setName('contact.name')
         ->setError(idx($errors, 'contact.name', null)))
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Contact Email'))
         ->setValue(idx($data, 'email', ''))
         ->setName('email')
         ->setError(idx($errors, 'email', null)));
 
     return $form;
   }
 
   private function readSignatureForm(
     LegalpadDocument $document,
     AphrontRequest $request) {
 
     $signature_type = $document->getSignatureType();
     switch ($signature_type) {
       case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
         $result = $this->readIndividualSignatureForm(
           $document,
           $request);
         break;
       case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
         $result = $this->readCorporateSignatureForm(
           $document,
           $request);
         break;
       default:
         throw new Exception(
           pht(
             'This document has an unknown signature type ("%s").',
             $signature_type));
     }
 
     return $result;
   }
 
   private function readIndividualSignatureForm(
     LegalpadDocument $document,
     AphrontRequest $request) {
 
     $signature_data = array();
     $errors = array();
     $field_errors = array();
 
 
     $name = $request->getStr('name');
 
     if (!strlen($name)) {
       $field_errors['name'] = pht('Required');
       $errors[] = pht('Name field is required.');
     } else {
       $field_errors['name'] = null;
     }
     $signature_data['name'] = $name;
 
     $viewer = $request->getUser();
     if ($viewer->isLoggedIn()) {
       $email = $viewer->loadPrimaryEmailAddress();
     } else {
       $email = $request->getStr('email');
 
       $addr_obj = null;
       if (!strlen($email)) {
         $field_errors['email'] = pht('Required');
         $errors[] = pht('Email field is required.');
       } else {
         $addr_obj = new PhutilEmailAddress($email);
         $domain = $addr_obj->getDomainName();
         if (!$domain) {
           $field_errors['email'] = pht('Invalid');
           $errors[] = pht('A valid email is required.');
         } else {
           $field_errors['email'] = null;
         }
       }
     }
     $signature_data['email'] = $email;
 
     return array($signature_data, $errors, $field_errors);
   }
 
   private function readCorporateSignatureForm(
     LegalpadDocument $document,
     AphrontRequest $request) {
 
     $viewer = $request->getUser();
     if (!$viewer->isLoggedIn()) {
       throw new Exception(
         pht(
           'You can not sign a document on behalf of a corporation unless '.
           'you are logged in.'));
     }
 
     $signature_data = array();
     $errors = array();
     $field_errors = array();
 
     $name = $request->getStr('name');
 
     if (!strlen($name)) {
       $field_errors['name'] = pht('Required');
       $errors[] = pht('Company name is required.');
     } else {
       $field_errors['name'] = null;
     }
     $signature_data['name'] = $name;
 
     $address = $request->getStr('address');
     if (!strlen($address)) {
       $field_errors['address'] = pht('Required');
       $errors[] = pht('Company address is required.');
     } else {
       $field_errors['address'] = null;
     }
     $signature_data['address'] = $address;
 
     $contact_name = $request->getStr('contact.name');
     if (!strlen($contact_name)) {
       $field_errors['contact.name'] = pht('Required');
       $errors[] = pht('Contact name is required.');
     } else {
       $field_errors['contact.name'] = null;
     }
     $signature_data['contact.name'] = $contact_name;
 
     $email = $request->getStr('email');
     $addr_obj = null;
     if (!strlen($email)) {
       $field_errors['email'] = pht('Required');
       $errors[] = pht('Contact email is required.');
     } else {
       $addr_obj = new PhutilEmailAddress($email);
       $domain = $addr_obj->getDomainName();
       if (!$domain) {
         $field_errors['email'] = pht('Invalid');
         $errors[] = pht('A valid email is required.');
       } else {
         $field_errors['email'] = null;
       }
     }
     $signature_data['email'] = $email;
 
     return array($signature_data, $errors, $field_errors);
   }
 
   private function sendVerifySignatureEmail(
     LegalpadDocument $doc,
     LegalpadDocumentSignature $signature) {
 
     $signature_data = $signature->getSignatureData();
     $email = new PhutilEmailAddress($signature_data['email']);
     $doc_name = $doc->getTitle();
     $doc_link = PhabricatorEnv::getProductionURI('/'.$doc->getMonogram());
     $path = $this->getApplicationURI(sprintf(
       '/verify/%s/',
       $signature->getSecretKey()));
     $link = PhabricatorEnv::getProductionURI($path);
 
     $name = idx($signature_data, 'name');
 
     $body = pht(
       "%s:\n\n".
       "This email address was used to sign a Legalpad document ".
       "in Phabricator:\n\n".
       "  %s\n\n".
       "Please verify you own this email address and accept the ".
       "agreement by clicking this link:\n\n".
       "  %s\n\n".
       "Your signature is not valid until you complete this ".
       "verification step.\n\nYou can review the document here:\n\n".
       "  %s\n",
       $name,
       $doc_name,
       $link,
       $doc_link);
 
     id(new PhabricatorMetaMTAMail())
       ->addRawTos(array($email->getAddress()))
       ->setSubject(pht('[Legalpad] Signature Verification'))
       ->setForceDelivery(true)
       ->setBody($body)
       ->setRelatedPHID($signature->getDocumentPHID())
       ->saveAndSend();
   }
 
   private function signInResponse() {
     return id(new Aphront403Response())
       ->setForbiddenText(
         pht(
           'The email address specified is associated with an account. '.
           'Please login to that account and sign this document again.'));
   }
 
 }
diff --git a/src/applications/macro/controller/PhabricatorMacroAudioController.php b/src/applications/macro/controller/PhabricatorMacroAudioController.php
index 92b98e556..7e2b92463 100644
--- a/src/applications/macro/controller/PhabricatorMacroAudioController.php
+++ b/src/applications/macro/controller/PhabricatorMacroAudioController.php
@@ -1,153 +1,160 @@
 <?php
 
 final class PhabricatorMacroAudioController extends PhabricatorMacroController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $this->requireApplicationCapability(
       PhabricatorMacroManageCapability::CAPABILITY);
 
     $macro = id(new PhabricatorMacroQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
         ))
       ->withIDs(array($id))
       ->executeOne();
 
     if (!$macro) {
       return new Aphront404Response();
     }
 
     $errors = array();
     $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/');
 
     $e_file = null;
     $file = null;
 
     if ($request->isFormPost()) {
       $xactions = array();
 
       if ($request->getBool('behaviorForm')) {
         $xactions[] = id(new PhabricatorMacroTransaction())
           ->setTransactionType(
             PhabricatorMacroTransaction::TYPE_AUDIO_BEHAVIOR)
           ->setNewValue($request->getStr('audioBehavior'));
       } else {
         $file = null;
         if ($request->getFileExists('file')) {
           $file = PhabricatorFile::newFromPHPUpload(
             $_FILES['file'],
             array(
               'name' => $request->getStr('name'),
               'authorPHID' => $viewer->getPHID(),
               'isExplicitUpload' => true,
             ));
         }
 
         if ($file) {
           if (!$file->isAudio()) {
             $errors[] = pht('You must upload audio.');
             $e_file = pht('Invalid');
           } else {
             $xactions[] = id(new PhabricatorMacroTransaction())
               ->setTransactionType(PhabricatorMacroTransaction::TYPE_AUDIO)
               ->setNewValue($file->getPHID());
           }
         } else {
           $errors[] = pht('You must upload an audio file.');
           $e_file = pht('Required');
         }
       }
 
       if (!$errors) {
         id(new PhabricatorMacroEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request)
           ->applyTransactions($macro, $xactions);
 
         return id(new AphrontRedirectResponse())->setURI($view_uri);
       }
     }
 
     $form = id(new AphrontFormView())
       ->addHiddenInput('behaviorForm', 1)
       ->setUser($viewer);
 
     $options = id(new AphrontFormRadioButtonControl())
       ->setLabel(pht('Audio Behavior'))
       ->setName('audioBehavior')
       ->setValue(
         nonempty(
           $macro->getAudioBehavior(),
           PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE));
 
     $options->addButton(
       PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE,
       pht('Do Not Play'),
       pht('Do not play audio.'));
 
     $options->addButton(
       PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE,
       pht('Play Once'),
       pht('Play audio once, when the viewer looks at the macro.'));
 
     $options->addButton(
       PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP,
       pht('Play Continuously'),
       pht(
         'Play audio continuously, treating the macro as an audio source. '.
         'Best for ambient sounds.'));
 
     $form->appendChild($options);
-
-    $form
-      ->appendChild(
+    $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save Audio Behavior'))
           ->addCancelButton($view_uri));
 
     $crumbs = $this->buildApplicationCrumbs();
 
-    $title = pht('Edit Audio Behavior');
+    $title = pht('Edit Audio: %s', $macro->getName());
     $crumb = pht('Edit Audio');
 
     $crumbs->addTextCrumb(pht('Macro "%s"', $macro->getName()), $view_uri);
     $crumbs->addTextCrumb($crumb, $request->getRequestURI());
+    $crumbs->setBorder(true);
 
     $upload_form = id(new AphrontFormView())
       ->setEncType('multipart/form-data')
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormFileControl())
           ->setLabel(pht('Audio File'))
           ->setName('file'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Upload File')));
 
     $upload = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Upload New Audio'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($upload_form);
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Behavior'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-pencil');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $form_box,
         $upload,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php
index 8c12e5528..386029c7c 100644
--- a/src/applications/macro/controller/PhabricatorMacroViewController.php
+++ b/src/applications/macro/controller/PhabricatorMacroViewController.php
@@ -1,202 +1,202 @@
 <?php
 
 final class PhabricatorMacroViewController
   extends PhabricatorMacroController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $macro = id(new PhabricatorMacroQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needFiles(true)
       ->executeOne();
     if (!$macro) {
       return new Aphront404Response();
     }
 
     $title_short = pht('Macro "%s"', $macro->getName());
     $title_long  = pht('Image Macro "%s"', $macro->getName());
 
     $curtain = $this->buildCurtain($macro);
     $subheader = $this->buildSubheaderView($macro);
     $file = $this->buildFileView($macro);
     $details = $this->buildPropertySectionView($macro);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($macro->getName());
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $macro,
       new PhabricatorMacroTransactionQuery());
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setPolicyObject($macro)
       ->setHeader($macro->getName())
       ->setHeaderIcon('fa-file-image-o');
 
     if (!$macro->getIsDisabled()) {
       $header->setStatus('fa-check', 'bluegrey', pht('Active'));
     } else {
       $header->setStatus('fa-ban', 'red', pht('Archived'));
     }
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     $comment_header = $is_serious
       ? pht('Add Comment')
       : pht('Grovel in Awe');
 
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $macro->getPHID());
 
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($macro->getPHID())
       ->setDraft($draft)
       ->setHeaderText($comment_header)
       ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/'))
       ->setSubmitButtonName(pht('Add Comment'));
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $timeline,
         $add_comment_form,
       ))
-      ->addPropertySection(pht('MACRO'), $file)
-      ->addPropertySection(pht('DETAILS'), $details);
+      ->addPropertySection(pht('Macro'), $file)
+      ->addPropertySection(pht('Details'), $details);
 
     return $this->newPage()
       ->setTitle($title_short)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($macro->getPHID()))
       ->appendChild($view);
   }
 
   private function buildCurtain(
     PhabricatorFileImageMacro $macro) {
     $can_manage = $this->hasApplicationCapability(
       PhabricatorMacroManageCapability::CAPABILITY);
 
     $curtain = $this->newCurtainView($macro);
 
     $curtain->addAction(
         id(new PhabricatorActionView())
         ->setName(pht('Edit Macro'))
         ->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/'))
         ->setDisabled(!$can_manage)
         ->setWorkflow(!$can_manage)
         ->setIcon('fa-pencil'));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Audio'))
         ->setHref($this->getApplicationURI('/audio/'.$macro->getID().'/'))
         ->setDisabled(!$can_manage)
         ->setWorkflow(!$can_manage)
         ->setIcon('fa-music'));
 
     if ($macro->getIsDisabled()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Activate Macro'))
           ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
           ->setWorkflow(true)
           ->setDisabled(!$can_manage)
           ->setIcon('fa-check'));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Archive Macro'))
           ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
           ->setWorkflow(true)
           ->setDisabled(!$can_manage)
           ->setIcon('fa-ban'));
     }
 
     return $curtain;
   }
 
   private function buildSubheaderView(
     PhabricatorFileImageMacro $macro) {
     $viewer = $this->getViewer();
 
     $author_phid = $macro->getAuthorPHID();
 
     $author = $viewer->renderHandle($author_phid)->render();
     $date = phabricator_datetime($macro->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('Masterfully imagined by %s on %s.', $author, $date);
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
   }
 
   private function buildPropertySectionView(
     PhabricatorFileImageMacro $macro) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     switch ($macro->getAudioBehavior()) {
       case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE:
         $view->addProperty(pht('Audio Behavior'), pht('Play Once'));
         break;
       case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP:
         $view->addProperty(pht('Audio Behavior'), pht('Loop'));
         break;
     }
 
     $audio_phid = $macro->getAudioPHID();
     if ($audio_phid) {
       $view->addProperty(
         pht('Audio'),
         $viewer->renderHandle($audio_phid));
     }
 
     if ($view->hasAnyProperties()) {
       return $view;
     }
 
     return null;
   }
 
   private function buildFileView(
     PhabricatorFileImageMacro $macro) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $file = $macro->getFile();
     if ($file) {
       $view->addImageContent(
         phutil_tag(
           'img',
           array(
             'src'     => $file->getViewURI(),
             'class'   => 'phabricator-image-macro-hero',
           )));
       return $view;
     }
     return null;
   }
 
 }
diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php
index d245817f2..90fcda28e 100644
--- a/src/applications/maniphest/controller/ManiphestBatchEditController.php
+++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php
@@ -1,217 +1,226 @@
 <?php
 
 final class ManiphestBatchEditController extends ManiphestController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $this->requireApplicationCapability(
       ManiphestBulkEditCapability::CAPABILITY);
 
     $project = null;
     $board_id = $request->getInt('board');
     if ($board_id) {
       $project = id(new PhabricatorProjectQuery())
         ->setViewer($viewer)
         ->withIDs(array($board_id))
         ->executeOne();
       if (!$project) {
         return new Aphront404Response();
       }
     }
 
     $task_ids = $request->getArr('batch');
     if (!$task_ids) {
       $task_ids = $request->getStrList('batch');
     }
 
     if (!$task_ids) {
       throw new Exception(
         pht(
           'No tasks are selected.'));
     }
 
     $tasks = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withIDs($task_ids)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->needSubscriberPHIDs(true)
       ->needProjectPHIDs(true)
       ->execute();
 
     if (!$tasks) {
       throw new Exception(
         pht("You don't have permission to edit any of the selected tasks."));
     }
 
     if ($project) {
       $cancel_uri = '/project/board/'.$project->getID().'/';
       $redirect_uri = $cancel_uri;
     } else {
       $cancel_uri = '/maniphest/';
       $redirect_uri = '/maniphest/?ids='.implode(',', mpull($tasks, 'getID'));
     }
 
     $actions = $request->getStr('actions');
     if ($actions) {
       $actions = phutil_json_decode($actions);
     }
 
     if ($request->isFormPost() && $actions) {
       $job = PhabricatorWorkerBulkJob::initializeNewJob(
         $viewer,
         new ManiphestTaskEditBulkJobType(),
         array(
           'taskPHIDs' => mpull($tasks, 'getPHID'),
           'actions' => $actions,
           'cancelURI' => $cancel_uri,
           'doneURI' => $redirect_uri,
         ));
 
       $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS;
 
       $xactions = array();
       $xactions[] = id(new PhabricatorWorkerBulkJobTransaction())
         ->setTransactionType($type_status)
         ->setNewValue(PhabricatorWorkerBulkJob::STATUS_CONFIRM);
 
       $editor = id(new PhabricatorWorkerBulkJobEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnMissingFields(true)
         ->applyTransactions($job, $xactions);
 
       return id(new AphrontRedirectResponse())
         ->setURI($job->getMonitorURI());
     }
 
     $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
 
     $list = new ManiphestTaskListView();
     $list->setTasks($tasks);
     $list->setUser($viewer);
     $list->setHandles($handles);
 
     $template = new AphrontTokenizerTemplateView();
     $template = $template->render();
 
     $projects_source = new PhabricatorProjectDatasource();
     $mailable_source = new PhabricatorMetaMTAMailableDatasource();
     $mailable_source->setViewer($viewer);
     $owner_source = new ManiphestAssigneeDatasource();
     $owner_source->setViewer($viewer);
     $spaces_source = id(new PhabricatorSpacesNamespaceDatasource())
       ->setViewer($viewer);
 
     require_celerity_resource('maniphest-batch-editor');
     Javelin::initBehavior(
       'maniphest-batch-editor',
       array(
         'root' => 'maniphest-batch-edit-form',
         'tokenizerTemplate' => $template,
         'sources' => array(
           'project' => array(
             'src' => $projects_source->getDatasourceURI(),
             'placeholder' => $projects_source->getPlaceholderText(),
             'browseURI' => $projects_source->getBrowseURI(),
           ),
           'owner' => array(
             'src' => $owner_source->getDatasourceURI(),
             'placeholder' => $owner_source->getPlaceholderText(),
             'browseURI' => $owner_source->getBrowseURI(),
             'limit' => 1,
           ),
           'cc' => array(
             'src' => $mailable_source->getDatasourceURI(),
             'placeholder' => $mailable_source->getPlaceholderText(),
             'browseURI' => $mailable_source->getBrowseURI(),
           ),
           'spaces' => array(
             'src' => $spaces_source->getDatasourceURI(),
             'placeholder' => $spaces_source->getPlaceholderText(),
             'browseURI' => $spaces_source->getBrowseURI(),
             'limit' => 1,
           ),
         ),
         'input' => 'batch-form-actions',
         'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(),
         'statusMap'   => ManiphestTaskStatus::getTaskStatusMap(),
       ));
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('board', $board_id)
       ->setID('maniphest-batch-edit-form');
 
     foreach ($tasks as $task) {
       $form->appendChild(
         phutil_tag(
           'input',
           array(
             'type' => 'hidden',
             'name' => 'batch[]',
             'value' => $task->getID(),
           )));
     }
 
     $form->appendChild(
       phutil_tag(
         'input',
         array(
           'type' => 'hidden',
           'name' => 'actions',
           'id'   => 'batch-form-actions',
         )));
     $form->appendChild(
       id(new PHUIFormInsetView())
         ->setTitle(pht('Actions'))
         ->setRightButton(javelin_tag(
             'a',
             array(
               'href' => '#',
               'class' => 'button green',
               'sigil' => 'add-action',
               'mustcapture' => true,
             ),
             pht('Add Another Action')))
         ->setContent(javelin_tag(
           'table',
           array(
             'sigil' => 'maniphest-batch-actions',
             'class' => 'maniphest-batch-actions-table',
           ),
           '')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Update Tasks'))
           ->addCancelButton($cancel_uri));
 
     $title = pht('Batch Editor');
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Batch Editor'))
+      ->setHeaderIcon('fa-pencil-square-o');
 
     $task_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Selected Tasks'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setObjectList($list);
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Batch Editor'))
+      ->setHeaderText(pht('Actions'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $task_box,
         $form_box,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php
index c7f0cf218..f16281691 100644
--- a/src/applications/maniphest/controller/ManiphestReportController.php
+++ b/src/applications/maniphest/controller/ManiphestReportController.php
@@ -1,793 +1,793 @@
 <?php
 
 final class ManiphestReportController extends ManiphestController {
 
   private $view;
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $this->view = $request->getURIData('view');
 
     if ($request->isFormPost()) {
       $uri = $request->getRequestURI();
 
       $project = head($request->getArr('set_project'));
       $project = nonempty($project, null);
       $uri = $uri->alter('project', $project);
 
       $window = $request->getStr('set_window');
       $uri = $uri->alter('window', $window);
 
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI('/maniphest/report/'));
     $nav->addLabel(pht('Open Tasks'));
     $nav->addFilter('user', pht('By User'));
     $nav->addFilter('project', pht('By Project'));
     $nav->addLabel(pht('Burnup'));
     $nav->addFilter('burn', pht('Burnup Rate'));
 
     $this->view = $nav->selectFilter($this->view, 'user');
 
     require_celerity_resource('maniphest-report-css');
 
     switch ($this->view) {
       case 'burn':
         $core = $this->renderBurn();
         break;
       case 'user':
       case 'project':
         $core = $this->renderOpenTasks();
         break;
       default:
         return new Aphront404Response();
     }
 
+    $crumbs = $this->buildApplicationCrumbs()
+      ->addTextCrumb(pht('Reports'));
+
     $nav->appendChild($core);
-    $nav->setCrumbs(
-      $this->buildApplicationCrumbs()
-        ->addTextCrumb(pht('Reports')));
+    $title = pht('Maniphest Reports');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->setNavigation($nav);
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => pht('Maniphest Reports'),
-        'device' => false,
-      ));
   }
 
   public function renderBurn() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $handle = null;
 
     $project_phid = $request->getStr('project');
     if ($project_phid) {
       $phids = array($project_phid);
       $handles = $this->loadViewerHandles($phids);
       $handle = $handles[$project_phid];
     }
 
     $table = new ManiphestTransaction();
     $conn = $table->establishConnection('r');
 
     $joins = '';
     if ($project_phid) {
       $joins = qsprintf(
         $conn,
         'JOIN %T t ON x.objectPHID = t.phid
           JOIN %T p ON p.src = t.phid AND p.type = %d AND p.dst = %s',
         id(new ManiphestTask())->getTableName(),
         PhabricatorEdgeConfig::TABLE_NAME_EDGE,
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
         $project_phid);
     }
 
     $data = queryfx_all(
       $conn,
       'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
         WHERE transactionType = %s
         ORDER BY x.dateCreated ASC',
       $table->getTableName(),
       $joins,
       ManiphestTransaction::TYPE_STATUS);
 
     $stats = array();
     $day_buckets = array();
 
     $open_tasks = array();
 
     foreach ($data as $key => $row) {
 
       // NOTE: Hack to avoid json_decode().
       $oldv = trim($row['oldValue'], '"');
       $newv = trim($row['newValue'], '"');
 
       if ($oldv == 'null') {
         $old_is_open = false;
       } else {
         $old_is_open = ManiphestTaskStatus::isOpenStatus($oldv);
       }
 
       $new_is_open = ManiphestTaskStatus::isOpenStatus($newv);
 
       $is_open  = ($new_is_open && !$old_is_open);
       $is_close = ($old_is_open && !$new_is_open);
 
       $data[$key]['_is_open'] = $is_open;
       $data[$key]['_is_close'] = $is_close;
 
       if (!$is_open && !$is_close) {
         // This is either some kind of bogus event, or a resolution change
         // (e.g., resolved -> invalid). Just skip it.
         continue;
       }
 
       $day_bucket = phabricator_format_local_time(
         $row['dateCreated'],
         $viewer,
         'Yz');
       $day_buckets[$day_bucket] = $row['dateCreated'];
       if (empty($stats[$day_bucket])) {
         $stats[$day_bucket] = array(
           'open'  => 0,
           'close' => 0,
         );
       }
       $stats[$day_bucket][$is_close ? 'close' : 'open']++;
     }
 
     $template = array(
       'open'  => 0,
       'close' => 0,
     );
 
     $rows = array();
     $rowc = array();
     $last_month = null;
     $last_month_epoch = null;
     $last_week = null;
     $last_week_epoch = null;
     $week = null;
     $month = null;
 
     $last = last_key($stats) - 1;
     $period = $template;
 
     foreach ($stats as $bucket => $info) {
       $epoch = $day_buckets[$bucket];
 
       $week_bucket = phabricator_format_local_time(
         $epoch,
         $viewer,
         'YW');
       if ($week_bucket != $last_week) {
         if ($week) {
           $rows[] = $this->formatBurnRow(
             pht('Week of %s', phabricator_date($last_week_epoch, $viewer)),
             $week);
           $rowc[] = 'week';
         }
         $week = $template;
         $last_week = $week_bucket;
         $last_week_epoch = $epoch;
       }
 
       $month_bucket = phabricator_format_local_time(
         $epoch,
         $viewer,
         'Ym');
       if ($month_bucket != $last_month) {
         if ($month) {
           $rows[] = $this->formatBurnRow(
             phabricator_format_local_time($last_month_epoch, $viewer, 'F, Y'),
             $month);
           $rowc[] = 'month';
         }
         $month = $template;
         $last_month = $month_bucket;
         $last_month_epoch = $epoch;
       }
 
       $rows[] = $this->formatBurnRow(phabricator_date($epoch, $viewer), $info);
       $rowc[] = null;
       $week['open'] += $info['open'];
       $week['close'] += $info['close'];
       $month['open'] += $info['open'];
       $month['close'] += $info['close'];
       $period['open'] += $info['open'];
       $period['close'] += $info['close'];
     }
 
     if ($week) {
       $rows[] = $this->formatBurnRow(
         pht('Week To Date'),
         $week);
       $rowc[] = 'week';
     }
 
     if ($month) {
       $rows[] = $this->formatBurnRow(
         pht('Month To Date'),
         $month);
       $rowc[] = 'month';
     }
 
     $rows[] = $this->formatBurnRow(
       pht('All Time'),
       $period);
     $rowc[] = 'aggregate';
 
     $rows = array_reverse($rows);
     $rowc = array_reverse($rowc);
 
     $table = new AphrontTableView($rows);
     $table->setRowClasses($rowc);
     $table->setHeaders(
       array(
         pht('Period'),
         pht('Opened'),
         pht('Closed'),
         pht('Change'),
       ));
     $table->setColumnClasses(
       array(
         'right wide',
         'n',
         'n',
         'n',
       ));
 
     if ($handle) {
       $inst = pht(
         'NOTE: This table reflects tasks currently in '.
         'the project. If a task was opened in the past but added to '.
         'the project recently, it is counted on the day it was '.
         'opened, not the day it was categorized. If a task was part '.
         'of this project in the past but no longer is, it is not '.
         'counted at all.');
       $header = pht('Task Burn Rate for Project %s', $handle->renderLink());
       $caption = phutil_tag('p', array(), $inst);
     } else {
       $header = pht('Task Burn Rate for All Tasks');
       $caption = null;
     }
 
     if ($caption) {
       $caption = id(new PHUIInfoView())
         ->appendChild($caption)
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
     }
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText($header);
     if ($caption) {
       $panel->setInfoView($caption);
     }
     $panel->setTable($table);
 
     $tokens = array();
     if ($handle) {
       $tokens = array($handle);
     }
 
     $filter = $this->renderReportFilters($tokens, $has_window = false);
 
     $id = celerity_generate_unique_node_id();
     $chart = phutil_tag(
       'div',
       array(
         'id' => $id,
         'style' => 'border: 1px solid #BFCFDA; '.
                    'background-color: #fff; '.
                    'margin: 8px 16px; '.
                    'height: 400px; ',
       ),
       '');
 
     list($burn_x, $burn_y) = $this->buildSeries($data);
 
     require_celerity_resource('d3');
     require_celerity_resource('phui-chart-css');
 
     Javelin::initBehavior('line-chart', array(
       'hardpoint' => $id,
       'x' => array(
         $burn_x,
       ),
       'y' => array(
         $burn_y,
       ),
       'xformat' => 'epoch',
       'yformat' => 'int',
     ));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Burnup Rate'))
       ->appendChild($chart);
 
     return array($filter, $box, $panel);
   }
 
   private function renderReportFilters(array $tokens, $has_window) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new PhabricatorProjectDatasource())
           ->setLabel(pht('Project'))
           ->setLimit(1)
           ->setName('set_project')
           // TODO: This is silly, but this is Maniphest reports.
           ->setValue(mpull($tokens, 'getPHID')));
 
     if ($has_window) {
       list($window_str, $ignored, $window_error) = $this->getWindow();
       $form
         ->appendChild(
           id(new AphrontFormTextControl())
             ->setLabel(pht('Recently Means'))
             ->setName('set_window')
             ->setCaption(
               pht('Configure the cutoff for the "Recently Closed" column.'))
             ->setValue($window_str)
             ->setError($window_error));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Filter By Project')));
 
     $filter = new AphrontListFilterView();
     $filter->appendChild($form);
 
     return $filter;
   }
 
   private function buildSeries(array $data) {
     $out = array();
 
     $counter = 0;
     foreach ($data as $row) {
       $t = (int)$row['dateCreated'];
       if ($row['_is_close']) {
         --$counter;
         $out[$t] = $counter;
       } else if ($row['_is_open']) {
         ++$counter;
         $out[$t] = $counter;
       }
     }
 
     return array(array_keys($out), array_values($out));
   }
 
   private function formatBurnRow($label, $info) {
     $delta = $info['open'] - $info['close'];
     $fmt = number_format($delta);
     if ($delta > 0) {
       $fmt = '+'.$fmt;
       $fmt = phutil_tag('span', array('class' => 'red'), $fmt);
     } else {
       $fmt = phutil_tag('span', array('class' => 'green'), $fmt);
     }
 
     return array(
       $label,
       number_format($info['open']),
       number_format($info['close']),
       $fmt,
     );
   }
 
   public function renderOpenTasks() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants());
 
     switch ($this->view) {
       case 'project':
         $query->needProjectPHIDs(true);
         break;
     }
 
     $project_phid = $request->getStr('project');
     $project_handle = null;
     if ($project_phid) {
       $phids = array($project_phid);
       $handles = $this->loadViewerHandles($phids);
       $project_handle = $handles[$project_phid];
 
       $query->withEdgeLogicPHIDs(
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
         PhabricatorQueryConstraint::OPERATOR_OR,
         $phids);
     }
 
     $tasks = $query->execute();
 
     $recently_closed = $this->loadRecentlyClosedTasks();
 
     $date = phabricator_date(time(), $viewer);
 
     switch ($this->view) {
       case 'user':
         $result = mgroup($tasks, 'getOwnerPHID');
         $leftover = idx($result, '', array());
         unset($result['']);
 
         $result_closed = mgroup($recently_closed, 'getOwnerPHID');
         $leftover_closed = idx($result_closed, '', array());
         unset($result_closed['']);
 
         $base_link = '/maniphest/?assigned=';
         $leftover_name = phutil_tag('em', array(), pht('(Up For Grabs)'));
         $col_header = pht('User');
         $header = pht('Open Tasks by User and Priority (%s)', $date);
         break;
       case 'project':
         $result = array();
         $leftover = array();
         foreach ($tasks as $task) {
           $phids = $task->getProjectPHIDs();
           if ($phids) {
             foreach ($phids as $project_phid) {
               $result[$project_phid][] = $task;
             }
           } else {
             $leftover[] = $task;
           }
         }
 
         $result_closed = array();
         $leftover_closed = array();
         foreach ($recently_closed as $task) {
           $phids = $task->getProjectPHIDs();
           if ($phids) {
             foreach ($phids as $project_phid) {
               $result_closed[$project_phid][] = $task;
             }
           } else {
             $leftover_closed[] = $task;
           }
         }
 
         $base_link = '/maniphest/?projects=';
         $leftover_name = phutil_tag('em', array(), pht('(No Project)'));
         $col_header = pht('Project');
         $header = pht('Open Tasks by Project and Priority (%s)', $date);
         break;
     }
 
     $phids = array_keys($result);
     $handles = $this->loadViewerHandles($phids);
     $handles = msort($handles, 'getName');
 
     $order = $request->getStr('order', 'name');
     list($order, $reverse) = AphrontTableView::parseSort($order);
 
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-tooltips', array());
 
     $rows = array();
     $pri_total = array();
     foreach (array_merge($handles, array(null)) as $handle) {
       if ($handle) {
         if (($project_handle) &&
             ($project_handle->getPHID() == $handle->getPHID())) {
           // If filtering by, e.g., "bugs", don't show a "bugs" group.
           continue;
         }
 
         $tasks = idx($result, $handle->getPHID(), array());
         $name = phutil_tag(
           'a',
           array(
             'href' => $base_link.$handle->getPHID(),
           ),
           $handle->getName());
         $closed = idx($result_closed, $handle->getPHID(), array());
       } else {
         $tasks = $leftover;
         $name  = $leftover_name;
         $closed = $leftover_closed;
       }
 
       $taskv = $tasks;
       $tasks = mgroup($tasks, 'getPriority');
 
       $row = array();
       $row[] = $name;
       $total = 0;
       foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) {
         $n = count(idx($tasks, $pri, array()));
         if ($n == 0) {
           $row[] = '-';
         } else {
           $row[] = number_format($n);
         }
         $total += $n;
       }
       $row[] = number_format($total);
 
       list($link, $oldest_all) = $this->renderOldest($taskv);
       $row[] = $link;
 
       $normal_or_better = array();
       foreach ($taskv as $id => $task) {
         // TODO: This is sort of a hard-code for the default "normal" status.
         // When reports are more powerful, this should be made more general.
         if ($task->getPriority() < 50) {
           continue;
         }
         $normal_or_better[$id] = $task;
       }
 
       list($link, $oldest_pri) = $this->renderOldest($normal_or_better);
       $row[] = $link;
 
       if ($closed) {
         $task_ids = implode(',', mpull($closed, 'getID'));
         $row[] = phutil_tag(
           'a',
           array(
             'href' => '/maniphest/?ids='.$task_ids,
             'target' => '_blank',
           ),
           number_format(count($closed)));
       } else {
         $row[] = '-';
       }
 
       switch ($order) {
         case 'total':
           $row['sort'] = $total;
           break;
         case 'oldest-all':
           $row['sort'] = $oldest_all;
           break;
         case 'oldest-pri':
           $row['sort'] = $oldest_pri;
           break;
         case 'closed':
           $row['sort'] = count($closed);
           break;
         case 'name':
         default:
           $row['sort'] = $handle ? $handle->getName() : '~';
           break;
       }
 
       $rows[] = $row;
     }
 
     $rows = isort($rows, 'sort');
     foreach ($rows as $k => $row) {
       unset($rows[$k]['sort']);
     }
     if ($reverse) {
       $rows = array_reverse($rows);
     }
 
     $cname = array($col_header);
     $cclass = array('pri right wide');
     $pri_map = ManiphestTaskPriority::getShortNameMap();
     foreach ($pri_map as $pri => $label) {
       $cname[] = $label;
       $cclass[] = 'n';
     }
     $cname[] = pht('Total');
     $cclass[] = 'n';
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => pht('Oldest open task.'),
           'size' => 200,
         ),
       ),
       pht('Oldest (All)'));
     $cclass[] = 'n';
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => pht(
             'Oldest open task, excluding those with Low or Wishlist priority.'),
           'size' => 200,
         ),
       ),
       pht('Oldest (Pri)'));
     $cclass[] = 'n';
 
     list($ignored, $window_epoch) = $this->getWindow();
     $edate = phabricator_datetime($window_epoch, $viewer);
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip'  => pht('Closed after %s', $edate),
           'size' => 260,
         ),
       ),
       pht('Recently Closed'));
     $cclass[] = 'n';
 
     $table = new AphrontTableView($rows);
     $table->setHeaders($cname);
     $table->setColumnClasses($cclass);
     $table->makeSortable(
       $request->getRequestURI(),
       'order',
       $order,
       $reverse,
       array(
         'name',
         null,
         null,
         null,
         null,
         null,
         null,
         'total',
         'oldest-all',
         'oldest-pri',
         'closed',
       ));
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText($header);
     $panel->setTable($table);
 
     $tokens = array();
     if ($project_handle) {
       $tokens = array($project_handle);
     }
     $filter = $this->renderReportFilters($tokens, $has_window = true);
 
     return array($filter, $panel);
   }
 
 
   /**
    * Load all the tasks that have been recently closed.
    */
   private function loadRecentlyClosedTasks() {
     list($ignored, $window_epoch) = $this->getWindow();
 
     $table = new ManiphestTask();
     $xtable = new ManiphestTransaction();
     $conn_r = $table->establishConnection('r');
 
     // TODO: Gross. This table is not meant to be queried like this. Build
     // real stats tables.
 
     $open_status_list = array();
     foreach (ManiphestTaskStatus::getOpenStatusConstants() as $constant) {
       $open_status_list[] = json_encode((string)$constant);
     }
 
     $rows = queryfx_all(
       $conn_r,
       'SELECT t.id FROM %T t JOIN %T x ON x.objectPHID = t.phid
         WHERE t.status NOT IN (%Ls)
         AND x.oldValue IN (null, %Ls)
         AND x.newValue NOT IN (%Ls)
         AND t.dateModified >= %d
         AND x.dateCreated >= %d',
       $table->getTableName(),
       $xtable->getTableName(),
       ManiphestTaskStatus::getOpenStatusConstants(),
       $open_status_list,
       $open_status_list,
       $window_epoch,
       $window_epoch);
 
     if (!$rows) {
       return array();
     }
 
     $ids = ipull($rows, 'id');
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer($this->getRequest()->getUser())
       ->withIDs($ids);
 
     switch ($this->view) {
       case 'project':
         $query->needProjectPHIDs(true);
         break;
     }
 
     return $query->execute();
   }
 
   /**
    * Parse the "Recently Means" filter into:
    *
    *    - A string representation, like "12 AM 7 days ago" (default);
    *    - a locale-aware epoch representation; and
    *    - a possible error.
    */
   private function getWindow() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago');
 
     $error = null;
     $window_epoch = null;
 
     // Do locale-aware parsing so that the user's timezone is assumed for
     // time windows like "3 PM", rather than assuming the server timezone.
 
     $window_epoch = PhabricatorTime::parseLocalTime($window_str, $viewer);
     if (!$window_epoch) {
       $error = 'Invalid';
       $window_epoch = time() - (60 * 60 * 24 * 7);
     }
 
     // If the time ends up in the future, convert it to the corresponding time
     // and equal distance in the past. This is so users can type "6 days" (which
     // means "6 days from now") and get the behavior of "6 days ago", rather
     // than no results (because the window epoch is in the future). This might
     // be a little confusing because it casues "tomorrow" to mean "yesterday"
     // and "2022" (or whatever) to mean "ten years ago", but these inputs are
     // nonsense anyway.
 
     if ($window_epoch > time()) {
       $window_epoch = time() - ($window_epoch - time());
     }
 
     return array($window_str, $window_epoch, $error);
   }
 
   private function renderOldest(array $tasks) {
     assert_instances_of($tasks, 'ManiphestTask');
     $oldest = null;
     foreach ($tasks as $id => $task) {
       if (($oldest === null) ||
           ($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) {
         $oldest = $id;
       }
     }
 
     if ($oldest === null) {
       return array('-', 0);
     }
 
     $oldest = $tasks[$oldest];
 
     $raw_age = (time() - $oldest->getDateCreated());
     $age = number_format($raw_age / (24 * 60 * 60)).' d';
 
     $link = javelin_tag(
       'a',
       array(
         'href'  => '/T'.$oldest->getID(),
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(),
         ),
         'target' => '_blank',
       ),
       $age);
 
     return array($link, $raw_age);
   }
 
 }
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
index 285f919ce..1007672e2 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -1,364 +1,364 @@
 <?php
 
 final class ManiphestTaskDetailController extends ManiphestController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $task = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needSubscriberPHIDs(true)
       ->executeOne();
     if (!$task) {
       return new Aphront404Response();
     }
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $task,
       PhabricatorCustomField::ROLE_VIEW);
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($task);
 
     $edit_engine = id(new ManiphestEditEngine())
       ->setViewer($viewer)
       ->setTargetObject($task);
 
     $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST;
     $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
     $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
     $e_rev    = ManiphestTaskHasRevisionEdgeType::EDGECONST;
     $e_mock   = ManiphestTaskHasMockEdgeType::EDGECONST;
 
     $phid = $task->getPHID();
 
     $query = id(new PhabricatorEdgeQuery())
       ->withSourcePHIDs(array($phid))
       ->withEdgeTypes(
         array(
           $e_commit,
           $e_dep_on,
           $e_dep_by,
           $e_rev,
           $e_mock,
         ));
     $edges = idx($query->execute(), $phid);
     $phids = array_fill_keys($query->getDestinationPHIDs(), true);
 
     if ($task->getOwnerPHID()) {
       $phids[$task->getOwnerPHID()] = true;
     }
     $phids[$task->getAuthorPHID()] = true;
 
     $phids = array_keys($phids);
     $handles = $viewer->loadHandles($phids);
 
     $timeline = $this->buildTransactionTimeline(
       $task,
       new ManiphestTransactionQuery());
 
     $monogram = $task->getMonogram();
     $crumbs = $this->buildApplicationCrumbs()
       ->addTextCrumb($monogram)
       ->setBorder(true);
 
     $header = $this->buildHeaderView($task);
     $details = $this->buildPropertyView($task, $field_list, $edges, $handles);
     $description = $this->buildDescriptionView($task);
     $curtain = $this->buildCurtain($task, $edit_engine);
 
     $title = pht('%s %s', $monogram, $task->getTitle());
 
     $comment_view = $edit_engine
       ->buildEditEngineCommentView($task);
 
     $timeline->setQuoteRef($monogram);
     $comment_view->setTransactionTimeline($timeline);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $timeline,
         $comment_view,
       ))
-      ->addPropertySection(pht('DESCRIPTION'), $description)
-      ->addPropertySection(pht('DETAILS'), $details);
+      ->addPropertySection(pht('Description'), $description)
+      ->addPropertySection(pht('Details'), $details);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(
         array(
           $task->getPHID(),
         ))
       ->appendChild(
         array(
           $view,
       ));
 
   }
 
   private function buildHeaderView(ManiphestTask $task) {
     $view = id(new PHUIHeaderView())
       ->setHeader($task->getTitle())
       ->setUser($this->getRequest()->getUser())
       ->setPolicyObject($task);
 
     $priority_name = ManiphestTaskPriority::getTaskPriorityName(
       $task->getPriority());
     $priority_color = ManiphestTaskPriority::getTaskPriorityColor(
       $task->getPriority());
 
     $status = $task->getStatus();
     $status_name = ManiphestTaskStatus::renderFullDescription(
       $status, $priority_name, $priority_color);
     $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name);
 
     $view->setHeaderIcon(ManiphestTaskStatus::getStatusIcon(
       $task->getStatus()).' '.$priority_color);
 
     if (ManiphestTaskPoints::getIsEnabled()) {
       $points = $task->getPoints();
       if ($points !== null) {
         $points_name = pht('%s %s',
           $task->getPoints(),
           ManiphestTaskPoints::getPointsLabel());
         $tag = id(new PHUITagView())
           ->setName($points_name)
           ->setShade('blue')
           ->setType(PHUITagView::TYPE_SHADE);
 
         $view->addTag($tag);
       }
     }
 
     return $view;
   }
 
 
   private function buildCurtain(
     ManiphestTask $task,
     PhabricatorEditEngine $edit_engine) {
     $viewer = $this->getViewer();
 
     $id = $task->getID();
     $phid = $task->getPHID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $task,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($task);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Task'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("/task/edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Merge Duplicates In'))
         ->setHref("/search/attach/{$phid}/TASK/merge/")
         ->setWorkflow(true)
         ->setIcon('fa-compress')
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
     $edit_config = $edit_engine->loadDefaultEditConfiguration();
     $can_create = (bool)$edit_config;
 
     $can_reassign = $edit_engine->hasEditAccessToTransaction(
       ManiphestTransaction::TYPE_OWNER);
 
     if ($can_create) {
       $form_key = $edit_config->getIdentifier();
       $edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
         ->setQueryParam('parent', $id)
         ->setQueryParam('template', $id)
         ->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
       $edit_uri = $this->getApplicationURI($edit_uri);
     } else {
       // TODO: This will usually give us a somewhat-reasonable error page, but
       // could be a bit cleaner.
       $edit_uri = "/task/edit/{$id}/";
       $edit_uri = $this->getApplicationURI($edit_uri);
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Create Subtask'))
         ->setHref($edit_uri)
         ->setIcon('fa-level-down')
         ->setDisabled(!$can_create)
         ->setWorkflow(!$can_create));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Blocking Tasks'))
         ->setHref("/search/attach/{$phid}/TASK/blocks/")
         ->setWorkflow(true)
         ->setIcon('fa-link')
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
 
     $owner_phid = $task->getOwnerPHID();
     $author_phid = $task->getAuthorPHID();
     $handles = $viewer->loadHandles(array($owner_phid, $author_phid));
 
     if ($owner_phid) {
       $image_uri = $handles[$owner_phid]->getImageURI();
       $image_href = $handles[$owner_phid]->getURI();
       $owner = $viewer->renderHandle($owner_phid)->render();
       $content = phutil_tag('strong', array(), $owner);
       $assigned_to = id(new PHUIHeadThingView())
         ->setImage($image_uri)
         ->setImageHref($image_href)
         ->setContent($content);
     } else {
       $assigned_to = phutil_tag('em', array(), pht('None'));
     }
 
     $curtain->newPanel()
       ->setHeaderText(pht('Assigned To'))
       ->appendChild($assigned_to);
 
     $author_uri = $handles[$author_phid]->getImageURI();
     $author_href = $handles[$author_phid]->getURI();
     $author = $viewer->renderHandle($author_phid)->render();
     $content = phutil_tag('strong', array(), $author);
     $date = phabricator_date($task->getDateCreated(), $viewer);
     $content = pht('%s, %s', $content, $date);
     $authored_by = id(new PHUIHeadThingView())
       ->setImage($author_uri)
       ->setImageHref($author_href)
       ->setContent($content);
 
     $curtain->newPanel()
       ->setHeaderText(pht('Authored By'))
       ->appendChild($authored_by);
 
     return $curtain;
   }
 
   private function buildPropertyView(
     ManiphestTask $task,
     PhabricatorCustomFieldList $field_list,
     array $edges,
     $handles) {
 
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $source = $task->getOriginalEmailSource();
     if ($source) {
       $subject = '[T'.$task->getID().'] '.$task->getTitle();
       $view->addProperty(
         pht('From Email'),
         phutil_tag(
           'a',
           array(
             'href' => 'mailto:'.$source.'?subject='.$subject,
           ),
           $source));
     }
 
     $edge_types = array(
       ManiphestTaskDependedOnByTaskEdgeType::EDGECONST
         => pht('Blocks'),
       ManiphestTaskDependsOnTaskEdgeType::EDGECONST
         => pht('Blocked By'),
       ManiphestTaskHasRevisionEdgeType::EDGECONST
         => pht('Differential Revisions'),
       ManiphestTaskHasMockEdgeType::EDGECONST
         => pht('Pholio Mocks'),
     );
 
     $revisions_commits = array();
 
     $commit_phids = array_keys(
       $edges[ManiphestTaskHasCommitEdgeType::EDGECONST]);
     if ($commit_phids) {
       $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST;
       $drev_edges = id(new PhabricatorEdgeQuery())
         ->withSourcePHIDs($commit_phids)
         ->withEdgeTypes(array($commit_drev))
         ->execute();
 
       foreach ($commit_phids as $phid) {
         $revisions_commits[$phid] = $handles->renderHandle($phid)
           ->setShowHovercard(true);
         $revision_phid = key($drev_edges[$phid][$commit_drev]);
         $revision_handle = $handles->getHandleIfExists($revision_phid);
         if ($revision_handle) {
           $task_drev = ManiphestTaskHasRevisionEdgeType::EDGECONST;
           unset($edges[$task_drev][$revision_phid]);
           $revisions_commits[$phid] = hsprintf(
             '%s / %s',
             $revision_handle->renderHovercardLink($revision_handle->getName()),
             $revisions_commits[$phid]);
         }
       }
     }
 
     foreach ($edge_types as $edge_type => $edge_name) {
       if ($edges[$edge_type]) {
         $edge_handles = $viewer->loadHandles(array_keys($edges[$edge_type]));
         $view->addProperty(
           $edge_name,
           $edge_handles->renderList());
       }
     }
 
     if ($revisions_commits) {
       $view->addProperty(
         pht('Commits'),
         phutil_implode_html(phutil_tag('br'), $revisions_commits));
     }
 
     $field_list->appendFieldsToPropertyList(
       $task,
       $viewer,
       $view);
 
     if ($view->hasAnyProperties()) {
       return $view;
     }
 
     return null;
   }
 
   private function buildDescriptionView(ManiphestTask $task) {
     $viewer = $this->getViewer();
 
     $section = null;
 
     $description = $task->getDescription();
     if (strlen($description)) {
       $section = new PHUIPropertyListView();
       $section->addTextContent(
         phutil_tag(
           'div',
           array(
             'class' => 'phabricator-remarkup',
           ),
           id(new PHUIRemarkupView($viewer, $description))
             ->setContextObject($task)));
     }
 
     return $section;
   }
 
 }
diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
index 273c0c547..8e5893a9b 100644
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -1,322 +1,461 @@
 <?php
 
 final class ManiphestEditEngine
   extends PhabricatorEditEngine {
 
   const ENGINECONST = 'maniphest.task';
 
   public function getEngineName() {
     return pht('Maniphest Tasks');
   }
 
   public function getSummaryHeader() {
     return pht('Configure Maniphest Task Forms');
   }
 
   public function getSummaryText() {
     return pht('Configure how users create and edit tasks.');
   }
 
   public function getEngineApplicationClass() {
     return 'PhabricatorManiphestApplication';
   }
 
   protected function newEditableObject() {
     return ManiphestTask::initializeNewTask($this->getViewer());
   }
 
   protected function newObjectQuery() {
     return id(new ManiphestTaskQuery());
   }
 
   protected function getObjectCreateTitleText($object) {
     return pht('Create New Task');
   }
 
   protected function getObjectEditTitleText($object) {
     return pht('Edit Task: %s', $object->getTitle());
   }
 
   protected function getObjectEditShortText($object) {
     return $object->getMonogram();
   }
 
   protected function getObjectCreateShortText() {
     return pht('Create Task');
   }
 
   protected function getObjectName() {
     return pht('Task');
   }
 
   protected function getEditorURI() {
     return $this->getApplication()->getApplicationURI('task/edit/');
   }
 
   protected function getCommentViewHeaderText($object) {
     return pht('Weigh In');
   }
 
   protected function getCommentViewButtonText($object) {
     return pht('Set Sail for Adventure');
   }
 
   protected function getObjectViewURI($object) {
     return '/'.$object->getMonogram();
   }
 
   protected function buildCustomEditFields($object) {
     $status_map = $this->getTaskStatusMap($object);
     $priority_map = $this->getTaskPriorityMap($object);
 
     if ($object->isClosed()) {
       $default_status = ManiphestTaskStatus::getDefaultStatus();
     } else {
       $default_status = ManiphestTaskStatus::getDefaultClosedStatus();
     }
 
     if ($object->getOwnerPHID()) {
       $owner_value = array($object->getOwnerPHID());
     } else {
       $owner_value = array($this->getViewer()->getPHID());
     }
 
+    $column_documentation = pht(<<<EODOCS
+You can use this transaction type to create a task into a particular workboard
+column, or move an existing task between columns.
+
+The transaction value can be specified in several forms. Some are simpler but
+less powerful, while others are more complex and more powerful.
+
+The simplest valid value is a single column PHID:
+
+```lang=json
+"PHID-PCOL-1111"
+```
+
+This will move the task into that column, or create the task into that column
+if you are creating a new task. If the task is currently on the board, it will
+be moved out of any exclusive columns. If the task is not currently on the
+board, it will be added to the board.
+
+You can also perform multiple moves at the same time by passing a list of
+PHIDs:
+
+```lang=json
+["PHID-PCOL-2222", "PHID-PCOL-3333"]
+```
+
+This is equivalent to performing each move individually.
+
+The most complex and most powerful form uses a dictionary to provide additional
+information about the move, including an optional specific position within the
+column.
+
+The target column should be identified as `columnPHID`, and you may select a
+position by passing either `beforePHID` or `afterPHID`, specifying the PHID of
+a task currently in the column that you want to move this task before or after:
+
+```lang=json
+[
+  {
+    "columnPHID": "PHID-PCOL-4444",
+    "beforePHID": "PHID-TASK-5555"
+  }
+]
+```
+
+Note that this affects only the "natural" position of the task. The task
+position when the board is sorted by some other attribute (like priority)
+depends on that attribute value: change a task's priority to move it on
+priority-sorted boards.
+EODOCS
+      );
+
+    $column_map = $this->getColumnMap($object);
+
     $fields = array(
       id(new PhabricatorHandlesEditField())
         ->setKey('parent')
         ->setLabel(pht('Parent Task'))
         ->setDescription(pht('Task to make this a subtask of.'))
         ->setConduitDescription(pht('Create as a subtask of another task.'))
         ->setConduitTypeDescription(pht('PHID of the parent task.'))
         ->setAliases(array('parentPHID'))
         ->setTransactionType(ManiphestTransaction::TYPE_PARENT)
         ->setHandleParameterType(new ManiphestTaskListHTTPParameterType())
         ->setSingleValue(null)
         ->setIsReorderable(false)
         ->setIsDefaultable(false)
         ->setIsLockable(false),
-      id(new PhabricatorHandlesEditField())
+      id(new PhabricatorColumnsEditField())
         ->setKey('column')
         ->setLabel(pht('Column'))
-        ->setDescription(pht('Workboard column to create this task into.'))
-        ->setConduitDescription(pht('Create into a workboard column.'))
-        ->setConduitTypeDescription(pht('PHID of workboard column.'))
-        ->setAliases(array('columnPHID'))
-        ->setTransactionType(ManiphestTransaction::TYPE_COLUMN)
-        ->setSingleValue(null)
-        ->setIsInvisible(true)
+        ->setDescription(pht('Create a task in a workboard column.'))
+        ->setConduitDescription(
+          pht('Move a task to one or more workboard columns.'))
+        ->setConduitTypeDescription(
+          pht('List of columns to move the task to.'))
+        ->setConduitDocumentation($column_documentation)
+        ->setAliases(array('columnPHID', 'columns', 'columnPHIDs'))
+        ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
         ->setIsReorderable(false)
         ->setIsDefaultable(false)
-        ->setIsLockable(false),
+        ->setIsLockable(false)
+        ->setCommentActionLabel(pht('Move on Workboard'))
+        ->setCommentActionOrder(2000)
+        ->setColumnMap($column_map),
       id(new PhabricatorTextEditField())
         ->setKey('title')
         ->setLabel(pht('Title'))
         ->setDescription(pht('Name of the task.'))
         ->setConduitDescription(pht('Rename the task.'))
         ->setConduitTypeDescription(pht('New task name.'))
         ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
         ->setIsRequired(true)
         ->setValue($object->getTitle()),
       id(new PhabricatorUsersEditField())
         ->setKey('owner')
         ->setAliases(array('ownerPHID', 'assign', 'assigned'))
         ->setLabel(pht('Assigned To'))
         ->setDescription(pht('User who is responsible for the task.'))
         ->setConduitDescription(pht('Reassign the task.'))
         ->setConduitTypeDescription(
           pht('New task owner, or `null` to unassign.'))
         ->setTransactionType(ManiphestTransaction::TYPE_OWNER)
         ->setIsCopyable(true)
         ->setSingleValue($object->getOwnerPHID())
         ->setCommentActionLabel(pht('Assign / Claim'))
         ->setCommentActionValue($owner_value),
       id(new PhabricatorSelectEditField())
         ->setKey('status')
         ->setLabel(pht('Status'))
         ->setDescription(pht('Status of the task.'))
         ->setConduitDescription(pht('Change the task status.'))
         ->setConduitTypeDescription(pht('New task status constant.'))
         ->setTransactionType(ManiphestTransaction::TYPE_STATUS)
         ->setIsCopyable(true)
         ->setValue($object->getStatus())
         ->setOptions($status_map)
         ->setCommentActionLabel(pht('Change Status'))
         ->setCommentActionValue($default_status),
       id(new PhabricatorSelectEditField())
         ->setKey('priority')
         ->setLabel(pht('Priority'))
         ->setDescription(pht('Priority of the task.'))
         ->setConduitDescription(pht('Change the priority of the task.'))
         ->setConduitTypeDescription(pht('New task priority constant.'))
         ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
         ->setIsCopyable(true)
         ->setValue($object->getPriority())
         ->setOptions($priority_map)
         ->setCommentActionLabel(pht('Change Priority')),
     );
 
     if (ManiphestTaskPoints::getIsEnabled()) {
       $points_label = ManiphestTaskPoints::getPointsLabel();
       $action_label = ManiphestTaskPoints::getPointsActionLabel();
 
       $fields[] = id(new PhabricatorPointsEditField())
         ->setKey('points')
         ->setLabel($points_label)
         ->setDescription(pht('Point value of the task.'))
         ->setConduitDescription(pht('Change the task point value.'))
         ->setConduitTypeDescription(pht('New task point value.'))
         ->setTransactionType(ManiphestTransaction::TYPE_POINTS)
         ->setIsCopyable(true)
         ->setValue($object->getPoints())
         ->setCommentActionLabel($action_label);
     }
 
     $fields[] = id(new PhabricatorRemarkupEditField())
       ->setKey('description')
       ->setLabel(pht('Description'))
       ->setDescription(pht('Task description.'))
       ->setConduitDescription(pht('Update the task description.'))
       ->setConduitTypeDescription(pht('New task description.'))
       ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
       ->setValue($object->getDescription())
       ->setPreviewPanel(
         id(new PHUIRemarkupPreviewPanel())
           ->setHeader(pht('Description Preview')));
 
     return $fields;
   }
 
   private function getTaskStatusMap(ManiphestTask $task) {
     $status_map = ManiphestTaskStatus::getTaskStatusMap();
 
     $current_status = $task->getStatus();
 
     // If the current status is something we don't recognize (maybe an older
     // status which was deleted), put a dummy entry in the status map so that
     // saving the form doesn't destroy any data by accident.
     if (idx($status_map, $current_status) === null) {
       $status_map[$current_status] = pht('<Unknown: %s>', $current_status);
     }
 
     $dup_status = ManiphestTaskStatus::getDuplicateStatus();
     foreach ($status_map as $status => $status_name) {
       // Always keep the task's current status.
       if ($status == $current_status) {
         continue;
       }
 
       // Don't allow tasks to be changed directly into "Closed, Duplicate"
       // status. Instead, you have to merge them. See T4819.
       if ($status == $dup_status) {
         unset($status_map[$status]);
         continue;
       }
 
       // Don't let new or existing tasks be moved into a disabled status.
       if (ManiphestTaskStatus::isDisabledStatus($status)) {
         unset($status_map[$status]);
         continue;
       }
     }
 
     return $status_map;
   }
 
   private function getTaskPriorityMap(ManiphestTask $task) {
     $priority_map = ManiphestTaskPriority::getTaskPriorityMap();
     $current_priority = $task->getPriority();
 
     // If the current value isn't a legitimate one, put it in the dropdown
     // anyway so saving the form doesn't cause a side effects.
     if (idx($priority_map, $current_priority) === null) {
       $priority_map[$current_priority] = pht(
         '<Unknown: %s>',
         $current_priority);
     }
 
     foreach ($priority_map as $priority => $priority_name) {
       // Always keep the current priority.
       if ($priority == $current_priority) {
         continue;
       }
 
       if (ManiphestTaskPriority::isDisabledPriority($priority)) {
         unset($priority_map[$priority]);
         continue;
       }
     }
 
     return $priority_map;
   }
 
   protected function newEditResponse(
     AphrontRequest $request,
     $object,
     array $xactions) {
 
     if ($request->isAjax()) {
       // Reload the task to make sure we pick up the final task state.
       $viewer = $this->getViewer();
       $task = id(new ManiphestTaskQuery())
         ->setViewer($viewer)
         ->withIDs(array($object->getID()))
         ->needSubscriberPHIDs(true)
         ->needProjectPHIDs(true)
         ->executeOne();
 
       switch ($request->getStr('responseType')) {
         case 'card':
           return $this->buildCardResponse($task);
         default:
           return $this->buildListResponse($task);
       }
 
     }
 
     return parent::newEditResponse($request, $object, $xactions);
   }
 
   private function buildListResponse(ManiphestTask $task) {
     $controller = $this->getController();
 
     $payload = array(
       'tasks' => $controller->renderSingleTask($task),
       'data' => array(),
     );
 
     return id(new AphrontAjaxResponse())->setContent($payload);
   }
 
   private function buildCardResponse(ManiphestTask $task) {
     $controller = $this->getController();
     $request = $controller->getRequest();
     $viewer = $request->getViewer();
 
     $column_phid = $request->getStr('columnPHID');
 
     $visible_phids = $request->getStrList('visiblePHIDs');
     if (!$visible_phids) {
       $visible_phids = array();
     }
 
     $column = id(new PhabricatorProjectColumnQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($column_phid))
       ->executeOne();
     if (!$column) {
       return new Aphront404Response();
     }
 
     $board_phid = $column->getProjectPHID();
     $object_phid = $task->getPHID();
 
     return id(new PhabricatorBoardResponseEngine())
       ->setViewer($viewer)
       ->setBoardPHID($board_phid)
       ->setObjectPHID($object_phid)
       ->setVisiblePHIDs($visible_phids)
       ->buildResponse();
   }
 
+  private function getColumnMap(ManiphestTask $task) {
+    $phid = $task->getPHID();
+    if (!$phid) {
+      return array();
+    }
+
+    $board_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+      $phid,
+      PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+    if (!$board_phids) {
+      return array();
+    }
+
+    $viewer = $this->getViewer();
+
+    $layout_engine = id(new PhabricatorBoardLayoutEngine())
+      ->setViewer($viewer)
+      ->setBoardPHIDs($board_phids)
+      ->setObjectPHIDs(array($task->getPHID()))
+      ->executeLayout();
+
+    $map = array();
+    foreach ($board_phids as $board_phid) {
+      $in_columns = $layout_engine->getObjectColumns($board_phid, $phid);
+      $in_columns = mpull($in_columns, null, 'getPHID');
+
+      $all_columns = $layout_engine->getColumns($board_phid);
+      if (!$all_columns) {
+        // This could be a project with no workboard, or a project the viewer
+        // does not have permission to see.
+        continue;
+      }
+
+      $board = head($all_columns)->getProject();
+
+      $options = array();
+      foreach ($all_columns as $column) {
+        $name = $column->getDisplayName();
+
+        $is_hidden = $column->isHidden();
+        $is_selected = isset($in_columns[$column->getPHID()]);
+
+        // Don't show hidden, subproject or milestone columns in this map
+        // unless the object is currently in the column.
+        $skip_column = ($is_hidden || $column->getProxyPHID());
+        if ($skip_column) {
+          if (!$is_selected) {
+            continue;
+          }
+        }
+
+        if ($is_hidden) {
+          $name = pht('(%s)', $name);
+        }
+
+        if ($is_selected) {
+          $name = pht("\xE2\x97\x8F %s", $name);
+        } else {
+          $name = pht("\xE2\x97\x8B %s", $name);
+        }
+
+        $option = array(
+          'key' => $column->getPHID(),
+          'label' => $name,
+          'selected' => (bool)$is_selected,
+        );
+
+        $options[] = $option;
+      }
+
+      $map[] = array(
+        'label' => $board->getDisplayName(),
+        'options' => $options,
+      );
+    }
+
+    $map = isort($map, 'label');
+    $map = array_values($map);
+
+    return $map;
+  }
+
 
 }
diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
index b88c5a592..6c9a7d008 100644
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -1,1082 +1,1197 @@
 <?php
 
 final class ManiphestTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  private $moreValidationErrors = array();
+
   public function getEditorApplicationClass() {
     return 'PhabricatorManiphestApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('Maniphest Tasks');
   }
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $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_PROJECT_COLUMN;
     $types[] = ManiphestTransaction::TYPE_MERGED_INTO;
     $types[] = ManiphestTransaction::TYPE_MERGED_FROM;
     $types[] = ManiphestTransaction::TYPE_UNBLOCK;
     $types[] = ManiphestTransaction::TYPE_PARENT;
-    $types[] = ManiphestTransaction::TYPE_COLUMN;
     $types[] = ManiphestTransaction::TYPE_COVER_IMAGE;
     $types[] = ManiphestTransaction::TYPE_POINTS;
+    $types[] = PhabricatorTransactions::TYPE_COLUMNS;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
 
     return $types;
   }
 
   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_PROJECT_COLUMN:
-        // These are pre-populated.
-        return $xaction->getOldValue();
       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 ManiphestTransaction::TYPE_COLUMN:
+      case PhabricatorTransactions::TYPE_COLUMNS:
         return null;
     }
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     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_PROJECT_COLUMN:
       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 ManiphestTransaction::TYPE_COLUMN:
+      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;
     }
   }
 
   protected function transactionHasEffect(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $old = $xaction->getOldValue();
     $new = $xaction->getNewValue();
 
     switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_PROJECT_COLUMN:
-        $new_column_phids = $new['columnPHIDs'];
-        $old_column_phids = $old['columnPHIDs'];
-        sort($new_column_phids);
-        sort($old_column_phids);
-        return ($old !== $new);
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        return (bool)$new;
     }
 
     return parent::transactionHasEffect($object, $xaction);
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     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_PROJECT_COLUMN:
-        // these do external (edge) updates
-        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 ManiphestTransaction::TYPE_COLUMN:
+      case PhabricatorTransactions::TYPE_COLUMNS:
         return;
     }
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     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 ManiphestTransaction::TYPE_PROJECT_COLUMN:
-        $board_phid = idx($xaction->getNewValue(), 'projectPHID');
-        if (!$board_phid) {
-          throw new Exception(
-            pht(
-              "Expected '%s' in column transaction.",
-              'projectPHID'));
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        foreach ($xaction->getNewValue() as $move) {
+          $this->applyBoardMove($object, $move);
         }
-
-        $old_phids = idx($xaction->getOldValue(), 'columnPHIDs', array());
-        $new_phids = idx($xaction->getNewValue(), 'columnPHIDs', array());
-        if (count($new_phids) !== 1) {
-          throw new Exception(
-            pht(
-              "Expected exactly one '%s' in column transaction.",
-              'columnPHIDs'));
-        }
-
-        $before_phid = idx($xaction->getNewValue(), 'beforePHID');
-        $after_phid = idx($xaction->getNewValue(), 'afterPHID');
-
-        if (!$before_phid && !$after_phid && ($old_phids == $new_phids)) {
-          // If we are not moving the object between columns and also not
-          // reordering the position, this is a move on some other order
-          // (like priority). We can leave the positions untouched and just
-          // bail, there's no work to be done.
-          return;
-        }
-
-        // Otherwise, we're either moving between columns or adjusting the
-        // object's position in the "natural" ordering, so we do need to update
-        // some rows.
-
-        $object_phid = $object->getPHID();
-
-        // We're doing layout with the ominpotent viewer to make sure we don't
-        // remove positions in columns that exist, but which the actual actor
-        // can't see.
-        $omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
-
-        $select_phids = array($board_phid);
-
-        $descendants = id(new PhabricatorProjectQuery())
-          ->setViewer($omnipotent_viewer)
-          ->withAncestorProjectPHIDs($select_phids)
-          ->execute();
-        foreach ($descendants as $descendant) {
-          $select_phids[] = $descendant->getPHID();
-        }
-
-        $board_tasks = id(new ManiphestTaskQuery())
-          ->setViewer($omnipotent_viewer)
-          ->withEdgeLogicPHIDs(
-            PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
-            PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
-            array($select_phids))
-          ->execute();
-
-        $board_tasks = mpull($board_tasks, null, 'getPHID');
-        $board_tasks[$object_phid] = $object;
-
-        // Make sure tasks are sorted by ID, so we lay out new positions in
-        // a consistent way.
-        $board_tasks = msort($board_tasks, 'getID');
-
-        $object_phids = array_keys($board_tasks);
-
-        $engine = id(new PhabricatorBoardLayoutEngine())
-          ->setViewer($omnipotent_viewer)
-          ->setBoardPHIDs(array($board_phid))
-          ->setObjectPHIDs($object_phids)
-          ->executeLayout();
-
-        // TODO: This logic needs to be revised if we legitimately support
-        // multiple column positions.
-
-        // NOTE: When a task is newly created, it's implicitly added to the
-        // backlog but we don't currently record that in the "$old_phids". Just
-        // clean it up for now.
-        $columns = $engine->getObjectColumns($board_phid, $object_phid);
-        foreach ($columns as $column) {
-          $engine->queueRemovePosition(
-            $board_phid,
-            $column->getPHID(),
-            $object_phid);
-        }
-
-        // Remove all existing column positions on the board.
-        foreach ($old_phids as $column_phid) {
-          $engine->queueRemovePosition(
-            $board_phid,
-            $column_phid,
-            $object_phid);
-        }
-
-        // Add new positions.
-        foreach ($new_phids as $column_phid) {
-          if ($before_phid) {
-            $engine->queueAddPositionBefore(
-              $board_phid,
-              $column_phid,
-              $object_phid,
-              $before_phid);
-          } else if ($after_phid) {
-            $engine->queueAddPositionAfter(
-              $board_phid,
-              $column_phid,
-              $object_phid,
-              $after_phid);
-          } else {
-            $engine->queueAddPosition(
-              $board_phid,
-              $column_phid,
-              $object_phid);
-          }
-        }
-
-        $engine->applyPositionUpdates();
-
         break;
       default:
         break;
     }
   }
 
   protected function applyFinalEffects(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     // When we change the status of a task, update tasks this tasks blocks
     // with a message to the effect of "alincoln resolved blocking task Txxx."
     $unblock_xaction = null;
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
         case ManiphestTransaction::TYPE_STATUS:
           $unblock_xaction = $xaction;
           break;
       }
     }
 
     if ($unblock_xaction !== null) {
       $blocked_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $object->getPHID(),
         ManiphestTaskDependedOnByTaskEdgeType::EDGECONST);
       if ($blocked_phids) {
         // In theory we could apply these through policies, but that seems a
         // little bit surprising. For now, use the actor's vision.
         $blocked_tasks = id(new ManiphestTaskQuery())
           ->setViewer($this->getActor())
           ->withPHIDs($blocked_phids)
           ->needSubscriberPHIDs(true)
           ->needProjectPHIDs(true)
           ->execute();
 
         $old = $unblock_xaction->getOldValue();
         $new = $unblock_xaction->getNewValue();
 
         foreach ($blocked_tasks as $blocked_task) {
           $parent_xaction = id(new ManiphestTransaction())
             ->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK)
             ->setOldValue(array($object->getPHID() => $old))
             ->setNewValue(array($object->getPHID() => $new));
 
           if ($this->getIsNewObject()) {
             $parent_xaction->setMetadataValue('blocker.new', true);
           }
 
           id(new ManiphestTransactionEditor())
             ->setActor($this->getActor())
             ->setActingAsPHID($this->getActingAsPHID())
             ->setContentSource($this->getContentSource())
             ->setContinueOnNoEffect(true)
             ->setContinueOnMissingFields(true)
             ->applyTransactions($blocked_task, array($parent_xaction));
         }
       }
     }
 
     return $xactions;
   }
 
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function getMailSubjectPrefix() {
     return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
   }
 
   protected function getMailThreadID(PhabricatorLiskDAO $object) {
     return 'maniphest-task-'.$object->getPHID();
   }
 
   protected function getMailTo(PhabricatorLiskDAO $object) {
     $phids = array();
 
     if ($object->getOwnerPHID()) {
       $phids[] = $object->getOwnerPHID();
     }
     $phids[] = $this->getActingAsPHID();
 
     return $phids;
   }
 
   public function getMailTagsMap() {
     return array(
       ManiphestTransaction::MAILTAG_STATUS =>
         pht("A task's status changes."),
       ManiphestTransaction::MAILTAG_OWNER =>
         pht("A task's owner changes."),
       ManiphestTransaction::MAILTAG_PRIORITY =>
         pht("A task's priority changes."),
       ManiphestTransaction::MAILTAG_CC =>
         pht("A task's subscribers change."),
       ManiphestTransaction::MAILTAG_PROJECTS =>
         pht("A task's associated projects change."),
       ManiphestTransaction::MAILTAG_UNBLOCK =>
         pht('One of the tasks a task is blocked by changes status.'),
       ManiphestTransaction::MAILTAG_COLUMN =>
         pht('A task is moved between columns on a workboard.'),
       ManiphestTransaction::MAILTAG_COMMENT =>
         pht('Someone comments on a task.'),
       ManiphestTransaction::MAILTAG_OTHER =>
         pht('Other task activity not listed above occurs.'),
     );
   }
 
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new ManiphestReplyHandler())
       ->setMailReceiver($object);
   }
 
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     $id = $object->getID();
     $title = $object->getTitle();
 
     return id(new PhabricatorMetaMTAMail())
       ->setSubject("T{$id}: {$title}")
       ->addHeader('Thread-Topic', "T{$id}: ".$object->getOriginalTitle());
   }
 
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $body = parent::buildMailBody($object, $xactions);
 
     if ($this->getIsNewObject()) {
       $body->addRemarkupSection(
         pht('TASK DESCRIPTION'),
         $object->getDescription());
     }
 
     $body->addLinkSection(
       pht('TASK DETAIL'),
       PhabricatorEnv::getProductionURI('/T'.$object->getID()));
 
 
     $board_phids = array();
-    $type_column = ManiphestTransaction::TYPE_PROJECT_COLUMN;
+    $type_columns = PhabricatorTransactions::TYPE_COLUMNS;
     foreach ($xactions as $xaction) {
-      if ($xaction->getTransactionType() == $type_column) {
-        $new = $xaction->getNewValue();
-        $project_phid = idx($new, 'projectPHID');
-        if ($project_phid) {
-          $board_phids[] = $project_phid;
+      if ($xaction->getTransactionType() == $type_columns) {
+        $moves = $xaction->getNewValue();
+        foreach ($moves as $move) {
+          $board_phids[] = $move['boardPHID'];
         }
       }
     }
 
     if ($board_phids) {
       $projects = id(new PhabricatorProjectQuery())
         ->setViewer($this->requireActor())
         ->withPHIDs($board_phids)
         ->execute();
 
       foreach ($projects as $project) {
         $body->addLinkSection(
           pht('WORKBOARD'),
           PhabricatorEnv::getProductionURI(
             '/project/board/'.$project->getID().'/'));
       }
     }
 
 
     return $body;
   }
 
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return $this->shouldSendMail($object, $xactions);
   }
 
   protected function supportsSearch() {
     return true;
   }
 
   protected function shouldApplyHeraldRules(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function buildHeraldAdapter(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     return id(new HeraldManiphestTaskAdapter())
       ->setTask($object);
   }
 
   protected function requireCapabilities(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     parent::requireCapabilities($object, $xaction);
 
     $app_capability_map = array(
       ManiphestTransaction::TYPE_PRIORITY =>
         ManiphestEditPriorityCapability::CAPABILITY,
       ManiphestTransaction::TYPE_STATUS =>
         ManiphestEditStatusCapability::CAPABILITY,
       ManiphestTransaction::TYPE_OWNER =>
         ManiphestEditAssignCapability::CAPABILITY,
       PhabricatorTransactions::TYPE_EDIT_POLICY =>
         ManiphestEditPoliciesCapability::CAPABILITY,
       PhabricatorTransactions::TYPE_VIEW_POLICY =>
         ManiphestEditPoliciesCapability::CAPABILITY,
     );
 
 
     $transaction_type = $xaction->getTransactionType();
 
     $app_capability = null;
     if ($transaction_type == PhabricatorTransactions::TYPE_EDGE) {
       switch ($xaction->getMetadataValue('edge:type')) {
         case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
           $app_capability = ManiphestEditProjectsCapability::CAPABILITY;
           break;
       }
     } else {
       $app_capability = idx($app_capability_map, $transaction_type);
     }
 
     if ($app_capability) {
       $app = id(new PhabricatorApplicationQuery())
         ->setViewer($this->getActor())
         ->withClasses(array('PhabricatorManiphestApplication'))
         ->executeOne();
       PhabricatorPolicyFilter::requireCapability(
         $this->getActor(),
         $app,
         $app_capability);
     }
   }
 
   protected function adjustObjectForPolicyChecks(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $copy = parent::adjustObjectForPolicyChecks($object, $xactions);
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
         case ManiphestTransaction::TYPE_OWNER:
           $copy->setOwnerPHID($xaction->getNewValue());
           break;
         default:
           continue;
       }
     }
 
     return $copy;
   }
 
   /**
    * Get priorities for moving a task to a new priority.
    */
   public static function getEdgeSubpriority(
     $priority,
     $is_end) {
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withPriorities(array($priority))
       ->setLimit(1);
 
     if ($is_end) {
       $query->setOrderVector(array('-priority', '-subpriority', '-id'));
     } else {
       $query->setOrderVector(array('priority', 'subpriority', 'id'));
     }
 
     $result = $query->executeOne();
     $step = (double)(2 << 32);
 
     if ($result) {
       $base = $result->getSubpriority();
       if ($is_end) {
         $sub = ($base - $step);
       } else {
         $sub = ($base + $step);
       }
     } else {
       $sub = 0;
     }
 
     return array($priority, $sub);
   }
 
 
   /**
    * Get priorities for moving a task before or after another task.
    */
   public static function getAdjacentSubpriority(
     ManiphestTask $dst,
     $is_after,
     $allow_recursion = true) {
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
       ->withPriorities(array($dst->getPriority()))
       ->setLimit(1);
 
     if ($is_after) {
       $query->setAfterID($dst->getID());
     } else {
       $query->setBeforeID($dst->getID());
     }
 
     $adjacent = $query->executeOne();
 
     $base = $dst->getSubpriority();
     $step = (double)(2 << 32);
 
     // If we find an adjacent task, we average the two subpriorities and
     // return the result.
     if ($adjacent) {
       $epsilon = 0.01;
 
       // If the adjacent task has a subpriority that is identical or very
       // close to the task we're looking at, we're going to move it and all
       // tasks with the same subpriority a little farther down the subpriority
       // scale.
       if ($allow_recursion &&
           (abs($adjacent->getSubpriority() - $base) < $epsilon)) {
         $conn_w = $adjacent->establishConnection('w');
 
         $min = ($adjacent->getSubpriority() - ($epsilon));
         $max = ($adjacent->getSubpriority() + ($epsilon));
 
         // Get all of the tasks with the similar subpriorities to the adjacent
         // task, including the adjacent task itself.
         $query = id(new ManiphestTaskQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withPriorities(array($adjacent->getPriority()))
           ->withSubpriorityBetween($min, $max);
 
         if (!$is_after) {
           $query->setOrderVector(array('-priority', '-subpriority', '-id'));
         } else {
           $query->setOrderVector(array('priority', 'subpriority', 'id'));
         }
 
         $shift_all = $query->execute();
         $shift_last = last($shift_all);
 
         // Select the most extreme subpriority in the result set as the
         // base value.
         $shift_base = head($shift_all)->getSubpriority();
 
         // Find the subpriority before or after the task at the end of the
         // block.
         list($shift_pri, $shift_sub) = self::getAdjacentSubpriority(
           $shift_last,
           $is_after,
           $allow_recursion = false);
 
         $delta = ($shift_sub - $shift_base);
         $count = count($shift_all);
 
         $shift = array();
         $cursor = 1;
         foreach ($shift_all as $shift_task) {
           $shift_target = $shift_base + (($cursor / $count) * $delta);
           $cursor++;
 
           queryfx(
             $conn_w,
             'UPDATE %T SET subpriority = %f WHERE id = %d',
             $adjacent->getTableName(),
             $shift_target,
             $shift_task->getID());
 
           // If we're shifting the adjacent task, update it.
           if ($shift_task->getID() == $adjacent->getID()) {
             $adjacent->setSubpriority($shift_target);
           }
 
           // If we're shifting the original target task, update the base
           // subpriority.
           if ($shift_task->getID() == $dst->getID()) {
             $base = $shift_target;
           }
         }
       }
 
       $sub = ($adjacent->getSubpriority() + $base) / 2;
     } else {
       // Otherwise, we take a step away from the target's subpriority and
       // use that.
       if ($is_after) {
         $sub = ($base - $step);
       } else {
         $sub = ($base + $step);
       }
     }
 
     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_COLUMN:
-        $with_effect = array();
-        foreach ($xactions as $xaction) {
-          $column_phid = $xaction->getNewValue();
-          if (!$column_phid) {
-            continue;
-          }
-
-          $with_effect[] = $xaction;
-
-          $column = $this->loadProjectColumn($column_phid);
-          if (!$column) {
-            $errors[] = new PhabricatorApplicationTransactionValidationError(
-              $type,
-              pht('Invalid'),
-              pht(
-                'Column PHID "%s" does not identify a visible column.',
-                $column_phid),
-              $xaction);
-          }
-        }
-
-        if ($with_effect && !$this->getIsNewObject()) {
-          $errors[] = new PhabricatorApplicationTransactionValidationError(
-            $type,
-            pht('Invalid'),
-            pht(
-              'You can only put a task into an initial column during task '.
-              'creation.'),
-            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) {
+
+    $errors = parent::validateAllTransactions($object, $xactions);
+
+    if ($this->moreValidationErrors) {
+      $errors = array_merge($errors, $this->moreValidationErrors);
+    }
+
+    return $errors;
+  }
+
   protected function expandTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $actor = $this->getActor();
     $actor_phid = $actor->getPHID();
 
     $results = parent::expandTransactions($object, $xactions);
 
     $is_unassigned = ($object->getOwnerPHID() === null);
 
     $any_assign = false;
     foreach ($xactions as $xaction) {
       if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) {
         $any_assign = true;
         break;
       }
     }
 
     $is_open = !$object->isClosed();
 
     $new_status = null;
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
         case ManiphestTransaction::TYPE_STATUS:
           $new_status = $xaction->getNewValue();
           break;
       }
     }
 
     if ($new_status === null) {
       $is_closing = false;
     } else {
       $is_closing = ManiphestTaskStatus::isClosedStatus($new_status);
     }
 
     // If the task is not assigned, not being assigned, currently open, and
     // being closed, try to assign the actor as the owner.
     if ($is_unassigned && !$any_assign && $is_open && $is_closing) {
       $is_claim = ManiphestTaskStatus::isClaimStatus($new_status);
 
       // Don't assign the actor if they aren't a real user.
       // 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)
           ->setNewValue($actor_phid);
       }
     }
 
     // Automatically subscribe the author when they create a task.
     if ($this->getIsNewObject()) {
       if ($actor_phid) {
         $results[] = id(new ManiphestTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
           ->setNewValue(
             array(
               '+' => array($actor_phid => $actor_phid),
             ));
       }
     }
 
     return $results;
   }
 
   protected function expandTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $results = parent::expandTransaction($object, $xaction);
 
-    switch ($xaction->getTransactionType()) {
-      case ManiphestTransaction::TYPE_COLUMN:
-        $column_phid = $xaction->getNewValue();
-        if (!$column_phid) {
-          break;
+    $type = $xaction->getTransactionType();
+    switch ($type) {
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        try {
+          $this->buildMoveTransaction($object, $xaction);
+
+          // Implicilty add the task to any boards that we're moving it
+          // on, since moves on a board the task isn't part of are not
+          // meaningful.
+          $board_phids = ipull($xaction->getNewValue(), 'boardPHID');
+          if ($board_phids) {
+            $results[] = id(new ManiphestTransaction())
+              ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+              ->setMetadataValue(
+                'edge:type',
+                PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
+              ->setIgnoreOnNoEffect(true)
+              ->setNewValue(
+                array(
+                  '+' => array_fuse($board_phids),
+                ));
+          }
+        } catch (Exception $ex) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Invalid'),
+            $ex->getMessage(),
+            $xaction);
+          $this->moreValidationErrors[] = $error;
         }
-
-        // When a task is created into a column, we also generate a transaction
-        // to actually put it in that column.
-        $column = $this->loadProjectColumn($column_phid);
-        $results[] = id(new ManiphestTransaction())
-          ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)
-          ->setOldValue(
-            array(
-              'projectPHID' => $column->getProjectPHID(),
-              'columnPHIDs' => array(),
-            ))
-          ->setNewValue(
-            array(
-              'projectPHID' => $column->getProjectPHID(),
-              'columnPHIDs' => array($column->getPHID()),
-            ));
         break;
       case ManiphestTransaction::TYPE_OWNER:
         // If this is a no-op update, don't expand it.
         $old_value = $object->getOwnerPHID();
         $new_value = $xaction->getNewValue();
         if ($old_value === $new_value) {
           continue;
         }
 
         // When a task is reassigned, move the old owner to the subscriber
         // list so they're still in the loop.
         if ($old_value) {
           $results[] = id(new ManiphestTransaction())
             ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
             ->setIgnoreOnNoEffect(true)
             ->setNewValue(
               array(
                 '+' => array($old_value => $old_value),
               ));
         }
         break;
     }
 
     return $results;
   }
 
-  private function loadProjectColumn($column_phid) {
-    return id(new PhabricatorProjectColumnQuery())
-      ->setViewer($this->getActor())
-      ->withPHIDs(array($column_phid))
-      ->executeOne();
-  }
-
   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) {
+
+    $new = $xaction->getNewValue();
+    if (!is_array($new)) {
+      $this->validateColumnPHID($new);
+      $new = array($new);
+    }
+
+    $nearby_phids = array();
+    foreach ($new as $key => $value) {
+      if (!is_array($value)) {
+        $this->validateColumnPHID($value);
+        $value = array(
+          'columnPHID' => $value,
+        );
+      }
+
+      PhutilTypeSpec::checkMap(
+        $value,
+        array(
+          'columnPHID' => 'string',
+          'beforePHID' => 'optional string',
+          'afterPHID' => 'optional string',
+        ));
+
+      $new[$key] = $value;
+
+      if (!empty($value['beforePHID'])) {
+        $nearby_phids[] = $value['beforePHID'];
+      }
+
+      if (!empty($value['afterPHID'])) {
+        $nearby_phids[] = $value['afterPHID'];
+      }
+    }
+
+    if ($nearby_phids) {
+      $nearby_objects = id(new PhabricatorObjectQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs($nearby_phids)
+        ->execute();
+      $nearby_objects = mpull($nearby_objects, null, 'getPHID');
+    } else {
+      $nearby_objects = array();
+    }
+
+    $column_phids = ipull($new, 'columnPHID');
+    if ($column_phids) {
+      $columns = id(new PhabricatorProjectColumnQuery())
+        ->setViewer($this->getActor())
+        ->withPHIDs($column_phids)
+        ->execute();
+      $columns = mpull($columns, null, 'getPHID');
+    } else {
+      $columns = array();
+    }
+
+    $board_phids = mpull($columns, 'getProjectPHID');
+    $object_phid = $object->getPHID();
+
+    $object_phids = $nearby_phids;
+
+    // Note that we may not have an object PHID if we're creating a new
+    // object.
+    if ($object_phid) {
+      $object_phids[] = $object_phid;
+    }
+
+    if ($object_phids) {
+      $layout_engine = id(new PhabricatorBoardLayoutEngine())
+        ->setViewer($this->getActor())
+        ->setBoardPHIDs($board_phids)
+        ->setObjectPHIDs($object_phids)
+        ->setFetchAllBoards(true)
+        ->executeLayout();
+    }
+
+    foreach ($new as $key => $spec) {
+      $column_phid = $spec['columnPHID'];
+      $column = idx($columns, $column_phid);
+      if (!$column) {
+        throw new Exception(
+          pht(
+            'Column move transaction specifies column PHID "%s", but there '.
+            'is no corresponding column with this PHID.',
+            $column_phid));
+      }
+
+      $board_phid = $column->getProjectPHID();
+
+      $nearby = array();
+
+      if (!empty($spec['beforePHID'])) {
+        $nearby['beforePHID'] = $spec['beforePHID'];
+      }
+
+      if (!empty($spec['afterPHID'])) {
+        $nearby['afterPHID'] = $spec['afterPHID'];
+      }
+
+      if (count($nearby) > 1) {
+        throw new Exception(
+          pht(
+            'Column move transaction moves object to multiple positions. '.
+            'Specify only "beforePHID" or "afterPHID", not both.'));
+      }
+
+      foreach ($nearby as $where => $nearby_phid) {
+        if (empty($nearby_objects[$nearby_phid])) {
+          throw new Exception(
+            pht(
+              'Column move transaction specifies object "%s" as "%s", but '.
+              'there is no corresponding object with this PHID.',
+              $object_phid,
+              $where));
+        }
+
+        $nearby_columns = $layout_engine->getObjectColumns(
+          $board_phid,
+          $nearby_phid);
+        $nearby_columns = mpull($nearby_columns, null, 'getPHID');
+
+        if (empty($nearby_columns[$column_phid])) {
+          throw new Exception(
+            pht(
+              'Column move transaction specifies object "%s" as "%s" in '.
+              'column "%s", but this object is not in that column!',
+              $nearby_phid,
+              $where,
+              $column_phid));
+        }
+      }
+
+      if ($object_phid) {
+        $old_columns = $layout_engine->getObjectColumns(
+          $board_phid,
+          $object_phid);
+        $old_column_phids = mpull($old_columns, 'getPHID');
+      } else {
+        $old_column_phids = array();
+      }
+
+      $spec += array(
+        'boardPHID' => $board_phid,
+        'fromColumnPHIDs' => $old_column_phids,
+      );
+
+      // Check if the object is already in this column, and isn't being moved.
+      // We can just drop this column change if it has no effect.
+      $from_map = array_fuse($spec['fromColumnPHIDs']);
+      $already_here = isset($from_map[$column_phid]);
+      $is_reordering = (bool)$nearby;
+
+      if ($already_here && !$is_reordering) {
+        unset($new[$key]);
+      } else {
+        $new[$key] = $spec;
+      }
+    }
+
+    $new = array_values($new);
+    $xaction->setNewValue($new);
+  }
+
+  private function applyBoardMove($object, array $move) {
+    $board_phid = $move['boardPHID'];
+    $column_phid = $move['columnPHID'];
+    $before_phid = idx($move, 'beforePHID');
+    $after_phid = idx($move, 'afterPHID');
+
+    $object_phid = $object->getPHID();
+
+    // We're doing layout with the ominpotent viewer to make sure we don't
+    // remove positions in columns that exist, but which the actual actor
+    // can't see.
+    $omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
+
+    $select_phids = array($board_phid);
+
+    $descendants = id(new PhabricatorProjectQuery())
+      ->setViewer($omnipotent_viewer)
+      ->withAncestorProjectPHIDs($select_phids)
+      ->execute();
+    foreach ($descendants as $descendant) {
+      $select_phids[] = $descendant->getPHID();
+    }
+
+    $board_tasks = id(new ManiphestTaskQuery())
+      ->setViewer($omnipotent_viewer)
+      ->withEdgeLogicPHIDs(
+        PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
+        PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
+        array($select_phids))
+      ->execute();
+
+    $board_tasks = mpull($board_tasks, null, 'getPHID');
+    $board_tasks[$object_phid] = $object;
+
+    // Make sure tasks are sorted by ID, so we lay out new positions in
+    // a consistent way.
+    $board_tasks = msort($board_tasks, 'getID');
+
+    $object_phids = array_keys($board_tasks);
+
+    $engine = id(new PhabricatorBoardLayoutEngine())
+      ->setViewer($omnipotent_viewer)
+      ->setBoardPHIDs(array($board_phid))
+      ->setObjectPHIDs($object_phids)
+      ->executeLayout();
+
+    // TODO: This logic needs to be revised when we legitimately support
+    // multiple column positions.
+    $columns = $engine->getObjectColumns($board_phid, $object_phid);
+    foreach ($columns as $column) {
+      $engine->queueRemovePosition(
+        $board_phid,
+        $column->getPHID(),
+        $object_phid);
+    }
+
+    if ($before_phid) {
+      $engine->queueAddPositionBefore(
+        $board_phid,
+        $column_phid,
+        $object_phid,
+        $before_phid);
+    } else if ($after_phid) {
+      $engine->queueAddPositionAfter(
+        $board_phid,
+        $column_phid,
+        $object_phid,
+        $after_phid);
+    } else {
+      $engine->queueAddPosition(
+        $board_phid,
+        $column_phid,
+        $object_phid);
+    }
+
+    $engine->applyPositionUpdates();
+  }
+
+
+  private function validateColumnPHID($value) {
+    if (phid_get_type($value) == PhabricatorProjectColumnPHIDType::TYPECONST) {
+      return;
+    }
+
+    throw new Exception(
+      pht(
+        'When moving objects between columns on a board, columns must '.
+        'be identified by PHIDs. This transaction uses "%s" to identify '.
+        'a column, but that is not a valid column PHID.',
+        $value));
+  }
+
+
 
 }
diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php
index caf57a3f7..79901d7ae 100644
--- a/src/applications/maniphest/storage/ManiphestTransaction.php
+++ b/src/applications/maniphest/storage/ManiphestTransaction.php
@@ -1,991 +1,953 @@
 <?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_PROJECT_COLUMN = 'projectcolumn';
   const TYPE_MERGED_INTO = 'mergedinto';
   const TYPE_MERGED_FROM = 'mergedfrom';
   const TYPE_UNBLOCK = 'unblock';
   const TYPE_PARENT = 'parent';
-  const TYPE_COLUMN = 'column';
   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';
 
   const MAILTAG_STATUS = 'maniphest-status';
   const MAILTAG_OWNER = 'maniphest-owner';
   const MAILTAG_PRIORITY = 'maniphest-priority';
   const MAILTAG_CC = 'maniphest-cc';
   const MAILTAG_PROJECTS = 'maniphest-projects';
   const MAILTAG_COMMENT = 'maniphest-comment';
   const MAILTAG_COLUMN = 'maniphest-column';
   const MAILTAG_UNBLOCK = 'maniphest-unblock';
   const MAILTAG_OTHER = 'maniphest-other';
 
 
   public function getApplicationName() {
     return 'maniphest';
   }
 
   public function getApplicationTransactionType() {
     return ManiphestTaskPHIDType::TYPECONST;
   }
 
   public function getApplicationTransactionCommentObject() {
     return new ManiphestTransactionComment();
   }
 
   public function shouldGenerateOldValue() {
     switch ($this->getTransactionType()) {
-      case self::TYPE_PROJECT_COLUMN:
       case self::TYPE_EDGE:
       case self::TYPE_UNBLOCK:
         return false;
     }
 
     return parent::shouldGenerateOldValue();
   }
 
   public function getRemarkupBlocks() {
     $blocks = parent::getRemarkupBlocks();
 
     switch ($this->getTransactionType()) {
       case self::TYPE_DESCRIPTION:
         $blocks[] = $this->getNewValue();
         break;
     }
 
     return $blocks;
   }
 
   public function getRequiredHandlePHIDs() {
     $phids = parent::getRequiredHandlePHIDs();
 
     $new = $this->getNewValue();
     $old = $this->getOldValue();
 
     switch ($this->getTransactionType()) {
       case self::TYPE_OWNER:
         if ($new) {
           $phids[] = $new;
         }
 
         if ($old) {
           $phids[] = $old;
         }
         break;
-      case self::TYPE_PROJECT_COLUMN:
-        $phids[] = $new['projectPHID'];
-        $phids[] = head($new['columnPHIDs']);
-        break;
       case self::TYPE_MERGED_INTO:
         $phids[] = $new;
         break;
       case self::TYPE_MERGED_FROM:
         $phids = array_merge($phids, $new);
         break;
       case self::TYPE_EDGE:
         $phids = array_mergev(
           array(
             $phids,
             array_keys(nonempty($old, array())),
             array_keys(nonempty($new, array())),
           ));
         break;
       case self::TYPE_ATTACH:
         $old = nonempty($old, array());
         $new = nonempty($new, array());
         $phids = array_mergev(
           array(
             $phids,
             array_keys(idx($new, 'FILE', array())),
             array_keys(idx($old, 'FILE', array())),
           ));
         break;
       case self::TYPE_UNBLOCK:
         foreach (array_keys($new) as $phid) {
           $phids[] = $phid;
         }
         break;
       case self::TYPE_STATUS:
         $commit_phid = $this->getMetadataValue('commitPHID');
         if ($commit_phid) {
           $phids[] = $commit_phid;
         }
         break;
     }
 
     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:
-      case self::TYPE_COLUMN:
         return true;
-      case self::TYPE_PROJECT_COLUMN:
-        $old_cols = idx($this->getOldValue(), 'columnPHIDs');
-        $new_cols = idx($this->getNewValue(), 'columnPHIDs');
-
-        $old_cols = array_values($old_cols);
-        $new_cols = array_values($new_cols);
-        sort($old_cols);
-        sort($new_cols);
-
-        return ($old_cols === $new_cols);
       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 self::TYPE_PROJECT_COLUMN:
+      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 self::TYPE_PROJECT_COLUMN:
+      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();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return pht(
           '%s created this task.',
           $this->renderHandleLink($author_phid));
 
       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 blocking task %s.',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($blocker_phid));
         } else if ($old_closed && !$new_closed) {
           return pht(
             '%s reopened blocking task %s as "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($blocker_phid),
             $new_name);
         } else if (!$old_closed && $new_closed) {
           return pht(
             '%s closed blocking task %s as "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($blocker_phid),
             $new_name);
         } else {
           return pht(
             '%s changed the status of blocking task %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_PROJECT_COLUMN:
-        $project_phid = $new['projectPHID'];
-        $column_phid = head($new['columnPHIDs']);
-        return pht(
-          '%s moved this task to %s on the %s workboard.',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($column_phid),
-          $this->renderHandleLink($project_phid));
-        break;
-
       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();
   }
 
   public function getTitleForFeed() {
     $author_phid = $this->getAuthorPHID();
     $object_phid = $this->getObjectPHID();
 
     $old = $this->getOldValue();
     $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 task blocking %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 task blocking %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 task blocking %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_PROJECT_COLUMN:
-        $project_phid = $new['projectPHID'];
-        $column_phid = head($new['columnPHIDs']);
-        return pht(
-          '%s moved %s to %s on the %s workboard.',
-          $this->renderHandleLink($author_phid),
-          $this->renderHandleLink($object_phid),
-          $this->renderHandleLink($column_phid),
-          $this->renderHandleLink($project_phid));
-
       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));
 
     }
 
     return parent::getTitleForFeed();
   }
 
   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:
         $tags[] = self::MAILTAG_STATUS;
         break;
       case self::TYPE_OWNER:
         $tags[] = self::MAILTAG_OWNER;
         break;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         $tags[] = self::MAILTAG_CC;
         break;
       case PhabricatorTransactions::TYPE_EDGE:
         switch ($this->getMetadataValue('edge:type')) {
           case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
             $tags[] = self::MAILTAG_PROJECTS;
             break;
           default:
             $tags[] = self::MAILTAG_OTHER;
             break;
         }
         break;
       case self::TYPE_PRIORITY:
         $tags[] = self::MAILTAG_PRIORITY;
         break;
       case self::TYPE_UNBLOCK:
         $tags[] = self::MAILTAG_UNBLOCK;
         break;
-      case self::TYPE_PROJECT_COLUMN:
+      case PhabricatorTransactions::TYPE_COLUMNS:
         $tags[] = self::MAILTAG_COLUMN;
         break;
       case PhabricatorTransactions::TYPE_COMMENT:
         $tags[] = self::MAILTAG_COMMENT;
         break;
       default:
         $tags[] = self::MAILTAG_OTHER;
         break;
     }
     return $tags;
   }
 
   public function getNoEffectDescription() {
 
     switch ($this->getTransactionType()) {
       case self::TYPE_STATUS:
         return pht('The task already has the selected status.');
       case self::TYPE_OWNER:
         return pht('The task already has the selected owner.');
       case self::TYPE_PRIORITY:
         return pht('The task already has the selected priority.');
     }
 
     return parent::getNoEffectDescription();
   }
 
 }
diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
index f86f9eb4d..54068e1e7 100644
--- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
@@ -1,210 +1,210 @@
 <?php
 
 final class PhabricatorApplicationDetailViewController
   extends PhabricatorApplicationsController {
 
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $application = $request->getURIData('application');
 
     $selected = id(new PhabricatorApplicationQuery())
       ->setViewer($viewer)
       ->withClasses(array($application))
       ->executeOne();
     if (!$selected) {
       return new Aphront404Response();
     }
 
     $title = $selected->getName();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($selected->getName());
     $crumbs->setBorder(true);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
       ->setPolicyObject($selected)
       ->setHeaderIcon($selected->getIcon());
 
     if ($selected->isInstalled()) {
       $header->setStatus('fa-check', 'bluegrey', pht('Installed'));
     } else {
       $header->setStatus('fa-ban', 'dark', pht('Uninstalled'));
     }
 
     $curtain = $this->buildCurtain($selected);
     $details = $this->buildPropertySectionView($selected);
     $policies = $this->buildPolicyView($selected);
 
     $configs =
       PhabricatorApplicationConfigurationPanel::loadAllPanelsForApplication(
         $selected);
 
     $panels = array();
     foreach ($configs as $config) {
       $config->setViewer($viewer);
       $config->setApplication($selected);
       $panel = $config->buildConfigurationPagePanel();
       $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
       $panels[] = $panel;
 
     }
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
           $policies,
           $panels,
         ))
-      ->addPropertySection(pht('DETAILS'), $details);
+      ->addPropertySection(pht('Details'), $details);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $view,
       ));
   }
 
   private function buildPropertySectionView(
     PhabricatorApplication $application) {
 
     $viewer = $this->getViewer();
     $properties = id(new PHUIPropertyListView());
 
     $properties->addProperty(
       pht('Description'),
       $application->getShortDescription());
 
     if ($application->getFlavorText()) {
       $properties->addProperty(
         null,
         phutil_tag('em', array(), $application->getFlavorText()));
     }
 
     if ($application->isPrototype()) {
       $proto_href = PhabricatorEnv::getDoclink(
         'User Guide: Prototype Applications');
       $learn_more = phutil_tag(
         'a',
         array(
           'href' => $proto_href,
           'target' => '_blank',
         ),
         pht('Learn More'));
 
       $properties->addProperty(
         pht('Prototype'),
         pht(
           'This application is a prototype. %s',
           $learn_more));
     }
 
     $overview = $application->getOverview();
     if (strlen($overview)) {
       $overview = new PHUIRemarkupView($viewer, $overview);
       $properties->addSectionHeader(
         pht('Overview'), PHUIPropertyListView::ICON_SUMMARY);
       $properties->addTextContent($overview);
     }
 
     return $properties;
   }
 
   private function buildPolicyView(
     PhabricatorApplication $application) {
 
     $viewer = $this->getViewer();
     $properties = id(new PHUIPropertyListView());
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('POLICIES'))
       ->setHeaderIcon('fa-lock');
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $application);
 
     foreach ($application->getCapabilities() as $capability) {
       $properties->addProperty(
         $application->getCapabilityLabel($capability),
         idx($descriptions, $capability));
     }
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
 
   }
 
   private function buildCurtain(PhabricatorApplication $application) {
     $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $application,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $key = get_class($application);
     $edit_uri = $this->getApplicationURI("edit/{$key}/");
     $install_uri = $this->getApplicationURI("{$key}/install/");
     $uninstall_uri = $this->getApplicationURI("{$key}/uninstall/");
 
     $curtain = $this->newCurtainView($application);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Policies'))
         ->setIcon('fa-pencil')
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit)
         ->setHref($edit_uri));
 
     if ($application->canUninstall()) {
       if ($application->isInstalled()) {
         $curtain->addAction(
           id(new PhabricatorActionView())
             ->setName(pht('Uninstall'))
             ->setIcon('fa-times')
             ->setDisabled(!$can_edit)
             ->setWorkflow(true)
             ->setHref($uninstall_uri));
       } else {
         $action = id(new PhabricatorActionView())
           ->setName(pht('Install'))
           ->setIcon('fa-plus')
           ->setDisabled(!$can_edit)
           ->setWorkflow(true)
           ->setHref($install_uri);
 
         $prototypes_enabled = PhabricatorEnv::getEnvConfig(
           'phabricator.show-prototypes');
         if ($application->isPrototype() && !$prototypes_enabled) {
           $action->setDisabled(true);
         }
 
         $curtain->addAction($action);
       }
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Uninstall'))
           ->setIcon('fa-times')
           ->setWorkflow(true)
           ->setDisabled(true)
           ->setHref($uninstall_uri));
     }
 
     return $curtain;
   }
 
 }
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
index ec43312e2..fda471b79 100644
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
@@ -1,378 +1,381 @@
 <?php
 
 final class PhabricatorMetaMTAMailViewController
   extends PhabricatorMetaMTAController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $mail = id(new PhabricatorMetaMTAMailQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$mail) {
       return new Aphront404Response();
     }
 
     if ($mail->hasSensitiveContent()) {
       $title = pht('Content Redacted');
     } else {
       $title = $mail->getSubject();
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
-      ->setPolicyObject($mail);
+      ->setPolicyObject($mail)
+      ->setHeaderIcon('fa-envelope');
 
     $status = $mail->getStatus();
     $name = PhabricatorMailOutboundStatus::getStatusName($status);
     $icon = PhabricatorMailOutboundStatus::getStatusIcon($status);
     $color = PhabricatorMailOutboundStatus::getStatusColor($status);
     $header->setStatus($icon, $color, $name);
 
     $crumbs = $this->buildApplicationCrumbs()
-      ->addTextCrumb(pht('Mail %d', $mail->getID()));
+      ->addTextCrumb(pht('Mail %d', $mail->getID()))
+      ->setBorder(true);
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
+      ->setHeaderText(pht('Mail'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($this->buildMessageProperties($mail), pht('Message'))
       ->addPropertyList($this->buildHeaderProperties($mail), pht('Headers'))
       ->addPropertyList($this->buildDeliveryProperties($mail), pht('Delivery'))
       ->addPropertyList($this->buildMetadataProperties($mail), pht('Metadata'));
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $object_box,
-      ),
-      array(
-        'title' => $title,
-        'pageObjects' => array($mail->getPHID()),
-      ));
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($object_box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->setPageObjectPHIDs(array($mail->getPHID()))
+      ->appendChild($view);
   }
 
   private function buildMessageProperties(PhabricatorMetaMTAMail $mail) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($mail);
 
     if ($mail->getFrom()) {
       $from_str = $viewer->renderHandle($mail->getFrom());
     } else {
       $from_str = pht('Sent by Phabricator');
     }
     $properties->addProperty(
       pht('From'),
       $from_str);
 
     if ($mail->getToPHIDs()) {
       $to_list = $viewer->renderHandleList($mail->getToPHIDs());
     } else {
       $to_list = pht('None');
     }
     $properties->addProperty(
       pht('To'),
       $to_list);
 
     if ($mail->getCcPHIDs()) {
       $cc_list = $viewer->renderHandleList($mail->getCcPHIDs());
     } else {
       $cc_list = pht('None');
     }
     $properties->addProperty(
       pht('Cc'),
       $cc_list);
 
     $properties->addProperty(
       pht('Sent'),
       phabricator_datetime($mail->getDateCreated(), $viewer));
 
     $properties->addSectionHeader(
       pht('Message'),
       PHUIPropertyListView::ICON_SUMMARY);
 
     if ($mail->hasSensitiveContent()) {
       $body = phutil_tag(
         'em',
         array(),
         pht(
           'The content of this mail is sensitive and it can not be '.
           'viewed from the web UI.'));
     } else {
       $body = phutil_tag(
         'div',
         array(
           'style' => 'white-space: pre-wrap',
         ),
         $mail->getBody());
     }
 
     $properties->addTextContent($body);
 
 
     return $properties;
   }
 
   private function buildHeaderProperties(PhabricatorMetaMTAMail $mail) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setStacked(true);
 
     $headers = $mail->getDeliveredHeaders();
     if ($headers === null) {
       $headers = $mail->generateHeaders();
     }
 
     // Sort headers by name.
     $headers = isort($headers, 0);
 
     foreach ($headers as $header) {
       list($key, $value) = $header;
       $properties->addProperty($key, $value);
     }
 
     return $properties;
   }
 
   private function buildDeliveryProperties(PhabricatorMetaMTAMail $mail) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $actors = $mail->getDeliveredActors();
     $reasons = null;
     if (!$actors) {
       if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
         $delivery = $this->renderEmptyMessage(
           pht(
             'This message has not been delivered yet, so delivery information '.
             'is not available.'));
       } else {
         $delivery = $this->renderEmptyMessage(
           pht(
             'This is an older message that predates recording delivery '.
             'information, so none is available.'));
       }
     } else {
       $actor = idx($actors, $viewer->getPHID());
       if (!$actor) {
         $delivery = phutil_tag(
           'em',
           array(),
           pht('This message was not delivered to you.'));
       } else {
         $deliverable = $actor['deliverable'];
         if ($deliverable) {
           $delivery = pht('Delivered');
         } else {
           $delivery = pht('Voided');
         }
 
         $reasons = id(new PHUIStatusListView());
 
         $reason_codes = $actor['reasons'];
         if (!$reason_codes) {
           $reason_codes = array(
             PhabricatorMetaMTAActor::REASON_NONE,
           );
         }
 
         $icon_yes = 'fa-check green';
         $icon_no = 'fa-times red';
 
         foreach ($reason_codes as $reason) {
           $target = phutil_tag(
             'strong',
             array(),
             PhabricatorMetaMTAActor::getReasonName($reason));
 
           if (PhabricatorMetaMTAActor::isDeliveryReason($reason)) {
             $icon = $icon_yes;
           } else {
             $icon = $icon_no;
           }
 
           $item = id(new PHUIStatusItemView())
             ->setIcon($icon)
             ->setTarget($target)
             ->setNote(PhabricatorMetaMTAActor::getReasonDescription($reason));
 
           $reasons->addItem($item);
         }
       }
     }
 
     $properties->addProperty(pht('Delivery'), $delivery);
     if ($reasons) {
       $properties->addProperty(pht('Reasons'), $reasons);
       $properties->addProperty(
         null,
         $this->renderEmptyMessage(
           pht(
             'Delivery reasons are listed from weakest to strongest.')));
     }
 
     $properties->addSectionHeader(
       pht('Routing Rules'), 'fa-paper-plane-o');
 
     $map = $mail->getDeliveredRoutingMap();
     $routing_detail = null;
     if ($map === null) {
       if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
         $routing_result = $this->renderEmptyMessage(
           pht(
             'This message has not been sent yet, so routing rules have '.
             'not been computed.'));
       } else {
         $routing_result = $this->renderEmptyMessage(
           pht(
             'This is an older message which predates routing rules.'));
       }
     } else {
       $rule = idx($map, $viewer->getPHID());
       if ($rule === null) {
         $rule = idx($map, 'default');
       }
 
       if ($rule === null) {
         $routing_result = $this->renderEmptyMessage(
           pht(
             'No routing rules applied when delivering this message to you.'));
       } else {
         $rule_const = $rule['rule'];
         $reason_phid = $rule['reason'];
         switch ($rule_const) {
           case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION:
             $routing_result = pht(
               'This message was routed as a notification because it '.
               'matched %s.',
               $viewer->renderHandle($reason_phid)->render());
             break;
           case PhabricatorMailRoutingRule::ROUTE_AS_MAIL:
             $routing_result = pht(
               'This message was routed as an email because it matched %s.',
               $viewer->renderHandle($reason_phid)->render());
             break;
           default:
             $routing_result = pht('Unknown routing rule "%s".', $rule_const);
             break;
         }
       }
 
       $routing_rules = $mail->getDeliveredRoutingRules();
       if ($routing_rules) {
         $rules = array();
         foreach ($routing_rules as $rule) {
           $phids = idx($rule, 'phids');
           if ($phids === null) {
             $rules[] = $rule;
           } else if (in_array($viewer->getPHID(), $phids)) {
             $rules[] = $rule;
           }
         }
 
         // Reorder rules by strength.
         foreach ($rules as $key => $rule) {
           $const = $rule['routingRule'];
           $phids = $rule['phids'];
 
           if ($phids === null) {
             $type = 'A';
           } else {
             $type = 'B';
           }
 
           $rules[$key]['strength'] = sprintf(
             '~%s%08d',
             $type,
             PhabricatorMailRoutingRule::getRuleStrength($const));
         }
         $rules = isort($rules, 'strength');
 
         $routing_detail = id(new PHUIStatusListView());
         foreach ($rules as $rule) {
           $const = $rule['routingRule'];
           $phids = $rule['phids'];
 
           $name = PhabricatorMailRoutingRule::getRuleName($const);
 
           $icon = PhabricatorMailRoutingRule::getRuleIcon($const);
           $color = PhabricatorMailRoutingRule::getRuleColor($const);
 
           if ($phids === null) {
             $kind = pht('Global');
           } else {
             $kind = pht('Personal');
           }
 
           $target = array($kind, ': ', $name);
           $target = phutil_tag('strong', array(), $target);
 
           $item = id(new PHUIStatusItemView())
             ->setTarget($target)
             ->setNote($viewer->renderHandle($rule['reasonPHID']))
             ->setIcon($icon, $color);
 
           $routing_detail->addItem($item);
         }
       }
     }
 
     $properties->addProperty(pht('Effective Rule'), $routing_result);
 
     if ($routing_detail !== null) {
       $properties->addProperty(pht('All Matching Rules'), $routing_detail);
       $properties->addProperty(
         null,
         $this->renderEmptyMessage(
           pht(
             'Matching rules are listed from weakest to strongest.')));
     }
 
     return $properties;
   }
 
   private function buildMetadataProperties(PhabricatorMetaMTAMail $mail) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(pht('Message PHID'), $mail->getPHID());
 
     $details = $mail->getMessage();
     if (!strlen($details)) {
       $details = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Status Details'), $details);
 
     $actor_phid = $mail->getActorPHID();
     if ($actor_phid) {
       $actor_str = $viewer->renderHandle($actor_phid);
     } else {
       $actor_str = pht('Generated by Phabricator');
     }
     $properties->addProperty(pht('Actor'), $actor_str);
 
     $related_phid = $mail->getRelatedPHID();
     if ($related_phid) {
       $related = $viewer->renderHandle($mail->getRelatedPHID());
     } else {
       $related = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Related Object'), $related);
 
     return $properties;
   }
 
   private function renderEmptyMessage($message) {
     return phutil_tag('em', array(), $message);
   }
 
 }
diff --git a/src/applications/multimeter/controller/MultimeterSampleController.php b/src/applications/multimeter/controller/MultimeterSampleController.php
index f9a36b37d..002303818 100644
--- a/src/applications/multimeter/controller/MultimeterSampleController.php
+++ b/src/applications/multimeter/controller/MultimeterSampleController.php
@@ -1,344 +1,351 @@
 <?php
 
 final class MultimeterSampleController extends MultimeterController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $group_map = $this->getColumnMap();
 
     $group = explode('.', $request->getStr('group'));
     $group = array_intersect($group, array_keys($group_map));
     $group = array_fuse($group);
 
     if (empty($group['type'])) {
       $group['type'] = 'type';
     }
 
     $now = PhabricatorTime::getNow();
     $ago = ($now - phutil_units('24 hours in seconds'));
 
     $table = new MultimeterEvent();
     $conn = $table->establishConnection('r');
 
     $where = array();
     $where[] = qsprintf(
       $conn,
       'epoch >= %d AND epoch <= %d',
       $ago,
       $now);
 
     $with = array();
     foreach ($group_map as $key => $column) {
 
       // Don't let non-admins filter by viewers, this feels a little too
       // invasive of privacy.
       if ($key == 'viewer') {
         if (!$viewer->getIsAdmin()) {
           continue;
         }
       }
 
       $with[$key] = $request->getStrList($key);
       if ($with[$key]) {
         $where[] = qsprintf(
           $conn,
           '%T IN (%Ls)',
           $column,
           $with[$key]);
       }
     }
 
     $where = '('.implode(') AND (', $where).')';
 
     $data = queryfx_all(
       $conn,
       'SELECT *,
           count(*) AS N,
           SUM(sampleRate * resourceCost) AS totalCost,
           SUM(sampleRate * resourceCost) / SUM(sampleRate) AS averageCost
         FROM %T
         WHERE %Q
         GROUP BY %Q
         ORDER BY totalCost DESC, MAX(id) DESC
         LIMIT 100',
       $table->getTableName(),
       $where,
       implode(', ', array_select_keys($group_map, $group)));
 
     $this->loadDimensions($data);
     $phids = array();
     foreach ($data as $row) {
       $viewer_name = $this->getViewerDimension($row['eventViewerID'])
         ->getName();
       $viewer_phid = $this->getEventViewerPHID($viewer_name);
       if ($viewer_phid) {
         $phids[] = $viewer_phid;
       }
     }
     $handles = $viewer->loadHandles($phids);
 
     $rows = array();
     foreach ($data as $row) {
 
       if ($row['N'] == 1) {
         $events_col = $row['id'];
       } else {
         $events_col = $this->renderGroupingLink(
           $group,
           'id',
           pht('%s Event(s)', new PhutilNumber($row['N'])));
       }
 
       if (isset($group['request'])) {
         $request_col = $row['requestKey'];
         if (!$with['request']) {
           $request_col = $this->renderSelectionLink(
             'request',
             $row['requestKey'],
             $request_col);
         }
       } else {
         $request_col = $this->renderGroupingLink($group, 'request');
       }
 
       if (isset($group['viewer'])) {
         if ($viewer->getIsAdmin()) {
           $viewer_col = $this->getViewerDimension($row['eventViewerID'])
             ->getName();
 
           $viewer_phid = $this->getEventViewerPHID($viewer_col);
           if ($viewer_phid) {
             $viewer_col = $handles[$viewer_phid]->getName();
           }
 
           if (!$with['viewer']) {
             $viewer_col = $this->renderSelectionLink(
               'viewer',
               $row['eventViewerID'],
               $viewer_col);
           }
         } else {
           $viewer_col = phutil_tag('em', array(), pht('(Masked)'));
         }
       } else {
         $viewer_col = $this->renderGroupingLink($group, 'viewer');
       }
 
       if (isset($group['context'])) {
         $context_col = $this->getContextDimension($row['eventContextID'])
           ->getName();
         if (!$with['context']) {
           $context_col = $this->renderSelectionLink(
             'context',
             $row['eventContextID'],
             $context_col);
         }
       } else {
         $context_col = $this->renderGroupingLink($group, 'context');
       }
 
       if (isset($group['host'])) {
         $host_col = $this->getHostDimension($row['eventHostID'])
           ->getName();
         if (!$with['host']) {
           $host_col = $this->renderSelectionLink(
             'host',
             $row['eventHostID'],
             $host_col);
         }
       } else {
         $host_col = $this->renderGroupingLink($group, 'host');
       }
 
       if (isset($group['label'])) {
         $label_col = $this->getLabelDimension($row['eventLabelID'])
           ->getName();
         if (!$with['label']) {
           $label_col = $this->renderSelectionLink(
             'label',
             $row['eventLabelID'],
             $label_col);
         }
       } else {
         $label_col = $this->renderGroupingLink($group, 'label');
       }
 
       if ($with['type']) {
         $type_col = MultimeterEvent::getEventTypeName($row['eventType']);
       } else {
         $type_col = $this->renderSelectionLink(
           'type',
           $row['eventType'],
           MultimeterEvent::getEventTypeName($row['eventType']));
       }
 
       $rows[] = array(
         $events_col,
         $request_col,
         $viewer_col,
         $context_col,
         $host_col,
         $type_col,
         $label_col,
         MultimeterEvent::formatResourceCost(
           $viewer,
           $row['eventType'],
           $row['averageCost']),
         MultimeterEvent::formatResourceCost(
           $viewer,
           $row['eventType'],
           $row['totalCost']),
         ($row['N'] == 1)
           ? $row['sampleRate']
           : '-',
         phabricator_datetime($row['epoch'], $viewer),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('ID'),
           pht('Request'),
           pht('Viewer'),
           pht('Context'),
           pht('Host'),
           pht('Type'),
           pht('Label'),
           pht('Avg'),
           pht('Cost'),
           pht('Rate'),
           pht('Epoch'),
         ))
       ->setColumnClasses(
         array(
           null,
           null,
           null,
           null,
           null,
           null,
           'wide',
           'n',
           'n',
           'n',
           null,
         ));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(
-        pht(
-          'Samples (%s - %s)',
-          phabricator_datetime($ago, $viewer),
-          phabricator_datetime($now, $viewer)))
+      ->setHeaderText(pht('Samples'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($table);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Samples'),
       $this->getGroupURI(array(), true));
+    $crumbs->setBorder(true);
 
     $crumb_map = array(
       'host' => pht('By Host'),
       'context' => pht('By Context'),
       'viewer' => pht('By Viewer'),
       'request' => pht('By Request'),
       'label' => pht('By Label'),
       'id' => pht('By ID'),
     );
 
     $parts = array();
     foreach ($group as $item) {
       if ($item == 'type') {
         continue;
       }
       $parts[$item] = $item;
       $crumbs->addTextCrumb(
         idx($crumb_map, $item, $item),
         $this->getGroupURI($parts, true));
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('Samples'),
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader(
+        pht(
+          'Samples (%s - %s)',
+          phabricator_datetime($ago, $viewer),
+          phabricator_datetime($now, $viewer)))
+      ->setHeaderIcon('fa-motorcycle');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle(pht('Samples'))
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function renderGroupingLink(array $group, $key, $name = null) {
     $group[] = $key;
     $uri = $this->getGroupURI($group);
 
     if ($name === null) {
       $name = pht('(All)');
     }
 
     return phutil_tag(
       'a',
       array(
         'href' => $uri,
         'style' => 'font-weight: bold',
       ),
       $name);
   }
 
   private function getGroupURI(array $group, $wipe = false) {
     unset($group['type']);
     $uri = clone $this->getRequest()->getRequestURI();
 
     $group = implode('.', $group);
     if (!strlen($group)) {
       $group = null;
     }
     $uri->setQueryParam('group', $group);
 
     if ($wipe) {
       foreach ($this->getColumnMap() as $key => $column) {
         $uri->setQueryParam($key, null);
       }
     }
 
     return $uri;
   }
 
   private function renderSelectionLink($key, $value, $link_text) {
     $value = (array)$value;
 
     $uri = clone $this->getRequest()->getRequestURI();
     $uri->setQueryParam($key, implode(',', $value));
 
     return phutil_tag(
       'a',
       array(
         'href' => $uri,
       ),
       $link_text);
   }
 
   private function getColumnMap() {
     return array(
       'type' => 'eventType',
       'host' => 'eventHostID',
       'context' => 'eventContextID',
       'viewer' => 'eventViewerID',
       'request' => 'requestKey',
       'label' => 'eventLabelID',
       'id' => 'id',
     );
   }
 
   private function getEventViewerPHID($viewer_name) {
     if (!strncmp($viewer_name, 'user.', 5)) {
       return substr($viewer_name, 5);
     }
     return null;
   }
 
 }
diff --git a/src/applications/notification/controller/PhabricatorNotificationStatusController.php b/src/applications/notification/controller/PhabricatorNotificationStatusController.php
index eb79a3a8c..49d3fb9d9 100644
--- a/src/applications/notification/controller/PhabricatorNotificationStatusController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationStatusController.php
@@ -1,85 +1,82 @@
 <?php
 
 final class PhabricatorNotificationStatusController
   extends PhabricatorNotificationController {
 
   public function handleRequest(AphrontRequest $request) {
 
     try {
       $status = PhabricatorNotificationClient::getServerStatus();
       $status = $this->renderServerStatus($status);
     } catch (Exception $ex) {
       $status = new PHUIInfoView();
       $status->setTitle(pht('Notification Server Issue'));
       $status->appendChild(hsprintf(
         '%s<br /><br />'.
         '<strong>%s</strong> %s',
         pht(
           'Unable to determine server status. This probably means the server '.
           'is not in great shape. The specific issue encountered was:'),
         get_class($ex),
         phutil_escape_html_newlines($ex->getMessage())));
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Status'));
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $status,
-      ),
-      array(
-        'title' => pht('Notification Server Status'),
-        'device' => false,
-      ));
+    $title = pht('Notification Server Status');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($status);
   }
 
   private function renderServerStatus(array $status) {
 
     $rows = array();
     foreach ($status as $key => $value) {
       switch ($key) {
         case 'uptime':
           $value /= 1000;
           $value = phutil_format_relative_time_detailed($value);
           break;
         case 'log':
         case 'instance':
           break;
         default:
           $value = number_format($value);
           break;
       }
 
       $rows[] = array($key, $value);
     }
 
     $table = new AphrontTableView($rows);
     $table->setColumnClasses(
       array(
         'header',
         'wide',
       ));
 
     $test_icon = id(new PHUIIconView())
       ->setIcon('fa-exclamation-triangle');
 
     $test_button = id(new PHUIButtonView())
         ->setTag('a')
         ->setWorkflow(true)
         ->setText(pht('Send Test Notification'))
         ->setHref($this->getApplicationURI('test/'))
         ->setIcon($test_icon);
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Notification Server Status'))
       ->addActionLink($test_button);
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild($table);
 
     return $box;
   }
 }
diff --git a/src/applications/nuance/controller/NuanceConsoleController.php b/src/applications/nuance/controller/NuanceConsoleController.php
index 6416c19aa..d4a8f6c9f 100644
--- a/src/applications/nuance/controller/NuanceConsoleController.php
+++ b/src/applications/nuance/controller/NuanceConsoleController.php
@@ -1,53 +1,59 @@
 <?php
 
 final class NuanceConsoleController extends NuanceController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $menu = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Queues'))
         ->setHref($this->getApplicationURI('queue/'))
         ->setIcon('fa-align-left')
         ->addAttribute(pht('Manage Nuance queues.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Sources'))
         ->setHref($this->getApplicationURI('source/'))
         ->setIcon('fa-filter')
         ->addAttribute(pht('Manage Nuance sources.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Items'))
         ->setHref($this->getApplicationURI('item/'))
         ->setIcon('fa-clone')
         ->addAttribute(pht('Manage Nuance items.')));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Console'));
+    $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Console'))
       ->setObjectList($menu);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Nuance Console'))
+      ->setHeaderIcon('fa-fax');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $box,
-      ),
-      array(
-        'title'  => pht('Nuance Console'),
       ));
+
+    return $this->newPage()
+      ->setTitle(pht('Nuance Console'))
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/nuance/controller/NuanceItemManageController.php b/src/applications/nuance/controller/NuanceItemManageController.php
index c86d2cd98..2b6b4c89b 100644
--- a/src/applications/nuance/controller/NuanceItemManageController.php
+++ b/src/applications/nuance/controller/NuanceItemManageController.php
@@ -1,109 +1,109 @@
 <?php
 
 final class NuanceItemManageController extends NuanceController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $item = id(new NuanceItemQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$item) {
       return new Aphront404Response();
     }
 
     $title = pht('Item %d', $item->getID());
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Items'),
       $this->getApplicationURI('item/'));
     $crumbs->addTextCrumb(
       $title,
       $item->getURI());
     $crumbs->addTextCrumb(pht('Manage'));
     $crumbs->setBorder(true);
 
     $properties = $this->buildPropertyView($item);
     $curtain = $this->buildCurtain($item);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title);
 
     $timeline = $this->buildTransactionTimeline(
       $item,
       new NuanceItemTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
-      ->addPropertySection(pht('DETAILS'), $properties)
+      ->addPropertySection(pht('Details'), $properties)
       ->setMainColumn($timeline);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildPropertyView(NuanceItem $item) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(
       pht('Date Created'),
       phabricator_datetime($item->getDateCreated(), $viewer));
 
     $requestor_phid = $item->getRequestorPHID();
     if ($requestor_phid) {
       $requestor_view = $viewer->renderHandle($requestor_phid);
     } else {
       $requestor_view = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Requestor'), $requestor_view);
 
     $properties->addProperty(
       pht('Source'),
       $viewer->renderHandle($item->getSourcePHID()));
 
     $queue_phid = $item->getQueuePHID();
     if ($queue_phid) {
       $queue_view = $viewer->renderHandle($queue_phid);
     } else {
       $queue_view = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Queue'), $queue_view);
 
     $source = $item->getSource();
     $definition = $source->getDefinition();
 
     $definition->renderItemEditProperties(
       $viewer,
       $item,
       $properties);
 
     return $properties;
   }
 
   private function buildCurtain(NuanceItem $item) {
     $viewer = $this->getViewer();
     $id = $item->getID();
 
     $curtain = $this->newCurtainView($item);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View Item'))
         ->setIcon('fa-eye')
         ->setHref($item->getURI()));
 
     return $curtain;
   }
 
 
 }
diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php
index af602bfd7..93facde3c 100644
--- a/src/applications/nuance/controller/NuanceSourceViewController.php
+++ b/src/applications/nuance/controller/NuanceSourceViewController.php
@@ -1,116 +1,116 @@
 <?php
 
 final class NuanceSourceViewController
   extends NuanceSourceController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $source = id(new NuanceSourceQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$source) {
       return new Aphront404Response();
     }
 
     $source_id = $source->getID();
 
     $header = $this->buildHeaderView($source);
     $curtain = $this->buildCurtain($source);
     $properties = $this->buildPropertyView($source);
 
     $title = $source->getName();
 
     $routing_list = id(new PHUIPropertyListView())
       ->addProperty(
         pht('Default Queue'),
         $viewer->renderHandle($source->getDefaultQueuePHID()));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/'));
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $source,
       new NuanceSourceTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
-      ->addPropertySection(pht('DETAILS'), $properties)
-      ->addPropertySection(pht('ROUTING'), $routing_list)
+      ->addPropertySection(pht('Details'), $properties)
+      ->addPropertySection(pht('Routing'), $routing_list)
       ->setMainColumn($timeline);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildHeaderView(NuanceSource $source) {
     $viewer = $this->getViewer();
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($source->getName())
       ->setPolicyObject($source);
 
     return $header;
   }
 
   private function buildCurtain(NuanceSource $source) {
     $viewer = $this->getViewer();
     $id = $source->getID();
 
     $actions = id(new PhabricatorActionListView())
       ->setUser($viewer);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $source,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($source);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Source'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("source/edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $request = $this->getRequest();
     $definition = $source->getDefinition();
 
     $definition
       ->setViewer($viewer)
       ->setSource($source);
 
     $source_actions = $definition->getSourceViewActions($request);
     foreach ($source_actions as $source_action) {
       $curtain->addAction($source_action);
     }
 
     return $curtain;
   }
 
   private function buildPropertyView(
     NuanceSource $source) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setViewer($viewer);
 
     $definition = $source->getDefinition();
 
     $properties->addProperty(
       pht('Source Type'),
       $definition->getName());
 
     return $properties;
   }
 }
diff --git a/src/applications/oauthserver/PhabricatorOAuthServer.php b/src/applications/oauthserver/PhabricatorOAuthServer.php
index 38b2e3462..f5c074f4e 100644
--- a/src/applications/oauthserver/PhabricatorOAuthServer.php
+++ b/src/applications/oauthserver/PhabricatorOAuthServer.php
@@ -1,272 +1,284 @@
 <?php
 
 /**
  * Implements core OAuth 2.0 Server logic.
  *
  * This class should be used behind business logic that parses input to
  * determine pertinent @{class:PhabricatorUser} $user,
  * @{class:PhabricatorOAuthServerClient} $client(s),
  * @{class:PhabricatorOAuthServerAuthorizationCode} $code(s), and.
  * @{class:PhabricatorOAuthServerAccessToken} $token(s).
  *
  * For an OAuth 2.0 server, there are two main steps:
  *
  * 1) Authorization - the user authorizes a given client to access the data
  * the OAuth 2.0 server protects. Once this is achieved / if it has
  * been achived already, the OAuth server sends the client an authorization
  * code.
  * 2) Access Token - the client should send the authorization code received in
  * step 1 along with its id and secret to the OAuth server to receive an
  * access token. This access token can later be used to access Phabricator
  * data on behalf of the user.
  *
  * @task auth Authorizing @{class:PhabricatorOAuthServerClient}s and
  *            generating @{class:PhabricatorOAuthServerAuthorizationCode}s
  * @task token Validating @{class:PhabricatorOAuthServerAuthorizationCode}s
  *             and generating @{class:PhabricatorOAuthServerAccessToken}s
  * @task internal Internals
  */
 final class PhabricatorOAuthServer extends Phobject {
 
   const AUTHORIZATION_CODE_TIMEOUT = 300;
-  const ACCESS_TOKEN_TIMEOUT       = 3600;
 
   private $user;
   private $client;
 
   private function getUser() {
     if (!$this->user) {
       throw new PhutilInvalidStateException('setUser');
     }
     return $this->user;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   private function getClient() {
     if (!$this->client) {
       throw new PhutilInvalidStateException('setClient');
     }
     return $this->client;
   }
 
   public function setClient(PhabricatorOAuthServerClient $client) {
     $this->client = $client;
     return $this;
   }
 
   /**
    * @task auth
    * @return tuple <bool hasAuthorized, ClientAuthorization or null>
    */
   public function userHasAuthorizedClient(array $scope) {
 
     $authorization = id(new PhabricatorOAuthClientAuthorization())
       ->loadOneWhere(
         'userPHID = %s AND clientPHID = %s',
         $this->getUser()->getPHID(),
         $this->getClient()->getPHID());
     if (empty($authorization)) {
       return array(false, null);
     }
 
     if ($scope) {
       $missing_scope = array_diff_key($scope, $authorization->getScope());
     } else {
       $missing_scope = false;
     }
 
     if ($missing_scope) {
       return array(false, $authorization);
     }
 
     return array(true, $authorization);
   }
 
   /**
    * @task auth
    */
   public function authorizeClient(array $scope) {
     $authorization = new PhabricatorOAuthClientAuthorization();
     $authorization->setUserPHID($this->getUser()->getPHID());
     $authorization->setClientPHID($this->getClient()->getPHID());
     $authorization->setScope($scope);
     $authorization->save();
 
     return $authorization;
   }
 
   /**
    * @task auth
    */
   public function generateAuthorizationCode(PhutilURI $redirect_uri) {
 
     $code   = Filesystem::readRandomCharacters(32);
     $client = $this->getClient();
 
     $authorization_code = new PhabricatorOAuthServerAuthorizationCode();
     $authorization_code->setCode($code);
     $authorization_code->setClientPHID($client->getPHID());
     $authorization_code->setClientSecret($client->getSecret());
     $authorization_code->setUserPHID($this->getUser()->getPHID());
     $authorization_code->setRedirectURI((string)$redirect_uri);
     $authorization_code->save();
 
     return $authorization_code;
   }
 
   /**
    * @task token
    */
   public function generateAccessToken() {
 
     $token = Filesystem::readRandomCharacters(32);
 
     $access_token = new PhabricatorOAuthServerAccessToken();
     $access_token->setToken($token);
     $access_token->setUserPHID($this->getUser()->getPHID());
     $access_token->setClientPHID($this->getClient()->getPHID());
     $access_token->save();
 
     return $access_token;
   }
 
   /**
    * @task token
    */
   public function validateAuthorizationCode(
     PhabricatorOAuthServerAuthorizationCode $test_code,
     PhabricatorOAuthServerAuthorizationCode $valid_code) {
 
     // check that all the meta data matches
     if ($test_code->getClientPHID() != $valid_code->getClientPHID()) {
       return false;
     }
     if ($test_code->getClientSecret() != $valid_code->getClientSecret()) {
       return false;
     }
 
     // check that the authorization code hasn't timed out
     $created_time = $test_code->getDateCreated();
     $must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT;
     return (time() < $must_be_used_by);
   }
 
   /**
    * @task token
    */
-  public function validateAccessToken(
-    PhabricatorOAuthServerAccessToken $token,
-    $required_scope) {
-
-    $created_time    = $token->getDateCreated();
-    $must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT;
-    $expired         = time() > $must_be_used_by;
-    $authorization   = id(new PhabricatorOAuthClientAuthorization())
-      ->loadOneWhere(
-        'userPHID = %s AND clientPHID = %s',
-        $token->getUserPHID(),
-        $token->getClientPHID());
+  public function authorizeToken(
+    PhabricatorOAuthServerAccessToken $token) {
+
+    $user_phid = $token->getUserPHID();
+    $client_phid = $token->getClientPHID();
 
+    $authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
+      ->setViewer(PhabricatorUser::getOmnipotentUser())
+      ->withUserPHIDs(array($user_phid))
+      ->withClientPHIDs(array($client_phid))
+      ->executeOne();
     if (!$authorization) {
-      return false;
-    }
-    $token_scope = $authorization->getScope();
-    if (!isset($token_scope[$required_scope])) {
-      return false;
+      return null;
     }
 
-    $valid = true;
-    if ($expired) {
-      $valid = false;
-      // check if the scope includes "offline_access", which makes the
-      // token valid despite being expired
-      if (isset(
-        $token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) {
-        $valid = true;
-      }
+    $application = $authorization->getClient();
+    if ($application->getIsDisabled()) {
+      return null;
     }
 
-    return $valid;
+    return $authorization;
+  }
+
+  public function validateRedirectURI($uri) {
+    try {
+      $this->assertValidRedirectURI($uri);
+      return true;
+    } catch (Exception $ex) {
+      return false;
+    }
   }
 
   /**
    * See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2
    * for details on what makes a given redirect URI "valid".
    */
-  public function validateRedirectURI(PhutilURI $uri) {
-    if (!PhabricatorEnv::isValidRemoteURIForLink($uri)) {
-      return false;
+  public function assertValidRedirectURI($raw_uri) {
+    // This covers basics like reasonable formatting and the existence of a
+    // protocol.
+    PhabricatorEnv::requireValidRemoteURIForLink($raw_uri);
+
+    $uri = new PhutilURI($raw_uri);
+
+    $fragment = $uri->getFragment();
+    if (strlen($fragment)) {
+      throw new Exception(
+        pht(
+          'OAuth application redirect URIs must not contain URI '.
+          'fragments, but the URI "%s" has a fragment ("%s").',
+          $raw_uri,
+          $fragment));
     }
 
-    if ($uri->getFragment()) {
-      return false;
+    $protocol = $uri->getProtocol();
+    switch ($protocol) {
+      case 'http':
+      case 'https':
+        break;
+      default:
+        throw new Exception(
+          pht(
+            'OAuth application redirect URIs must only use the "http" or '.
+            '"https" protocols, but the URI "%s" uses the "%s" protocol.',
+            $raw_uri,
+            $protocol));
     }
-
-    if (!$uri->getDomain()) {
-      return false;
-    }
-
-    return true;
   }
 
   /**
    * If there's a URI specified in an OAuth request, it must be validated in
    * its own right. Further, it must have the same domain, the same path, the
    * same port, and (at least) the same query parameters as the primary URI.
    */
   public function validateSecondaryRedirectURI(
     PhutilURI $secondary_uri,
     PhutilURI $primary_uri) {
 
     // The secondary URI must be valid.
     if (!$this->validateRedirectURI($secondary_uri)) {
       return false;
     }
 
     // Both URIs must point at the same domain.
     if ($secondary_uri->getDomain() != $primary_uri->getDomain()) {
       return false;
     }
 
     // Both URIs must have the same path
     if ($secondary_uri->getPath() != $primary_uri->getPath()) {
       return false;
     }
 
     // Both URIs must have the same port
     if ($secondary_uri->getPort() != $primary_uri->getPort()) {
       return false;
     }
 
     // Any query parameters present in the first URI must be exactly present
     // in the second URI.
     $need_params = $primary_uri->getQueryParams();
     $have_params = $secondary_uri->getQueryParams();
 
     foreach ($need_params as $key => $value) {
       if (!array_key_exists($key, $have_params)) {
         return false;
       }
       if ((string)$have_params[$key] != (string)$value) {
         return false;
       }
     }
 
     // If the first URI is HTTPS, the second URI must also be HTTPS. This
     // defuses an attack where a third party with control over the network
     // tricks you into using HTTP to authenticate over a link which is supposed
     // to be HTTPS only and sniffs all your token cookies.
     if (strtolower($primary_uri->getProtocol()) == 'https') {
       if (strtolower($secondary_uri->getProtocol()) != 'https') {
         return false;
       }
     }
 
     return true;
   }
 
 }
diff --git a/src/applications/oauthserver/PhabricatorOAuthServerScope.php b/src/applications/oauthserver/PhabricatorOAuthServerScope.php
index 4ab6c455d..68aec848f 100644
--- a/src/applications/oauthserver/PhabricatorOAuthServerScope.php
+++ b/src/applications/oauthserver/PhabricatorOAuthServerScope.php
@@ -1,127 +1,21 @@
 <?php
 
 final class PhabricatorOAuthServerScope extends Phobject {
 
-  const SCOPE_OFFLINE_ACCESS = 'offline_access';
-  const SCOPE_WHOAMI         = 'whoami';
-  const SCOPE_NOT_ACCESSIBLE = 'not_accessible';
-
-  /*
-   * Note this does not contain SCOPE_NOT_ACCESSIBLE which is magic
-   * used to simplify code for data that is not currently accessible
-   * via OAuth.
-   */
-  public static function getScopesDict() {
-    return array(
-      self::SCOPE_OFFLINE_ACCESS => 1,
-      self::SCOPE_WHOAMI         => 1,
-    );
-  }
-
-  public static function getDefaultScope() {
-    return self::SCOPE_WHOAMI;
-  }
-
-  public static function getCheckboxControl(
-    array $current_scopes) {
-
-    $have_options = false;
-    $scopes = self::getScopesDict();
-    $scope_keys = array_keys($scopes);
-    sort($scope_keys);
-    $default_scope = self::getDefaultScope();
-
-    $checkboxes = new AphrontFormCheckboxControl();
-    foreach ($scope_keys as $scope) {
-      if ($scope == $default_scope) {
-        continue;
-      }
-      if (!isset($current_scopes[$scope])) {
-        continue;
-      }
-
-      $checkboxes->addCheckbox(
-        $name = $scope,
-        $value = 1,
-        $label = self::getCheckboxLabel($scope),
-        $checked = isset($current_scopes[$scope]));
-      $have_options = true;
-    }
-
-    if ($have_options) {
-      $checkboxes->setLabel(pht('Scope'));
-      return $checkboxes;
-    }
-
-    return null;
+  public static function getScopeMap() {
+    return array();
   }
 
-  private static function getCheckboxLabel($scope) {
-    $label = null;
-    switch ($scope) {
-      case self::SCOPE_OFFLINE_ACCESS:
-        $label = pht('Make access tokens granted to this client never expire.');
-        break;
-      case self::SCOPE_WHOAMI:
-        $label = pht('Read access to Conduit method %s.', 'user.whoami');
-        break;
-    }
-
-    return $label;
-  }
+  public static function filterScope(array $scope) {
+    $valid_scopes = self::getScopeMap();
 
-  public static function getScopesFromRequest(AphrontRequest $request) {
-    $scopes = self::getScopesDict();
-    $requested_scopes = array();
-    foreach ($scopes as $scope => $bit) {
-      if ($request->getBool($scope)) {
-        $requested_scopes[$scope] = 1;
+    foreach ($scope as $key => $scope_item) {
+      if (!isset($valid_scopes[$scope_item])) {
+        unset($scope[$key]);
       }
     }
-    $requested_scopes[self::getDefaultScope()] = 1;
-    return $requested_scopes;
-  }
-
-  /**
-   * A scopes list is considered valid if each scope is a known scope
-   * and each scope is seen only once. Otherwise, the list is invalid.
-   */
-  public static function validateScopesList($scope_list) {
-    $scopes       = explode(' ', $scope_list);
-    $known_scopes = self::getScopesDict();
-    $seen_scopes  = array();
-    foreach ($scopes as $scope) {
-      if (!isset($known_scopes[$scope])) {
-        return false;
-      }
-      if (isset($seen_scopes[$scope])) {
-        return false;
-      }
-      $seen_scopes[$scope] = 1;
-    }
-
-    return true;
-  }
-
-  /**
-   * A scopes dictionary is considered valid if each key is a known scope.
-   * Otherwise, the dictionary is invalid.
-   */
-  public static function validateScopesDict($scope_dict) {
-    $known_scopes   = self::getScopesDict();
-    $unknown_scopes = array_diff_key($scope_dict,
-                                     $known_scopes);
-    return empty($unknown_scopes);
-  }
 
-  /**
-   * Transforms a space-delimited scopes list into a scopes dict. The list
-   * should be validated by @{method:validateScopesList} before
-   * transformation.
-   */
-   public static function scopesListToDict($scope_list) {
-    $scopes = explode(' ', $scope_list);
-    return array_fill_keys($scopes, 1);
+    return $scope;
   }
 
 }
diff --git a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
index 7bd7eed9a..024d9101d 100644
--- a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
+++ b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
@@ -1,74 +1,74 @@
 <?php
 
 final class PhabricatorOAuthServerApplication extends PhabricatorApplication {
 
   public function getName() {
     return pht('OAuth Server');
   }
 
   public function getBaseURI() {
     return '/oauthserver/';
   }
 
   public function getShortDescription() {
     return pht('OAuth Login Provider');
   }
 
   public function getIcon() {
     return 'fa-hotel';
   }
 
   public function getTitleGlyph() {
     return "\xE2\x99\x86";
   }
 
   public function getFlavorText() {
     return pht('Login with Phabricator');
   }
 
   public function getApplicationGroup() {
     return self::GROUP_ADMIN;
   }
 
   public function isPrototype() {
     return true;
   }
 
   public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
     return array(
       array(
         'name' => pht('Using the Phabricator OAuth Server'),
         'href' => PhabricatorEnv::getDoclink(
           'Using the Phabricator OAuth Server'),
       ),
     );
   }
 
   public function getRoutes() {
     return array(
       '/oauthserver/' => array(
         '(?:query/(?P<queryKey>[^/]+)/)?'
           => 'PhabricatorOAuthClientListController',
         'auth/' => 'PhabricatorOAuthServerAuthController',
-        'test/(?P<id>\d+)/' => 'PhabricatorOAuthServerTestController',
         'token/' => 'PhabricatorOAuthServerTokenController',
-        'client/' => array(
-          'create/' => 'PhabricatorOAuthClientEditController',
-          'delete/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientDeleteController',
-          'edit/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientEditController',
-          'view/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientViewController',
-          'secret/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientSecretController',
+        $this->getEditRoutePattern('edit/') =>
+          'PhabricatorOAuthClientEditController',
+          'client/' => array(
+          'disable/(?P<id>\d+)/' => 'PhabricatorOAuthClientDisableController',
+          'view/(?P<id>\d+)/' => 'PhabricatorOAuthClientViewController',
+          'secret/(?P<id>\d+)/' => 'PhabricatorOAuthClientSecretController',
+          'test/(?P<id>\d+)/' => 'PhabricatorOAuthClientTestController',
         ),
       ),
     );
   }
 
   protected function getCustomCapabilities() {
     return array(
       PhabricatorOAuthServerCreateClientsCapability::CAPABILITY => array(
         'default' => PhabricatorPolicies::POLICY_ADMIN,
       ),
     );
   }
 
 }
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
index d473e1557..b9d916e82 100644
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
+++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
@@ -1,271 +1,293 @@
 <?php
 
 final class PhabricatorOAuthServerAuthController
   extends PhabricatorOAuthServerController {
 
+  protected function buildApplicationCrumbs() {
+    // We're specifically not putting an "OAuth Server" application crumb
+    // on the auth pages because it doesn't make sense to send users there.
+    return new PHUICrumbsView();
+  }
+
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $server = new PhabricatorOAuthServer();
     $client_phid = $request->getStr('client_id');
-    $scope = $request->getStr('scope');
     $redirect_uri = $request->getStr('redirect_uri');
     $response_type = $request->getStr('response_type');
 
     // state is an opaque value the client sent us for their own purposes
     // we just need to send it right back to them in the response!
     $state = $request->getStr('state');
 
     if (!$client_phid) {
       return $this->buildErrorResponse(
         'invalid_request',
         pht('Malformed Request'),
         pht(
           'Required parameter %s was not present in the request.',
           phutil_tag('strong', array(), 'client_id')));
     }
 
     // We require that users must be able to see an OAuth application
     // in order to authorize it. This allows an application's visibility
     // policy to be used to restrict authorized users.
     try {
       $client = id(new PhabricatorOAuthServerClientQuery())
         ->setViewer($viewer)
         ->withPHIDs(array($client_phid))
         ->executeOne();
     } catch (PhabricatorPolicyException $ex) {
       $ex->setContext(self::CONTEXT_AUTHORIZE);
       throw $ex;
     }
 
     $server->setUser($viewer);
     $is_authorized = false;
     $authorization = null;
     $uri = null;
     $name = null;
 
     // one giant try / catch around all the exciting database stuff so we
     // can return a 'server_error' response if something goes wrong!
     try {
       if (!$client) {
         return $this->buildErrorResponse(
           'invalid_request',
           pht('Invalid Client Application'),
           pht(
             'Request parameter %s does not specify a valid client application.',
             phutil_tag('strong', array(), 'client_id')));
       }
 
+      if ($client->getIsDisabled()) {
+        return $this->buildErrorResponse(
+          'invalid_request',
+          pht('Application Disabled'),
+          pht(
+            'The %s OAuth application has been disabled.',
+            phutil_tag('strong', array(), 'client_id')));
+      }
+
       $name = $client->getName();
       $server->setClient($client);
       if ($redirect_uri) {
         $client_uri   = new PhutilURI($client->getRedirectURI());
         $redirect_uri = new PhutilURI($redirect_uri);
         if (!($server->validateSecondaryRedirectURI($redirect_uri,
                                                     $client_uri))) {
           return $this->buildErrorResponse(
             'invalid_request',
             pht('Invalid Redirect URI'),
             pht(
               'Request parameter %s specifies an invalid redirect URI. '.
               'The redirect URI must be a fully-qualified domain with no '.
               'fragments, and must have the same domain and at least '.
               'the same query parameters as the redirect URI the client '.
               'registered.',
               phutil_tag('strong', array(), 'redirect_uri')));
         }
         $uri = $redirect_uri;
       } else {
         $uri = new PhutilURI($client->getRedirectURI());
       }
 
       if (empty($response_type)) {
         return $this->buildErrorResponse(
           'invalid_request',
           pht('Invalid Response Type'),
           pht(
             'Required request parameter %s is missing.',
             phutil_tag('strong', array(), 'response_type')));
       }
 
       if ($response_type != 'code') {
         return $this->buildErrorResponse(
           'unsupported_response_type',
           pht('Unsupported Response Type'),
           pht(
             'Request parameter %s specifies an unsupported response type. '.
             'Valid response types are: %s.',
             phutil_tag('strong', array(), 'response_type'),
             implode(', ', array('code'))));
       }
 
-      if ($scope) {
-        if (!PhabricatorOAuthServerScope::validateScopesList($scope)) {
-          return $this->buildErrorResponse(
-            'invalid_scope',
-            pht('Invalid Scope'),
-            pht(
-              'Request parameter %s specifies an unsupported scope.',
-              phutil_tag('strong', array(), 'scope')));
-        }
-        $scope = PhabricatorOAuthServerScope::scopesListToDict($scope);
-      } else {
-        return $this->buildErrorResponse(
-          'invalid_request',
-          pht('Malformed Request'),
-          pht(
-            'Required parameter %s was not present in the request.',
-            phutil_tag('strong', array(), 'scope')));
-      }
+
+      $requested_scope = $request->getStrList('scope');
+      $requested_scope = array_fuse($requested_scope);
+
+      $scope = PhabricatorOAuthServerScope::filterScope($requested_scope);
 
       // NOTE: We're always requiring a confirmation dialog to redirect.
       // Partly this is a general defense against redirect attacks, and
       // partly this shakes off anchors in the URI (which are not shaken
       // by 302'ing).
 
       $auth_info = $server->userHasAuthorizedClient($scope);
       list($is_authorized, $authorization) = $auth_info;
 
       if ($request->isFormPost()) {
-        $scope = PhabricatorOAuthServerScope::getScopesFromRequest($request);
-
         if ($authorization) {
           $authorization->setScope($scope)->save();
         } else {
           $authorization = $server->authorizeClient($scope);
         }
 
         $is_authorized = true;
       }
     } catch (Exception $e) {
       return $this->buildErrorResponse(
         'server_error',
         pht('Server Error'),
         pht(
           'The authorization server encountered an unexpected condition '.
           'which prevented it from fulfilling the request.'));
     }
 
     // When we reach this part of the controller, we can be in two states:
     //
     //   1. The user has not authorized the application yet. We want to
     //      give them an "Authorize this application?" dialog.
     //   2. The user has authorized the application. We want to give them
     //      a "Confirm Login" dialog.
 
     if ($is_authorized) {
 
       // The second case is simpler, so handle it first. The user either
       // authorized the application previously, or has just authorized the
       // application. Show them a confirm dialog with a normal link back to
       // the application. This shakes anchors from the URI.
 
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $auth_code = $server->generateAuthorizationCode($uri);
       unset($unguarded);
 
       $full_uri = $this->addQueryParams(
         $uri,
         array(
           'code'  => $auth_code->getCode(),
           'scope' => $authorization->getScopeString(),
           'state' => $state,
         ));
 
       if ($client->getIsTrusted()) {
         return id(new AphrontRedirectResponse())
           ->setIsExternal(true)
           ->setURI((string)$full_uri);
       }
 
       // TODO: It would be nice to give the user more options here, like
       // reviewing permissions, canceling the authorization, or aborting
       // the workflow.
 
       $dialog = id(new AphrontDialogView())
         ->setUser($viewer)
         ->setTitle(pht('Authenticate: %s', $name))
         ->appendParagraph(
           pht(
             'This application ("%s") is authorized to use your Phabricator '.
             'credentials. Continue to complete the authentication workflow.',
             phutil_tag('strong', array(), $name)))
         ->addCancelButton((string)$full_uri, pht('Continue to Application'));
 
       return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     // Here, we're confirming authorization for the application.
     if ($authorization) {
-      $desired_scopes = array_merge($scope, $authorization->getScope());
+      $missing_scope = array_diff_key($scope, $authorization->getScope());
     } else {
-      $desired_scopes = $scope;
-    }
-
-    if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) {
-      return $this->buildErrorResponse(
-        'invalid_scope',
-        pht('Invalid Scope'),
-        pht('The requested scope is invalid, unknown, or malformed.'));
+      $missing_scope = $scope;
     }
 
     $form = id(new AphrontFormView())
       ->addHiddenInput('client_id', $client_phid)
       ->addHiddenInput('redirect_uri', $redirect_uri)
       ->addHiddenInput('response_type', $response_type)
       ->addHiddenInput('state', $state)
       ->addHiddenInput('scope', $request->getStr('scope'))
-      ->setUser($viewer)
-      ->appendChild(
-        PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes));
+      ->setUser($viewer);
 
     $cancel_msg = pht('The user declined to authorize this application.');
     $cancel_uri = $this->addQueryParams(
       $uri,
       array(
         'error' => 'access_denied',
         'error_description' => $cancel_msg,
       ));
 
-    return $this->newDialog()
+    $dialog = $this->newDialog()
       ->setShortTitle(pht('Authorize Access'))
       ->setTitle(pht('Authorize "%s"?', $name))
       ->setSubmitURI($request->getRequestURI()->getPath())
       ->setWidth(AphrontDialogView::WIDTH_FORM)
       ->appendParagraph(
         pht(
           'Do you want to authorize the external application "%s" to '.
           'access your Phabricator account data, including your primary '.
           'email address?',
           phutil_tag('strong', array(), $name)))
-      ->appendChild($form->buildLayoutView())
+      ->appendForm($form)
       ->addSubmitButton(pht('Authorize Access'))
       ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize'));
+
+    if ($missing_scope) {
+      $dialog->appendParagraph(
+        pht(
+          'This application has requested these additional permissions. '.
+          'Authorizing it will grant it the permissions it requests:'));
+      foreach ($missing_scope as $scope_key => $ignored) {
+        // TODO: Once we introduce more scopes, explain them here.
+      }
+    }
+
+    $unknown_scope = array_diff_key($requested_scope, $scope);
+    if ($unknown_scope) {
+      $dialog->appendParagraph(
+        pht(
+          'This application also requested additional unrecognized '.
+          'permissions. These permissions may have existed in an older '.
+          'version of Phabricator, or may be from a future version of '.
+          'Phabricator. They will not be granted.'));
+
+      $unknown_form = id(new AphrontFormView())
+        ->setViewer($viewer)
+        ->appendChild(
+          id(new AphrontFormTextControl())
+            ->setLabel(pht('Unknown Scope'))
+            ->setValue(implode(', ', array_keys($unknown_scope)))
+            ->setDisabled(true));
+
+      $dialog->appendForm($unknown_form);
+    }
+
+    return $dialog;
   }
 
 
   private function buildErrorResponse($code, $title, $message) {
     $viewer = $this->getRequest()->getUser();
 
     return $this->newDialog()
       ->setTitle(pht('OAuth: %s', $title))
       ->appendParagraph($message)
       ->appendParagraph(
         pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code)))
       ->addCancelButton('/', pht('Alas!'));
   }
 
 
   private function addQueryParams(PhutilURI $uri, array $params) {
     $full_uri = clone $uri;
 
     foreach ($params as $key => $value) {
       if (strlen($value)) {
         $full_uri->setQueryParam($key, $value);
       }
     }
 
     return $full_uri;
   }
 
 }
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php
index 00be8a1e8..b71faae89 100644
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php
+++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php
@@ -1,15 +1,8 @@
 <?php
 
 abstract class PhabricatorOAuthServerController
   extends PhabricatorController {
 
   const CONTEXT_AUTHORIZE = 'oauthserver.authorize';
 
-  protected function buildApplicationCrumbs() {
-    // We're specifically not putting an "OAuth Server" application crumb
-    // on these pages because it doesn't make sense to send users there on
-    // the auth workflows.
-    return new PHUICrumbsView();
-  }
-
 }
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
deleted file mode 100644
index e5966518f..000000000
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-final class PhabricatorOAuthServerTestController
-  extends PhabricatorOAuthServerController {
-
-  public function handleRequest(AphrontRequest $request) {
-    $viewer = $this->getViewer();
-    $id = $request->getURIData('id');
-
-    $client = id(new PhabricatorOAuthServerClientQuery())
-      ->setViewer($viewer)
-      ->withIDs(array($id))
-      ->executeOne();
-    if (!$client) {
-      return new Aphront404Response();
-    }
-
-    $view_uri = $client->getViewURI();
-
-    // Look for an existing authorization.
-    $authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
-      ->setViewer($viewer)
-      ->withUserPHIDs(array($viewer->getPHID()))
-      ->withClientPHIDs(array($client->getPHID()))
-      ->executeOne();
-    if ($authorization) {
-      return $this->newDialog()
-        ->setTitle(pht('Already Authorized'))
-        ->appendParagraph(
-          pht(
-            'You have already authorized this application to access your '.
-            'account.'))
-        ->addCancelButton($view_uri, pht('Close'));
-    }
-
-    if ($request->isFormPost()) {
-      $server = id(new PhabricatorOAuthServer())
-        ->setUser($viewer)
-        ->setClient($client);
-
-      $scope = array();
-      $authorization = $server->authorizeClient($scope);
-
-      $id = $authorization->getID();
-      $panel_uri = '/settings/panel/oauthorizations/?id='.$id;
-
-      return id(new AphrontRedirectResponse())->setURI($panel_uri);
-    }
-
-    // TODO: It would be nice to put scope options in this dialog, maybe?
-
-    return $this->newDialog()
-      ->setTitle(pht('Authorize Application?'))
-      ->appendParagraph(
-        pht(
-          'This will create an authorization, permitting %s to access '.
-          'your account.',
-          phutil_tag('strong', array(), $client->getName())))
-      ->addCancelButton($view_uri)
-      ->addSubmitButton(pht('Authorize Application'));
-  }
-}
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php
index 7e35574f6..3b5ff7257 100644
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php
+++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php
@@ -1,160 +1,170 @@
 <?php
 
 final class PhabricatorOAuthServerTokenController
   extends PhabricatorOAuthServerController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function shouldAllowRestrictedParameter($parameter_name) {
     if ($parameter_name == 'code') {
       return true;
     }
     return parent::shouldAllowRestrictedParameter($parameter_name);
   }
 
   public function handleRequest(AphrontRequest $request) {
     $grant_type = $request->getStr('grant_type');
     $code = $request->getStr('code');
     $redirect_uri = $request->getStr('redirect_uri');
     $client_phid = $request->getStr('client_id');
     $client_secret = $request->getStr('client_secret');
     $response = new PhabricatorOAuthResponse();
     $server = new PhabricatorOAuthServer();
 
     if ($grant_type != 'authorization_code') {
       $response->setError('unsupported_grant_type');
       $response->setErrorDescription(
         pht(
           'Only %s %s is supported.',
           'grant_type',
           'authorization_code'));
       return $response;
     }
 
     if (!$code) {
       $response->setError('invalid_request');
       $response->setErrorDescription(pht('Required parameter code missing.'));
       return $response;
     }
 
     if (!$client_phid) {
       $response->setError('invalid_request');
       $response->setErrorDescription(
         pht(
           'Required parameter %s missing.',
           'client_id'));
       return $response;
     }
 
     if (!$client_secret) {
       $response->setError('invalid_request');
       $response->setErrorDescription(
         pht(
           'Required parameter %s missing.',
           'client_secret'));
       return $response;
     }
 
     // one giant try / catch around all the exciting database stuff so we
     // can return a 'server_error' response if something goes wrong!
     try {
       $auth_code = id(new PhabricatorOAuthServerAuthorizationCode())
         ->loadOneWhere('code = %s',
                        $code);
       if (!$auth_code) {
         $response->setError('invalid_grant');
         $response->setErrorDescription(
           pht(
-            'Authorization code %d not found.',
+            'Authorization code %s not found.',
             $code));
         return $response;
       }
 
       // if we have an auth code redirect URI, there must be a redirect_uri
       // in the request and it must match the auth code redirect uri *exactly*
       $auth_code_redirect_uri = $auth_code->getRedirectURI();
       if ($auth_code_redirect_uri) {
         $auth_code_redirect_uri = new PhutilURI($auth_code_redirect_uri);
         $redirect_uri           = new PhutilURI($redirect_uri);
         if (!$redirect_uri->getDomain() ||
              $redirect_uri != $auth_code_redirect_uri) {
           $response->setError('invalid_grant');
           $response->setErrorDescription(
             pht(
               'Redirect URI in request must exactly match redirect URI '.
               'from authorization code.'));
           return $response;
         }
       } else if ($redirect_uri) {
         $response->setError('invalid_grant');
         $response->setErrorDescription(
           pht(
             'Redirect URI in request and no redirect URI in authorization '.
             'code. The two must exactly match.'));
         return $response;
       }
 
       $client = id(new PhabricatorOAuthServerClient())
         ->loadOneWhere('phid = %s', $client_phid);
       if (!$client) {
         $response->setError('invalid_client');
         $response->setErrorDescription(
           pht(
-            'Client with %s %d not found.',
+            'Client with %s %s not found.',
             'client_id',
             $client_phid));
         return $response;
       }
+
+      if ($client->getIsDisabled()) {
+        $response->setError('invalid_client');
+        $response->setErrorDescription(
+          pht(
+            'OAuth application "%s" has been disabled.',
+            $client->getName()));
+
+        return $response;
+      }
+
       $server->setClient($client);
 
       $user_phid = $auth_code->getUserPHID();
       $user = id(new PhabricatorUser())
         ->loadOneWhere('phid = %s', $user_phid);
       if (!$user) {
         $response->setError('invalid_grant');
         $response->setErrorDescription(
           pht(
-            'User with PHID %d not found.',
+            'User with PHID %s not found.',
             $user_phid));
         return $response;
       }
       $server->setUser($user);
 
       $test_code = new PhabricatorOAuthServerAuthorizationCode();
       $test_code->setClientSecret($client_secret);
       $test_code->setClientPHID($client_phid);
       $is_good_code = $server->validateAuthorizationCode(
         $auth_code,
         $test_code);
       if (!$is_good_code) {
         $response->setError('invalid_grant');
         $response->setErrorDescription(
           pht(
-            'Invalid authorization code %d.',
+            'Invalid authorization code %s.',
             $code));
         return $response;
       }
 
       $unguarded    = AphrontWriteGuard::beginScopedUnguardedWrites();
       $access_token = $server->generateAccessToken();
       $auth_code->delete();
       unset($unguarded);
       $result = array(
         'access_token' => $access_token->getToken(),
-        'token_type'   => 'Bearer',
-        'expires_in'   => PhabricatorOAuthServer::ACCESS_TOKEN_TIMEOUT,
+        'token_type' => 'Bearer',
       );
       return $response->setContent($result);
     } catch (Exception $e) {
       $response->setError('server_error');
       $response->setErrorDescription(
         pht(
           'The authorization server encountered an unexpected condition '.
           'which prevented it from fulfilling the request.'));
       return $response;
     }
   }
 
 }
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php
deleted file mode 100644
index 409063d9e..000000000
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-final class PhabricatorOAuthClientDeleteController
-  extends PhabricatorOAuthClientController {
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
-
-    $client = id(new PhabricatorOAuthServerClientQuery())
-      ->setViewer($viewer)
-      ->withPHIDs(array($this->getClientPHID()))
-      ->requireCapabilities(
-        array(
-          PhabricatorPolicyCapability::CAN_VIEW,
-          PhabricatorPolicyCapability::CAN_EDIT,
-        ))
-      ->executeOne();
-    if (!$client) {
-      return new Aphront404Response();
-    }
-
-    if ($request->isFormPost()) {
-      $client->delete();
-      $app_uri = $this->getApplicationURI();
-      return id(new AphrontRedirectResponse())->setURI($app_uri);
-    }
-
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
-      ->setTitle(pht('Delete OAuth Application?'))
-      ->appendParagraph(
-        pht(
-          'Really delete the OAuth application %s?',
-          phutil_tag('strong', array(), $client->getName())))
-      ->addCancelButton($client->getViewURI())
-      ->addSubmitButton(pht('Delete Application'));
-
-    return id(new AphrontDialogResponse())->setDialog($dialog);
-  }
-
-}
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php
new file mode 100644
index 000000000..2ea995536
--- /dev/null
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php
@@ -0,0 +1,67 @@
+<?php
+
+final class PhabricatorOAuthClientDisableController
+  extends PhabricatorOAuthClientController {
+
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $this->getViewer();
+
+    $client = id(new PhabricatorOAuthServerClientQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($request->getURIData('id')))
+      ->requireCapabilities(
+        array(
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
+        ))
+      ->executeOne();
+    if (!$client) {
+      return new Aphront404Response();
+    }
+
+    $done_uri = $client->getViewURI();
+    $is_disable = !$client->getIsDisabled();
+
+    if ($request->isFormPost()) {
+      $xactions = array();
+
+      $xactions[] = id(new PhabricatorOAuthServerTransaction())
+        ->setTransactionType(PhabricatorOAuthServerTransaction::TYPE_DISABLED)
+        ->setNewValue((int)$is_disable);
+
+      $editor = id(new PhabricatorOAuthServerEditor())
+        ->setActor($viewer)
+        ->setContentSourceFromRequest($request)
+        ->setContinueOnNoEffect(true)
+        ->setContinueOnMissingFields(true)
+        ->applyTransactions($client, $xactions);
+
+      return id(new AphrontRedirectResponse())->setURI($done_uri);
+    }
+
+    if ($is_disable) {
+      $title = pht('Disable OAuth Application');
+      $body = pht(
+        'Really disable the %s OAuth application? Users will no longer be '.
+        'able to authenticate against it, nor access Phabricator using '.
+        'tokens generated by this application.',
+        phutil_tag('strong', array(), $client->getName()));
+      $button = pht('Disable Application');
+    } else {
+      $title = pht('Enable OAuth Application');
+      $body = pht(
+        'Really enable the %s OAuth application? Users will be able to '.
+        'authenticate against it, and existing tokens will become usable '.
+        'again.',
+        phutil_tag('strong', array(), $client->getName()));
+      $button = pht('Enable Application');
+    }
+
+    return $this->newDialog()
+      ->setTitle($title)
+      ->appendParagraph($body)
+      ->addCancelButton($done_uri)
+      ->addSubmitButton($button);
+  }
+
+}
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php
index cd194aa74..178a2df35 100644
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php
@@ -1,137 +1,12 @@
 <?php
 
 final class PhabricatorOAuthClientEditController
   extends PhabricatorOAuthClientController {
 
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
-
-    $phid = $this->getClientPHID();
-    if ($phid) {
-      $client = id(new PhabricatorOAuthServerClientQuery())
-        ->setViewer($viewer)
-        ->withPHIDs(array($phid))
-        ->requireCapabilities(
-          array(
-            PhabricatorPolicyCapability::CAN_VIEW,
-            PhabricatorPolicyCapability::CAN_EDIT,
-          ))
-        ->executeOne();
-      if (!$client) {
-        return new Aphront404Response();
-      }
-
-      $title = pht('Edit OAuth Application: %s', $client->getName());
-      $submit_button = pht('Save Application');
-      $crumb_text = pht('Edit');
-      $cancel_uri = $client->getViewURI();
-      $is_new = false;
-    } else {
-      $this->requireApplicationCapability(
-        PhabricatorOAuthServerCreateClientsCapability::CAPABILITY);
-
-      $client = PhabricatorOAuthServerClient::initializeNewClient($viewer);
-
-      $title = pht('Create OAuth Application');
-      $submit_button = pht('Create Application');
-      $crumb_text = pht('Create Application');
-      $cancel_uri = $this->getApplicationURI();
-      $is_new = true;
-    }
-
-    $errors = array();
-    $e_redirect = true;
-    $e_name = true;
-    if ($request->isFormPost()) {
-      $redirect_uri = $request->getStr('redirect_uri');
-      $client->setName($request->getStr('name'));
-      $client->setRedirectURI($redirect_uri);
-
-      if (!strlen($client->getName())) {
-        $errors[] = pht('You must choose a name for this OAuth application.');
-        $e_name = pht('Required');
-      }
-
-      $server = new PhabricatorOAuthServer();
-      $uri = new PhutilURI($redirect_uri);
-      if (!$server->validateRedirectURI($uri)) {
-        $errors[] = pht(
-          'Redirect URI must be a fully qualified domain name '.
-          'with no fragments. See %s for more information on the correct '.
-          'format.',
-          'http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2');
-        $e_redirect = pht('Invalid');
-      }
-
-      $client->setViewPolicy($request->getStr('viewPolicy'));
-      $client->setEditPolicy($request->getStr('editPolicy'));
-      if (!$errors) {
-        $client->save();
-        $view_uri = $client->getViewURI();
-        return id(new AphrontRedirectResponse())->setURI($view_uri);
-      }
-    }
-
-    $policies = id(new PhabricatorPolicyQuery())
-      ->setViewer($viewer)
-      ->setObject($client)
-      ->execute();
-
-    $form = id(new AphrontFormView())
-      ->setUser($viewer)
-      ->appendChild(
-        id(new AphrontFormTextControl())
-          ->setLabel(pht('Name'))
-          ->setName('name')
-          ->setValue($client->getName())
-          ->setError($e_name))
-      ->appendChild(
-        id(new AphrontFormTextControl())
-          ->setLabel(pht('Redirect URI'))
-          ->setName('redirect_uri')
-          ->setValue($client->getRedirectURI())
-          ->setError($e_redirect))
-      ->appendChild(
-        id(new AphrontFormPolicyControl())
-          ->setUser($viewer)
-          ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
-          ->setPolicyObject($client)
-          ->setPolicies($policies)
-          ->setName('viewPolicy'))
-      ->appendChild(
-        id(new AphrontFormPolicyControl())
-          ->setUser($viewer)
-          ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
-          ->setPolicyObject($client)
-          ->setPolicies($policies)
-          ->setName('editPolicy'))
-      ->appendChild(
-        id(new AphrontFormSubmitControl())
-          ->addCancelButton($cancel_uri)
-          ->setValue($submit_button));
-
-    $crumbs = $this->buildApplicationCrumbs();
-    if (!$is_new) {
-      $crumbs->addTextCrumb(
-        $client->getName(),
-        $client->getViewURI());
-    }
-    $crumbs->addTextCrumb($crumb_text);
-
-    $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
-      ->setFormErrors($errors)
-      ->setForm($form);
-
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+  public function handleRequest(AphrontRequest $request) {
+    return id(new PhabricatorOAuthServerEditEngine())
+      ->setController($this)
+      ->buildResponse();
   }
 
 }
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php
index 217fa1d7b..513bed7bc 100644
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php
@@ -1,42 +1,26 @@
 <?php
 
 final class PhabricatorOAuthClientListController
   extends PhabricatorOAuthClientController {
 
-  private $queryKey;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->queryKey = idx($data, 'queryKey');
-  }
-
-  public function processRequest() {
-    $controller = id(new PhabricatorApplicationSearchController())
-      ->setQueryKey($this->queryKey)
-      ->setSearchEngine(new PhabricatorOAuthServerClientSearchEngine())
-      ->setNavigation($this->buildSideNavView());
-
-    return $this->delegateToController($controller);
+  public function handleRequest(AphrontRequest $request) {
+    return id(new PhabricatorOAuthServerClientSearchEngine())
+      ->setController($this)
+      ->buildResponse();
   }
 
   protected function buildApplicationCrumbs() {
     $crumbs = parent::buildApplicationCrumbs();
 
-    $can_create = $this->hasApplicationCapability(
-        PhabricatorOAuthServerCreateClientsCapability::CAPABILITY);
-
-    $crumbs->addAction(
-      id(new PHUIListItemView())
-        ->setHref($this->getApplicationURI('client/create/'))
-        ->setName(pht('Create Application'))
-        ->setDisabled(!$can_create)
-        ->setWorkflow(!$can_create)
-        ->setIcon('fa-plus-square'));
+    id(new PhabricatorOAuthServerEditEngine())
+      ->setViewer($this->getViewer())
+      ->addActionToCrumbs($crumbs);
 
     return $crumbs;
   }
 
 }
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php
index ceb1379b7..9f4e0d068 100644
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php
@@ -1,70 +1,68 @@
 <?php
 
 final class PhabricatorOAuthClientSecretController
   extends PhabricatorOAuthClientController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $client = id(new PhabricatorOAuthServerClientQuery())
       ->setViewer($viewer)
-      ->withPHIDs(array($this->getClientPHID()))
+      ->withIDs(array($request->getURIData('id')))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$client) {
       return new Aphront404Response();
     }
 
     $view_uri = $client->getViewURI();
     $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
       $viewer,
       $request,
       $view_uri);
 
     if ($request->isFormPost()) {
       $secret = $client->getSecret();
+
       $body = id(new PHUIFormLayoutView())
         ->appendChild(
           id(new AphrontFormTextAreaControl())
-          ->setLabel(pht('Plaintext'))
-          ->setReadOnly(true)
-          ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
-          ->setValue($secret));
+            ->setLabel(pht('Plaintext'))
+            ->setReadOnly(true)
+            ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
+            ->setValue($secret));
 
-      $dialog = id(new AphrontDialogView())
-        ->setUser($viewer)
+      return $this->newDialog()
         ->setWidth(AphrontDialogView::WIDTH_FORM)
         ->setTitle(pht('Application Secret'))
         ->appendChild($body)
         ->addCancelButton($view_uri, pht('Done'));
-
-      return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     if ($is_serious) {
       $body = pht(
         'The secret associated with this OAuth application will be shown in '.
         'plain text on your screen.');
     } else {
       $body = pht(
         'The secret associated with this OAuth application will be shown in '.
         'plain text on your screen. Before continuing, wrap your arms around '.
         'your monitor to create a human shield, keeping it safe from prying '.
         'eyes. Protect company secrets!');
     }
+
     return $this->newDialog()
-      ->setUser($viewer)
       ->setTitle(pht('Really show application secret?'))
       ->appendChild($body)
       ->addSubmitButton(pht('Show Application Secret'))
       ->addCancelButton($view_uri);
   }
 
 }
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php
new file mode 100644
index 000000000..427c1be2a
--- /dev/null
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php
@@ -0,0 +1,67 @@
+<?php
+
+final class PhabricatorOAuthClientTestController
+  extends PhabricatorOAuthClientController {
+
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $this->getViewer();
+    $id = $request->getURIData('id');
+
+    $client = id(new PhabricatorOAuthServerClientQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($id))
+      ->executeOne();
+    if (!$client) {
+      return new Aphront404Response();
+    }
+
+    $done_uri = $client->getViewURI();
+
+    if ($request->isFormPost()) {
+      $server = id(new PhabricatorOAuthServer())
+        ->setUser($viewer)
+        ->setClient($client);
+
+      // Create an authorization if we don't already have one.
+      $authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
+        ->setViewer($viewer)
+        ->withUserPHIDs(array($viewer->getPHID()))
+        ->withClientPHIDs(array($client->getPHID()))
+        ->executeOne();
+      if (!$authorization) {
+        $scope = array();
+        $authorization = $server->authorizeClient($scope);
+      }
+
+      $access_token = $server->generateAccessToken();
+
+      $form = id(new AphrontFormView())
+        ->setViewer($viewer)
+        ->appendInstructions(
+          pht(
+            'Keep this token private, it allows any bearer to access '.
+            'your account on behalf of this application.'))
+        ->appendChild(
+          id(new AphrontFormTextControl())
+            ->setLabel(pht('Token'))
+            ->setValue($access_token->getToken()));
+
+      return $this->newDialog()
+        ->setTitle(pht('OAuth Access Token'))
+        ->appendForm($form)
+        ->addCancelButton($done_uri, pht('Close'));
+    }
+
+    // TODO: It would be nice to put scope options in this dialog, maybe?
+
+    return $this->newDialog()
+      ->setTitle(pht('Authorize Application?'))
+      ->appendParagraph(
+        pht(
+          'This will create an authorization and OAuth token, permitting %s '.
+          'to access your account.',
+          phutil_tag('strong', array(), $client->getName())))
+      ->addCancelButton($done_uri)
+      ->addSubmitButton(pht('Authorize Application'));
+  }
+}
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php
index 368de86f1..394ace52a 100644
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php
@@ -1,126 +1,141 @@
 <?php
 
 final class PhabricatorOAuthClientViewController
   extends PhabricatorOAuthClientController {
 
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $this->getViewer();
 
     $client = id(new PhabricatorOAuthServerClientQuery())
       ->setViewer($viewer)
-      ->withPHIDs(array($this->getClientPHID()))
+      ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$client) {
       return new Aphront404Response();
     }
 
     $header = $this->buildHeaderView($client);
     $actions = $this->buildActionView($client);
     $properties = $this->buildPropertyListView($client);
     $properties->setActionList($actions);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($client->getName());
 
+    $timeline = $this->buildTransactionTimeline(
+      $client,
+      new PhabricatorOAuthServerTransactionQuery());
+    $timeline->setShouldTerminate(true);
+
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('OAuth Application: %s', $client->getName()),
-      ));
+    $title = pht('OAuth Application: %s', $client->getName());
+
+    return $this->newPage()
+      ->setCrumbs($crumbs)
+      ->setTitle($title)
+      ->appendChild(
+        array(
+          $box,
+          $timeline,
+        ));
   }
 
   private function buildHeaderView(PhabricatorOAuthServerClient $client) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader(pht('OAuth Application: %s', $client->getName()))
       ->setPolicyObject($client);
 
+    if ($client->getIsDisabled()) {
+      $header->setStatus('fa-ban', 'indigo', pht('Disabled'));
+    } else {
+      $header->setStatus('fa-check', 'green', pht('Enabled'));
+    }
+
     return $header;
   }
 
   private function buildActionView(PhabricatorOAuthServerClient $client) {
-    $viewer = $this->getRequest()->getUser();
+    $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $client,
       PhabricatorPolicyCapability::CAN_EDIT);
 
-    $authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
-      ->setViewer($viewer)
-      ->withUserPHIDs(array($viewer->getPHID()))
-      ->withClientPHIDs(array($client->getPHID()))
-      ->executeOne();
-    $is_authorized = (bool)$authorization;
     $id = $client->getID();
-    $phid = $client->getPHID();
 
     $view = id(new PhabricatorActionListView())
       ->setUser($viewer);
 
     $view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Application'))
         ->setIcon('fa-pencil')
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit)
         ->setHref($client->getEditURI()));
 
     $view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Show Application Secret'))
         ->setIcon('fa-eye')
-        ->setHref($this->getApplicationURI("client/secret/{$phid}/"))
+        ->setHref($this->getApplicationURI("client/secret/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
+    $is_disabled = $client->getIsDisabled();
+    if ($is_disabled) {
+      $disable_text = pht('Enable Application');
+      $disable_icon = 'fa-check';
+    } else {
+      $disable_text = pht('Disable Application');
+      $disable_icon = 'fa-ban';
+    }
+
+    $disable_uri = $this->getApplicationURI("client/disable/{$id}/");
+
     $view->addAction(
       id(new PhabricatorActionView())
-        ->setName(pht('Delete Application'))
-        ->setIcon('fa-times')
+        ->setName($disable_text)
+        ->setIcon($disable_icon)
         ->setWorkflow(true)
         ->setDisabled(!$can_edit)
-        ->setHref($client->getDeleteURI()));
+        ->setHref($disable_uri));
 
     $view->addAction(
       id(new PhabricatorActionView())
-        ->setName(pht('Create Test Authorization'))
-        ->setIcon('fa-wrench')
+        ->setName(pht('Generate Test Token'))
+        ->setIcon('fa-plus')
         ->setWorkflow(true)
-        ->setDisabled($is_authorized)
-        ->setHref($this->getApplicationURI('test/'.$id.'/')));
+        ->setHref($this->getApplicationURI("client/test/{$id}/")));
 
     return $view;
   }
 
   private function buildPropertyListView(PhabricatorOAuthServerClient $client) {
     $viewer = $this->getRequest()->getUser();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $view->addProperty(
-      pht('Client ID'),
+      pht('Client PHID'),
       $client->getPHID());
 
     $view->addProperty(
       pht('Redirect URI'),
       $client->getRedirectURI());
 
     $view->addProperty(
       pht('Created'),
       phabricator_datetime($client->getDateCreated(), $viewer));
 
     return $view;
   }
 }
diff --git a/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php
new file mode 100644
index 000000000..ad47552c1
--- /dev/null
+++ b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php
@@ -0,0 +1,100 @@
+<?php
+
+final class PhabricatorOAuthServerEditEngine
+  extends PhabricatorEditEngine {
+
+  const ENGINECONST = 'oauthserver.application';
+
+  public function isEngineConfigurable() {
+    return false;
+  }
+
+  public function getEngineName() {
+    return pht('OAuth Applications');
+  }
+
+  public function getSummaryHeader() {
+    return pht('Edit OAuth Applications');
+  }
+
+  public function getSummaryText() {
+    return pht('This engine manages OAuth client applications.');
+  }
+
+  public function getEngineApplicationClass() {
+    return 'PhabricatorOAuthServerApplication';
+  }
+
+  protected function newEditableObject() {
+    return PhabricatorOAuthServerClient::initializeNewClient(
+      $this->getViewer());
+  }
+
+  protected function newObjectQuery() {
+    return id(new PhabricatorOAuthServerClientQuery());
+  }
+
+  protected function getObjectCreateTitleText($object) {
+    return pht('Create OAuth Server');
+  }
+
+  protected function getObjectCreateButtonText($object) {
+    return pht('Create OAuth Server');
+  }
+
+  protected function getObjectEditTitleText($object) {
+    return pht('Edit OAuth Server: %s', $object->getName());
+  }
+
+  protected function getObjectEditShortText($object) {
+    return pht('Edit OAuth Server');
+  }
+
+  protected function getObjectCreateShortText() {
+    return pht('Create OAuth Server');
+  }
+
+  protected function getObjectName() {
+    return pht('OAuth Server');
+  }
+
+  protected function getObjectViewURI($object) {
+    return $object->getViewURI();
+  }
+
+  protected function getCreateNewObjectPolicy() {
+    return $this->getApplication()->getPolicy(
+      PhabricatorOAuthServerCreateClientsCapability::CAPABILITY);
+  }
+
+  protected function buildCustomEditFields($object) {
+    return array(
+      id(new PhabricatorTextEditField())
+        ->setKey('name')
+        ->setLabel(pht('Name'))
+        ->setIsRequired(true)
+        ->setTransactionType(PhabricatorOAuthServerTransaction::TYPE_NAME)
+        ->setDescription(pht('The name of the OAuth application.'))
+        ->setConduitDescription(pht('Rename the application.'))
+        ->setConduitTypeDescription(pht('New application name.'))
+        ->setValue($object->getName()),
+      id(new PhabricatorTextEditField())
+        ->setKey('redirectURI')
+        ->setLabel(pht('Redirect URI'))
+        ->setIsRequired(true)
+        ->setTransactionType(
+          PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI)
+        ->setDescription(
+          pht('The redirect URI for OAuth handshakes.'))
+        ->setConduitDescription(
+          pht(
+            'Change where this application redirects users to during OAuth '.
+            'handshakes.'))
+        ->setConduitTypeDescription(
+          pht(
+            'New OAuth application redirect URI.'))
+        ->setValue($object->getRedirectURI()),
+    );
+  }
+
+}
diff --git a/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php
new file mode 100644
index 000000000..32b9d4505
--- /dev/null
+++ b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php
@@ -0,0 +1,146 @@
+<?php
+
+final class PhabricatorOAuthServerEditor
+  extends PhabricatorApplicationTransactionEditor {
+
+  public function getEditorApplicationClass() {
+    return 'PhabricatorOAuthServerApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('OAuth Applications');
+  }
+
+  public function getTransactionTypes() {
+    $types = parent::getTransactionTypes();
+
+    $types[] = PhabricatorOAuthServerTransaction::TYPE_NAME;
+    $types[] = PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI;
+    $types[] = PhabricatorOAuthServerTransaction::TYPE_DISABLED;
+
+    $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+    $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+    return $types;
+  }
+
+  protected function getCustomTransactionOldValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorOAuthServerTransaction::TYPE_NAME:
+        return $object->getName();
+      case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
+        return $object->getRedirectURI();
+      case PhabricatorOAuthServerTransaction::TYPE_DISABLED:
+        return $object->getIsDisabled();
+    }
+  }
+
+  protected function getCustomTransactionNewValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorOAuthServerTransaction::TYPE_NAME:
+      case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
+        return $xaction->getNewValue();
+      case PhabricatorOAuthServerTransaction::TYPE_DISABLED:
+        return (int)$xaction->getNewValue();
+    }
+  }
+
+  protected function applyCustomInternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorOAuthServerTransaction::TYPE_NAME:
+        $object->setName($xaction->getNewValue());
+        return;
+      case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
+        $object->setRedirectURI($xaction->getNewValue());
+        return;
+      case PhabricatorOAuthServerTransaction::TYPE_DISABLED:
+        $object->setIsDisabled($xaction->getNewValue());
+        return;
+    }
+
+    return parent::applyCustomInternalTransaction($object, $xaction);
+  }
+
+  protected function applyCustomExternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorOAuthServerTransaction::TYPE_NAME:
+      case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
+      case PhabricatorOAuthServerTransaction::TYPE_DISABLED:
+        return;
+    }
+
+    return parent::applyCustomExternalTransaction($object, $xaction);
+  }
+
+  protected function validateTransaction(
+    PhabricatorLiskDAO $object,
+    $type,
+    array $xactions) {
+
+    $errors = parent::validateTransaction($object, $type, $xactions);
+
+    switch ($type) {
+      case PhabricatorOAuthServerTransaction::TYPE_NAME:
+        $missing = $this->validateIsEmptyTextField(
+          $object->getName(),
+          $xactions);
+
+        if ($missing) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Required'),
+            pht('OAuth applications must have a name.'),
+            nonempty(last($xactions), null));
+
+          $error->setIsMissingFieldError(true);
+          $errors[] = $error;
+        }
+        break;
+      case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
+        $missing = $this->validateIsEmptyTextField(
+          $object->getRedirectURI(),
+          $xactions);
+        if ($missing) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Required'),
+            pht('OAuth applications must have a valid redirect URI.'),
+            nonempty(last($xactions), null));
+
+          $error->setIsMissingFieldError(true);
+          $errors[] = $error;
+        } else {
+          foreach ($xactions as $xaction) {
+            $redirect_uri = $xaction->getNewValue();
+
+            try {
+              $server = new PhabricatorOAuthServer();
+              $server->assertValidRedirectURI($redirect_uri);
+            } catch (Exception $ex) {
+              $errors[] = new PhabricatorApplicationTransactionValidationError(
+                $type,
+                pht('Invalid'),
+                $ex->getMessage(),
+                $xaction);
+            }
+          }
+        }
+        break;
+    }
+
+    return $errors;
+  }
+
+}
diff --git a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
index f19be1443..a746008f5 100644
--- a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
+++ b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
@@ -1,95 +1,89 @@
 <?php
 
 final class PhabricatorOAuthClientAuthorizationQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $phids;
   private $userPHIDs;
   private $clientPHIDs;
 
-  public function witHPHIDs(array $phids) {
+  public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withUserPHIDs(array $phids) {
     $this->userPHIDs = $phids;
     return $this;
   }
 
   public function withClientPHIDs(array $phids) {
     $this->clientPHIDs = $phids;
     return $this;
   }
 
+  public function newResultObject() {
+    return new PhabricatorOAuthClientAuthorization();
+  }
+
   protected function loadPage() {
-    $table  = new PhabricatorOAuthClientAuthorization();
-    $conn_r = $table->establishConnection('r');
-
-    $data = queryfx_all(
-      $conn_r,
-      'SELECT * FROM %T auth %Q %Q %Q',
-      $table->getTableName(),
-      $this->buildWhereClause($conn_r),
-      $this->buildOrderClause($conn_r),
-      $this->buildLimitClause($conn_r));
-
-    return $table->loadAllFromArray($data);
+    return $this->loadStandardPage($this->newResultObject());
   }
 
   protected function willFilterPage(array $authorizations) {
     $client_phids = mpull($authorizations, 'getClientPHID');
 
     $clients = id(new PhabricatorOAuthServerClientQuery())
       ->setViewer($this->getViewer())
       ->setParentQuery($this)
       ->withPHIDs($client_phids)
       ->execute();
     $clients = mpull($clients, null, 'getPHID');
 
     foreach ($authorizations as $key => $authorization) {
       $client = idx($clients, $authorization->getClientPHID());
+
       if (!$client) {
+        $this->didRejectResult($authorization);
         unset($authorizations[$key]);
         continue;
       }
+
       $authorization->attachClient($client);
     }
 
     return $authorizations;
   }
 
-  protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
-    $where = array();
+  protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+    $where = parent::buildWhereClauseParts($conn);
 
-    if ($this->phids) {
+    if ($this->phids !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'phid IN (%Ls)',
         $this->phids);
     }
 
-    if ($this->userPHIDs) {
+    if ($this->userPHIDs !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'userPHID IN (%Ls)',
         $this->userPHIDs);
     }
 
-    if ($this->clientPHIDs) {
+    if ($this->clientPHIDs !== null) {
       $where[] = qsprintf(
-        $conn_r,
+        $conn,
         'clientPHID IN (%Ls)',
         $this->clientPHIDs);
     }
 
-    $where[] = $this->buildPagingClause($conn_r);
-
-    return $this->formatWhereClause($where);
+    return $where;
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorOAuthServerApplication';
   }
 
 }
diff --git a/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php b/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php
index 8168527f4..3cb027fe4 100644
--- a/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php
+++ b/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php
@@ -1,118 +1,103 @@
 <?php
 
 final class PhabricatorOAuthServerClientSearchEngine
   extends PhabricatorApplicationSearchEngine {
 
   public function getResultTypeDescription() {
     return pht('OAuth Clients');
   }
 
   public function getApplicationClassName() {
     return 'PhabricatorOAuthServerApplication';
   }
 
-  public function buildSavedQueryFromRequest(AphrontRequest $request) {
-    $saved = new PhabricatorSavedQuery();
-
-    $saved->setParameter(
-      'creatorPHIDs',
-      $this->readUsersFromRequest($request, 'creators'));
-
-    return $saved;
+  public function newQuery() {
+    return id(new PhabricatorOAuthServerClientQuery());
   }
 
-  public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
-    $query = id(new PhabricatorOAuthServerClientQuery());
+  protected function buildQueryFromParameters(array $map) {
+    $query = $this->newQuery();
 
-    $creator_phids = $saved->getParameter('creatorPHIDs', array());
-    if ($creator_phids) {
-      $query->withCreatorPHIDs($saved->getParameter('creatorPHIDs', array()));
+    if ($map['creatorPHIDs']) {
+      $query->withCreatorPHIDs($map['creatorPHIDs']);
     }
 
     return $query;
   }
 
-  public function buildSearchForm(
-    AphrontFormView $form,
-    PhabricatorSavedQuery $saved_query) {
-
-    $creator_phids = $saved_query->getParameter('creatorPHIDs', array());
-
-    $form
-      ->appendControl(
-        id(new AphrontFormTokenizerControl())
-          ->setDatasource(new PhabricatorPeopleDatasource())
-          ->setName('creators')
-          ->setLabel(pht('Creators'))
-          ->setValue($creator_phids));
+  protected function buildCustomSearchFields() {
+    return array(
+      id(new PhabricatorUsersSearchField())
+        ->setAliases(array('creators'))
+        ->setKey('creatorPHIDs')
+        ->setConduitKey('creators')
+        ->setLabel(pht('Creators'))
+        ->setDescription(
+          pht('Search for applications created by particular users.')),
+    );
   }
 
   protected function getURI($path) {
     return '/oauthserver/'.$path;
   }
 
   protected function getBuiltinQueryNames() {
     $names = array();
 
     if ($this->requireViewer()->isLoggedIn()) {
       $names['created'] = pht('Created');
     }
 
     $names['all'] = pht('All Applications');
 
     return $names;
   }
 
   public function buildSavedQueryFromBuiltin($query_key) {
     $query = $this->newSavedQuery();
     $query->setQueryKey($query_key);
 
     switch ($query_key) {
       case 'all':
         return $query;
       case 'created':
         return $query->setParameter(
           'creatorPHIDs',
           array($this->requireViewer()->getPHID()));
     }
 
     return parent::buildSavedQueryFromBuiltin($query_key);
   }
 
-  protected function getRequiredHandlePHIDsForResultList(
-    array $clients,
-    PhabricatorSavedQuery $query) {
-    return mpull($clients, 'getCreatorPHID');
-  }
-
   protected function renderResultList(
     array $clients,
     PhabricatorSavedQuery $query,
     array $handles) {
-    assert_instances_of($clients, 'PhabricatorOauthServerClient');
+    assert_instances_of($clients, 'PhabricatorOAuthServerClient');
 
     $viewer = $this->requireViewer();
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
     foreach ($clients as $client) {
-      $creator = $handles[$client->getCreatorPHID()];
-
       $item = id(new PHUIObjectItemView())
         ->setObjectName(pht('Application %d', $client->getID()))
         ->setHeader($client->getName())
         ->setHref($client->getViewURI())
-        ->setObject($client)
-        ->addByline(pht('Creator: %s', $creator->renderLink()));
+        ->setObject($client);
+
+      if ($client->getIsDisabled()) {
+        $item->setDisabled(true);
+      }
 
       $list->addItem($item);
     }
 
     $result = new PhabricatorApplicationSearchResultView();
     $result->setObjectList($list);
     $result->setNoDataString(pht('No clients found.'));
 
     return $result;
   }
 
 }
diff --git a/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php
new file mode 100644
index 000000000..4dd21e260
--- /dev/null
+++ b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorOAuthServerTransactionQuery
+  extends PhabricatorApplicationTransactionQuery {
+
+  public function getTemplateApplicationTransaction() {
+    return new PhabricatorOAuthServerTransaction();
+  }
+
+}
diff --git a/src/applications/oauthserver/storage/PhabricatorOAuthServerClient.php b/src/applications/oauthserver/storage/PhabricatorOAuthServerClient.php
index 79de173ba..4a4f48bfe 100644
--- a/src/applications/oauthserver/storage/PhabricatorOAuthServerClient.php
+++ b/src/applications/oauthserver/storage/PhabricatorOAuthServerClient.php
@@ -1,121 +1,143 @@
 <?php
 
 final class PhabricatorOAuthServerClient
   extends PhabricatorOAuthServerDAO
   implements
     PhabricatorPolicyInterface,
+    PhabricatorApplicationTransactionInterface,
     PhabricatorDestructibleInterface {
 
   protected $secret;
   protected $name;
   protected $redirectURI;
   protected $creatorPHID;
-  protected $isTrusted = 0;
+  protected $isTrusted;
   protected $viewPolicy;
   protected $editPolicy;
+  protected $isDisabled;
 
   public function getEditURI() {
-    return '/oauthserver/client/edit/'.$this->getPHID().'/';
+    $id = $this->getID();
+    return "/oauthserver/edit/{$id}/";
   }
 
   public function getViewURI() {
-    return '/oauthserver/client/view/'.$this->getPHID().'/';
-  }
-
-  public function getDeleteURI() {
-    return '/oauthserver/client/delete/'.$this->getPHID().'/';
+    $id = $this->getID();
+    return "/oauthserver/client/view/{$id}/";
   }
 
   public static function initializeNewClient(PhabricatorUser $actor) {
     return id(new PhabricatorOAuthServerClient())
       ->setCreatorPHID($actor->getPHID())
       ->setSecret(Filesystem::readRandomCharacters(32))
       ->setViewPolicy(PhabricatorPolicies::POLICY_USER)
-      ->setEditPolicy($actor->getPHID());
+      ->setEditPolicy($actor->getPHID())
+      ->setIsDisabled(0)
+      ->setIsTrusted(0);
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'text255',
         'secret' => 'text32',
         'redirectURI' => 'text255',
         'isTrusted' => 'bool',
+        'isDisabled' => 'bool',
       ),
       self::CONFIG_KEY_SCHEMA => array(
-        'key_phid' => null,
-        'phid' => array(
-          'columns' => array('phid'),
-          'unique' => true,
-        ),
         'creatorPHID' => array(
           'columns' => array('creatorPHID'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorOAuthServerClientPHIDType::TYPECONST);
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 
+
+/* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
+
+
+  public function getApplicationTransactionEditor() {
+    return new PhabricatorOAuthServerEditor();
+  }
+
+  public function getApplicationTransactionObject() {
+    return $this;
+  }
+
+  public function getApplicationTransactionTemplate() {
+    return new PhabricatorOAuthServerTransaction();
+  }
+
+  public function willRenderTimeline(
+    PhabricatorApplicationTransactionView $timeline,
+    AphrontRequest $request) {
+    return $timeline;
+  }
+
+
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
+
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $this->delete();
 
       $authorizations = id(new PhabricatorOAuthClientAuthorization())
         ->loadAllWhere('clientPHID = %s', $this->getPHID());
       foreach ($authorizations as $authorization) {
         $authorization->delete();
       }
 
       $tokens = id(new PhabricatorOAuthServerAccessToken())
         ->loadAllWhere('clientPHID = %s', $this->getPHID());
       foreach ($tokens as $token) {
         $token->delete();
       }
 
       $codes = id(new PhabricatorOAuthServerAuthorizationCode())
         ->loadAllWhere('clientPHID = %s', $this->getPHID());
       foreach ($codes as $code) {
         $code->delete();
       }
 
     $this->saveTransaction();
 
   }
 }
diff --git a/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php b/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php
new file mode 100644
index 000000000..b2624dd9a
--- /dev/null
+++ b/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php
@@ -0,0 +1,63 @@
+<?php
+
+final class PhabricatorOAuthServerTransaction
+  extends PhabricatorApplicationTransaction {
+
+  const TYPE_NAME = 'oauthserver.name';
+  const TYPE_REDIRECT_URI = 'oauthserver.redirect-uri';
+  const TYPE_DISABLED = 'oauthserver.disabled';
+
+  public function getApplicationName() {
+    return 'oauth_server';
+  }
+
+  public function getTableName() {
+    return 'oauth_server_transaction';
+  }
+
+  public function getApplicationTransactionType() {
+    return PhabricatorOAuthServerClientPHIDType::TYPECONST;
+  }
+
+  public function getApplicationTransactionCommentObject() {
+    return null;
+  }
+
+  public function getTitle() {
+    $author_phid = $this->getAuthorPHID();
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case PhabricatorTransactions::TYPE_CREATE:
+        return pht(
+          '%s created this OAuth application.',
+          $this->renderHandleLink($author_phid));
+      case self::TYPE_NAME:
+        return pht(
+          '%s renamed this application from "%s" to "%s".',
+          $this->renderHandleLink($author_phid),
+          $old,
+          $new);
+      case self::TYPE_REDIRECT_URI:
+        return pht(
+          '%s changed the application redirect URI from "%s" to "%s".',
+          $this->renderHandleLink($author_phid),
+          $old,
+          $new);
+      case self::TYPE_DISABLED:
+        if ($new) {
+          return pht(
+            '%s disabled this application.',
+            $this->renderHandleLink($author_phid));
+        } else {
+          return pht(
+            '%s enabled this application.',
+            $this->renderHandleLink($author_phid));
+        }
+    }
+
+    return parent::getTitle();
+  }
+
+}
diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php
index ccca55b6c..ad7db5cc6 100644
--- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php
@@ -1,165 +1,171 @@
 <?php
 
 final class PhabricatorOwnersPathsController
   extends PhabricatorOwnersController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getUser();
 
     $package = id(new PhabricatorOwnersPackageQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->needPaths(true)
       ->executeOne();
     if (!$package) {
       return new Aphront404Response();
     }
 
     if ($request->isFormPost()) {
       $paths = $request->getArr('path');
       $repos = $request->getArr('repo');
       $excludes = $request->getArr('exclude');
 
       $path_refs = array();
       foreach ($paths as $key => $path) {
         if (!isset($repos[$key])) {
           throw new Exception(
             pht(
               'No repository PHID for path "%s"!',
               $key));
         }
 
         if (!isset($excludes[$key])) {
           throw new Exception(
             pht(
               'No exclusion value for path "%s"!',
               $key));
         }
 
         $path_refs[] = array(
           'repositoryPHID' => $repos[$key],
           'path' => $path,
           'excluded' => (int)$excludes[$key],
         );
       }
 
       $type_paths = PhabricatorOwnersPackageTransaction::TYPE_PATHS;
 
       $xactions = array();
       $xactions[] = id(new PhabricatorOwnersPackageTransaction())
         ->setTransactionType($type_paths)
         ->setNewValue($path_refs);
 
       $editor = id(new PhabricatorOwnersPackageTransactionEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true)
         ->setContinueOnMissingFields(true);
 
       $editor->applyTransactions($package, $xactions);
 
       return id(new AphrontRedirectResponse())
         ->setURI('/owners/package/'.$package->getID().'/');
     } else {
       $paths = $package->getPaths();
       $path_refs = mpull($paths, 'getRef');
     }
 
     $repos = id(new PhabricatorRepositoryQuery())
       ->setViewer($viewer)
       ->execute();
 
     $default_paths = array();
     foreach ($repos as $repo) {
       $default_path = $repo->getDetail('default-owners-path');
       if ($default_path) {
         $default_paths[$repo->getPHID()] = $default_path;
       }
     }
 
     $repos = mpull($repos, 'getMonogram', 'getPHID');
     asort($repos);
 
     $template = new AphrontTypeaheadTemplateView();
     $template = $template->render();
 
     Javelin::initBehavior(
       'owners-path-editor',
       array(
         'root'                => 'path-editor',
         'table'               => 'paths',
         'add_button'          => 'addpath',
         'repositories'        => $repos,
         'input_template'      => $template,
         'pathRefs'            => $path_refs,
 
         'completeURI'         => '/diffusion/services/path/complete/',
         'validateURI'         => '/diffusion/services/path/validate/',
 
         'repositoryDefaultPaths' => $default_paths,
       ));
 
     require_celerity_resource('owners-path-editor-css');
 
     $cancel_uri = '/owners/package/'.$package->getID().'/';
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new PHUIFormInsetView())
           ->setTitle(pht('Paths'))
           ->addDivAttributes(array('id' => 'path-editor'))
           ->setRightButton(javelin_tag(
               'a',
               array(
                 'href' => '#',
                 'class' => 'button green',
                 'sigil' => 'addpath',
                 'mustcapture' => true,
               ),
               pht('Add New Path')))
           ->setDescription(
             pht(
               'Specify the files and directories which comprise '.
               'this package.'))
           ->setContent(javelin_tag(
               'table',
               array(
                 'class' => 'owners-path-editor-table',
                 'sigil' => 'paths',
               ),
               '')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($cancel_uri)
           ->setValue(pht('Save Paths')));
 
-    $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Edit Paths'))
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Paths'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       $package->getName(),
       $this->getApplicationURI('package/'.$package->getID().'/'));
     $crumbs->addTextCrumb(pht('Edit Paths'));
+    $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-      ),
-      array(
-        'title' => array(
-          $package->getName(),
-          pht('Edit Paths'),
-        ),
-      ));
-  }
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Edit Paths: %s', $package->getName()))
+      ->setHeaderIcon('fa-pencil');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    $title = array($package->getName(), pht('Edit Paths'));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
+      }
 
 }
diff --git a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php
index cfddcbcc4..87afe22cc 100644
--- a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php
@@ -1,68 +1,74 @@
 <?php
 
 final class PassphraseCredentialCreateController extends PassphraseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $types = PassphraseCredentialType::getAllCreateableTypes();
     $types = mpull($types, null, 'getCredentialType');
     $types = msort($types, 'getCredentialTypeName');
 
     $errors = array();
     $e_type = null;
 
     if ($request->isFormPost()) {
       $type = $request->getStr('type');
       if (empty($types[$type])) {
         $errors[] = pht('You must choose a credential type.');
         $e_type = pht('Required');
       }
 
       if (!$errors) {
         $uri = $this->getApplicationURI('edit/?type='.$type);
         return id(new AphrontRedirectResponse())->setURI($uri);
       }
     }
 
     $types_control = id(new AphrontFormRadioButtonControl())
       ->setName('type')
       ->setLabel(pht('Credential Type'))
       ->setError($e_type);
 
     foreach ($types as $type) {
       $types_control->addButton(
         $type->getCredentialType(),
         $type->getCredentialTypeName(),
         $type->getCredentialTypeDescription());
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild($types_control)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Continue'))
           ->addCancelButton($this->getApplicationURI()));
 
     $title = pht('New Credential');
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Create'));
+    $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Create New Credential'))
+      ->setHeaderText(pht('Credential'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-plus-square');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php
index 72c21a9e3..4aa687ddc 100644
--- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php
@@ -1,379 +1,384 @@
 <?php
 
 final class PassphraseCredentialEditController extends PassphraseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if ($id) {
       $credential = id(new PassphraseCredentialQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$credential) {
         return new Aphront404Response();
       }
 
       $type = $this->getCredentialType($credential->getCredentialType());
 
       $is_new = false;
     } else {
       $type_const = $request->getStr('type');
       $type = $this->getCredentialType($type_const);
 
       if (!$type->isCreateable()) {
         throw new Exception(
           pht(
             'Credential has noncreateable type "%s"!',
             $type_const));
       }
 
       $credential = PassphraseCredential::initializeNewCredential($viewer)
         ->setCredentialType($type->getCredentialType())
         ->setProvidesType($type->getProvidesType())
         ->attachImplementation($type);
 
       $is_new = true;
 
       // Prefill username if provided.
       $credential->setUsername((string)$request->getStr('username'));
 
       if (!$request->getStr('isInitialized')) {
         $type->didInitializeNewCredential($viewer, $credential);
       }
     }
 
     $errors = array();
 
     $v_name = $credential->getName();
     $e_name = true;
 
     $v_desc = $credential->getDescription();
     $v_space = $credential->getSpacePHID();
 
     $v_username = $credential->getUsername();
     $e_username = true;
 
     $v_is_locked = false;
 
     $bullet = "\xE2\x80\xA2";
 
     $v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null;
     if ($is_new && ($v_secret === null)) {
       // If we're creating a new credential, the credential type may have
       // populated the secret for us (for example, generated an SSH key). In
       // this case,
       try {
         $v_secret = $credential->getSecret()->openEnvelope();
       } catch (Exception $ex) {
         // Ignore this.
       }
     }
 
     $validation_exception = null;
     $errors = array();
     $e_password = null;
     if ($request->isFormPost()) {
 
       $v_name = $request->getStr('name');
       $v_desc = $request->getStr('description');
       $v_username = $request->getStr('username');
       $v_view_policy = $request->getStr('viewPolicy');
       $v_edit_policy = $request->getStr('editPolicy');
       $v_is_locked = $request->getStr('lock');
 
       $v_secret = $request->getStr('secret');
       $v_space = $request->getStr('spacePHID');
       $v_password = $request->getStr('password');
       $v_decrypt = $v_secret;
 
       $env_secret = new PhutilOpaqueEnvelope($v_secret);
       $env_password = new PhutilOpaqueEnvelope($v_password);
 
       if ($type->requiresPassword($env_secret)) {
         if (strlen($v_password)) {
           $v_decrypt = $type->decryptSecret($env_secret, $env_password);
           if ($v_decrypt === null) {
             $e_password = pht('Incorrect');
             $errors[] = pht(
               'This key requires a password, but the password you provided '.
               'is incorrect.');
           } else {
             $v_decrypt = $v_decrypt->openEnvelope();
           }
         } else {
           $e_password = pht('Required');
           $errors[] = pht(
             'This key requires a password. You must provide the password '.
             'for the key.');
         }
       }
 
       if (!$errors) {
         $type_name = PassphraseCredentialTransaction::TYPE_NAME;
         $type_desc = PassphraseCredentialTransaction::TYPE_DESCRIPTION;
         $type_username = PassphraseCredentialTransaction::TYPE_USERNAME;
         $type_destroy = PassphraseCredentialTransaction::TYPE_DESTROY;
         $type_secret_id = PassphraseCredentialTransaction::TYPE_SECRET_ID;
         $type_is_locked = PassphraseCredentialTransaction::TYPE_LOCK;
         $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY;
         $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY;
         $type_space = PhabricatorTransactions::TYPE_SPACE;
 
         $xactions = array();
 
         $xactions[] = id(new PassphraseCredentialTransaction())
           ->setTransactionType($type_name)
           ->setNewValue($v_name);
 
         $xactions[] = id(new PassphraseCredentialTransaction())
           ->setTransactionType($type_desc)
           ->setNewValue($v_desc);
 
         $xactions[] = id(new PassphraseCredentialTransaction())
           ->setTransactionType($type_view_policy)
           ->setNewValue($v_view_policy);
 
         $xactions[] = id(new PassphraseCredentialTransaction())
           ->setTransactionType($type_edit_policy)
           ->setNewValue($v_edit_policy);
 
         $xactions[] = id(new PassphraseCredentialTransaction())
           ->setTransactionType($type_space)
           ->setNewValue($v_space);
 
         // Open a transaction in case we're writing a new secret; this limits
         // the amount of code which handles secret plaintexts.
         $credential->openTransaction();
 
         if (!$credential->getIsLocked()) {
           if ($type->shouldRequireUsername()) {
             $xactions[] = id(new PassphraseCredentialTransaction())
             ->setTransactionType($type_username)
             ->setNewValue($v_username);
           }
           // If some value other than a sequence of bullets was provided for
           // the credential, update it. In particular, note that we are
           // explicitly allowing empty secrets: one use case is HTTP auth where
           // the username is a secret token which covers both identity and
           // authentication.
 
           if (!preg_match('/^('.$bullet.')+$/', trim($v_decrypt))) {
             // If the credential was previously destroyed, restore it when it is
             // edited if a secret is provided.
             $xactions[] = id(new PassphraseCredentialTransaction())
               ->setTransactionType($type_destroy)
               ->setNewValue(0);
 
             $new_secret = id(new PassphraseSecret())
               ->setSecretData($v_decrypt)
               ->save();
             $xactions[] = id(new PassphraseCredentialTransaction())
               ->setTransactionType($type_secret_id)
               ->setNewValue($new_secret->getID());
           }
 
           $xactions[] = id(new PassphraseCredentialTransaction())
             ->setTransactionType($type_is_locked)
             ->setNewValue($v_is_locked);
         }
 
         try {
           $editor = id(new PassphraseCredentialTransactionEditor())
             ->setActor($viewer)
             ->setContinueOnNoEffect(true)
             ->setContentSourceFromRequest($request)
             ->applyTransactions($credential, $xactions);
 
           $credential->saveTransaction();
 
           if ($request->isAjax()) {
             return id(new AphrontAjaxResponse())->setContent(
               array(
                 'phid' => $credential->getPHID(),
                 'name' => 'K'.$credential->getID().' '.$credential->getName(),
               ));
           } else {
             return id(new AphrontRedirectResponse())
               ->setURI('/K'.$credential->getID());
           }
         } catch (PhabricatorApplicationTransactionValidationException $ex) {
           $credential->killTransaction();
 
           $validation_exception = $ex;
 
           $e_name = $ex->getShortMessage($type_name);
           $e_username = $ex->getShortMessage($type_username);
 
           $credential->setViewPolicy($v_view_policy);
           $credential->setEditPolicy($v_edit_policy);
         }
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($credential)
       ->execute();
 
     $secret_control = $type->newSecretControl();
     $credential_is_locked = $credential->getIsLocked();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('isInitialized', true)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setName('name')
           ->setLabel(pht('Name'))
           ->setValue($v_name)
           ->setError($e_name))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
           ->setName('description')
           ->setLabel(pht('Description'))
           ->setValue($v_desc))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Credential Type'))
           ->setValue($type->getCredentialTypeName()))
       ->appendChild(
         id(new AphrontFormDividerControl()))
       ->appendControl(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($credential)
           ->setSpacePHID($v_space)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendControl(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($credential)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormDividerControl()));
 
     if ($credential_is_locked) {
       $form->appendRemarkupInstructions(
         pht('This credential is permanently locked and can not be edited.'));
     }
 
     if ($type->shouldRequireUsername()) {
       $form
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setName('username')
           ->setLabel(pht('Login/Username'))
           ->setValue($v_username)
           ->setDisabled($credential_is_locked)
           ->setError($e_username));
     }
        $form
        ->appendChild(
         $secret_control
           ->setName('secret')
           ->setLabel($type->getSecretLabel())
           ->setDisabled($credential_is_locked)
           ->setValue($v_secret));
 
     if ($type->shouldShowPasswordField()) {
       $form->appendChild(
         id(new AphrontFormPasswordControl())
           ->setDisableAutocomplete(true)
           ->setName('password')
           ->setLabel($type->getPasswordLabel())
           ->setDisabled($credential_is_locked)
           ->setError($e_password));
     }
 
     if ($is_new) {
       $form->appendChild(
         id(new AphrontFormCheckboxControl())
           ->addCheckbox(
             'lock',
             1,
             array(
               phutil_tag('strong', array(), pht('Lock Permanently:')),
               ' ',
               pht('Prevent the secret from being revealed or changed.'),
             ),
             $v_is_locked)
           ->setDisabled($credential_is_locked));
     }
 
     $crumbs = $this->buildApplicationCrumbs();
+    $crumbs->setBorder(true);
 
     if ($is_new) {
-      $title = pht('Create Credential');
-      $header = pht('Create New Credential');
+      $title = pht('Create New Credential');
       $crumbs->addTextCrumb(pht('Create'));
       $cancel_uri = $this->getApplicationURI();
+      $header_icon = 'fa-plus-square';
     } else {
-      $title = pht('Edit Credential');
-      $header = pht('Edit Credential %s', 'K'.$credential->getID());
+      $title = pht('Edit Credential: %s', $credential->getName());
       $crumbs->addTextCrumb(
         'K'.$credential->getID(),
         '/K'.$credential->getID());
       $crumbs->addTextCrumb(pht('Edit'));
       $cancel_uri = '/K'.$credential->getID();
+      $header_icon = 'fa-pencil';
     }
 
     if ($request->isAjax()) {
       if ($errors) {
         $errors = id(new PHUIInfoView())->setErrors($errors);
       }
 
-      $dialog = id(new AphrontDialogView())
-        ->setUser($viewer)
+      return $this->newDialog()
         ->setWidth(AphrontDialogView::WIDTH_FORM)
         ->setTitle($title)
         ->appendChild($errors)
         ->appendChild($form->buildLayoutView())
         ->addSubmitButton(pht('Create Credential'))
         ->addCancelButton($cancel_uri);
-
-      return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     $form->appendChild(
       id(new AphrontFormSubmitControl())
         ->setValue(pht('Save'))
         ->addCancelButton($cancel_uri));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($header)
+      ->setHeaderText(pht('Credential'))
       ->setFormErrors($errors)
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $box,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function getCredentialType($type_const) {
     $type = PassphraseCredentialType::getTypeByConstant($type_const);
 
     if (!$type) {
       throw new Exception(
         pht('Credential has invalid type "%s"!', $type_const));
     }
 
     return $type;
   }
 
 }
diff --git a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php
index 56fc6ac4a..481d111fd 100644
--- a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php
@@ -1,53 +1,51 @@
 <?php
 
 final class PassphraseCredentialPublicController
   extends PassphraseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $credential = id(new PassphraseCredentialQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
         ))
       ->executeOne();
     if (!$credential) {
       return new Aphront404Response();
     }
 
     $type = PassphraseCredentialType::getTypeByConstant(
       $credential->getCredentialType());
     if (!$type) {
       throw new Exception(pht('Credential has invalid type "%s"!', $type));
     }
 
     if (!$type->hasPublicKey()) {
       throw new Exception(pht('Credential has no public key!'));
     }
 
     $view_uri = '/'.$credential->getMonogram();
 
     $public_key = $type->getPublicKey($viewer, $credential);
 
     $body = id(new PHUIFormLayoutView())
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Public Key'))
           ->setReadOnly(true)
           ->setValue($public_key));
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setWidth(AphrontDialogView::WIDTH_FORM)
       ->setTitle(pht('Public Key (%s)', $credential->getMonogram()))
       ->appendChild($body)
       ->addCancelButton($view_uri, pht('Done'));
 
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php
index db3196477..1bbea88ec 100644
--- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php
@@ -1,227 +1,227 @@
 <?php
 
 final class PassphraseCredentialViewController extends PassphraseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $credential = id(new PassphraseCredentialQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$credential) {
       return new Aphront404Response();
     }
 
     $type = $credential->getImplementation();
 
     $timeline = $this->buildTransactionTimeline(
       $credential,
       new PassphraseCredentialTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $title = pht('%s %s', $credential->getMonogram(), $credential->getName());
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($credential->getMonogram());
     $crumbs->setBorder(true);
 
     $header = $this->buildHeaderView($credential);
     $curtain = $this->buildCurtain($credential, $type);
     $subheader = $this->buildSubheaderView($credential);
     $content = $this->buildPropertySectionView($credential, $type);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
       ->setMainColumn($timeline)
-      ->addPropertySection(pht('PROPERTIES'), $content);
+      ->addPropertySection(pht('Properties'), $content);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildHeaderView(PassphraseCredential $credential) {
     $viewer = $this->getRequest()->getUser();
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($credential->getName())
       ->setPolicyObject($credential)
       ->setHeaderIcon('fa-user-secret');
 
     if ($credential->getIsDestroyed()) {
       $header->setStatus('fa-ban', 'red', pht('Destroyed'));
     }
 
     return $header;
   }
 
   private function buildSubheaderView(
     PassphraseCredential $credential) {
     $viewer = $this->getViewer();
 
     $author = $viewer->renderHandle($credential->getAuthorPHID())->render();
     $date = phabricator_datetime($credential->getDateCreated(), $viewer);
     $author = phutil_tag('strong', array(), $author);
 
     $person = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($credential->getAuthorPHID()))
       ->needProfileImage(true)
       ->executeOne();
 
     if (!$person) {
       return null;
     }
 
     $image_uri = $person->getProfileImageURI();
     $image_href = '/p/'.$credential->getUsername();
 
     $content = pht('Created by %s on %s.', $author, $date);
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
   }
 
   private function buildCurtain(
     PassphraseCredential $credential,
     PassphraseCredentialType $type) {
     $viewer = $this->getViewer();
 
     $id = $credential->getID();
 
     $is_locked = $credential->getIsLocked();
     if ($is_locked) {
       $credential_lock_text = pht('Locked Permanently');
       $credential_lock_icon = 'fa-lock';
     } else {
       $credential_lock_text = pht('Lock Permanently');
       $credential_lock_icon = 'fa-unlock';
     }
 
     $allow_conduit = $credential->getAllowConduit();
     if ($allow_conduit) {
       $credential_conduit_text = pht('Prevent Conduit Access');
       $credential_conduit_icon = 'fa-ban';
     } else {
       $credential_conduit_text = pht('Allow Conduit Access');
       $credential_conduit_icon = 'fa-wrench';
     }
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $credential,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($credential);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Credential'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     if (!$credential->getIsDestroyed()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Destroy Credential'))
           ->setIcon('fa-times')
           ->setHref($this->getApplicationURI("destroy/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
 
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Show Secret'))
           ->setIcon('fa-eye')
           ->setHref($this->getApplicationURI("reveal/{$id}/"))
           ->setDisabled(!$can_edit || $is_locked)
           ->setWorkflow(true));
 
       if ($type->hasPublicKey()) {
         $curtain->addAction(
           id(new PhabricatorActionView())
             ->setName(pht('Show Public Key'))
             ->setIcon('fa-download')
             ->setHref($this->getApplicationURI("public/{$id}/"))
             ->setDisabled(!$can_edit)
             ->setWorkflow(true));
       }
 
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName($credential_conduit_text)
           ->setIcon($credential_conduit_icon)
           ->setHref($this->getApplicationURI("conduit/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
 
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName($credential_lock_text)
           ->setIcon($credential_lock_icon)
           ->setHref($this->getApplicationURI("lock/{$id}/"))
           ->setDisabled(!$can_edit || $is_locked)
           ->setWorkflow(true));
     }
 
     return $curtain;
   }
 
   private function buildPropertySectionView(
     PassphraseCredential $credential,
     PassphraseCredentialType $type) {
     $viewer = $this->getRequest()->getUser();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(
       pht('Credential Type'),
       $type->getCredentialTypeName());
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $credential);
 
     $properties->addProperty(
       pht('Editable By'),
       $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
 
     if ($type->shouldRequireUsername()) {
       $properties->addProperty(
         pht('Username'),
         $credential->getUsername());
     }
 
     $used_by_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
       $credential->getPHID(),
       PhabricatorCredentialsUsedByObjectEdgeType::EDGECONST);
 
     if ($used_by_phids) {
       $properties->addProperty(
         pht('Used By'),
         $viewer->renderHandleList($used_by_phids));
     }
 
     $description = $credential->getDescription();
     if (strlen($description)) {
       $properties->addSectionHeader(
         pht('Description'),
         PHUIPropertyListView::ICON_SUMMARY);
       $properties->addTextContent(
         new PHUIRemarkupView($viewer, $description));
     }
 
     return $properties;
   }
 
 }
diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php
index 7916279f6..f88ab7669 100644
--- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php
+++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php
@@ -1,117 +1,117 @@
 <?php
 
 final class PhabricatorPasteEditEngine
   extends PhabricatorEditEngine {
 
   const ENGINECONST = 'paste.paste';
 
   public function getEngineName() {
     return pht('Pastes');
   }
 
   public function getSummaryHeader() {
     return pht('Configure Paste Forms');
   }
 
   public function getSummaryText() {
     return pht('Configure creation and editing forms in Paste.');
   }
 
   public function getEngineApplicationClass() {
     return 'PhabricatorPasteApplication';
   }
 
   protected function newEditableObject() {
     return PhabricatorPaste::initializeNewPaste($this->getViewer());
   }
 
   protected function newObjectQuery() {
     return id(new PhabricatorPasteQuery())
       ->needRawContent(true);
   }
 
   protected function getObjectCreateTitleText($object) {
     return pht('Create New Paste');
   }
 
   protected function getObjectEditTitleText($object) {
-    return pht('Edit %s %s', $object->getMonogram(), $object->getTitle());
+    return pht('Edit Paste: %s', $object->getTitle());
   }
 
   protected function getObjectEditShortText($object) {
     return $object->getMonogram();
   }
 
   protected function getObjectCreateShortText() {
     return pht('Create Paste');
   }
 
   protected function getObjectName() {
     return pht('Paste');
   }
 
   protected function getCommentViewHeaderText($object) {
     return pht('Eat Paste');
   }
 
   protected function getCommentViewButtonText($object) {
     return pht('Nom Nom Nom Nom Nom');
   }
 
   protected function getObjectViewURI($object) {
     return '/P'.$object->getID();
   }
 
   protected function buildCustomEditFields($object) {
     $langs = array(
       '' => pht('(Detect From Filename in Title)'),
     ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
 
     return array(
       id(new PhabricatorTextEditField())
         ->setKey('title')
         ->setLabel(pht('Title'))
         ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
         ->setDescription(pht('The title of the paste.'))
         ->setConduitDescription(pht('Retitle the paste.'))
         ->setConduitTypeDescription(pht('New paste title.'))
         ->setValue($object->getTitle()),
       id(new PhabricatorSelectEditField())
         ->setKey('language')
         ->setLabel(pht('Language'))
         ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
         ->setAliases(array('lang'))
         ->setIsCopyable(true)
         ->setOptions($langs)
         ->setDescription(
           pht(
             'Language used for syntax highlighting. By default, inferred '.
             'from the title.'))
         ->setConduitDescription(
           pht('Change language used for syntax highlighting.'))
         ->setConduitTypeDescription(pht('New highlighting language.'))
         ->setValue($object->getLanguage()),
       id(new PhabricatorTextAreaEditField())
         ->setKey('text')
         ->setLabel(pht('Text'))
         ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
         ->setMonospaced(true)
         ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
         ->setDescription(pht('The main body text of the paste.'))
         ->setConduitDescription(pht('Change the paste content.'))
         ->setConduitTypeDescription(pht('New body content.'))
         ->setValue($object->getRawContent()),
       id(new PhabricatorSelectEditField())
         ->setKey('status')
         ->setLabel(pht('Status'))
         ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
         ->setIsConduitOnly(true)
         ->setOptions(PhabricatorPaste::getStatusNameMap())
         ->setDescription(pht('Active or archived status.'))
         ->setConduitDescription(pht('Active or archive the paste.'))
         ->setConduitTypeDescription(pht('New paste status constant.'))
         ->setValue($object->getStatus()),
     );
   }
 
 }
diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php
index bc0909cd4..0e8f49793 100644
--- a/src/applications/paste/storage/PhabricatorPaste.php
+++ b/src/applications/paste/storage/PhabricatorPaste.php
@@ -1,287 +1,287 @@
 <?php
 
 final class PhabricatorPaste extends PhabricatorPasteDAO
   implements
     PhabricatorSubscribableInterface,
     PhabricatorTokenReceiverInterface,
     PhabricatorFlaggableInterface,
     PhabricatorMentionableInterface,
     PhabricatorPolicyInterface,
     PhabricatorProjectInterface,
     PhabricatorDestructibleInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorSpacesInterface,
     PhabricatorConduitResultInterface {
 
   protected $title;
   protected $authorPHID;
   protected $filePHID;
   protected $language;
   protected $parentPHID;
   protected $viewPolicy;
   protected $editPolicy;
   protected $mailKey;
   protected $status;
   protected $spacePHID;
 
   const STATUS_ACTIVE = 'active';
   const STATUS_ARCHIVED = 'archived';
 
   private $content = self::ATTACHABLE;
   private $rawContent = self::ATTACHABLE;
   private $snippet = self::ATTACHABLE;
 
   public static function initializeNewPaste(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorPasteApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(PasteDefaultViewCapability::CAPABILITY);
     $edit_policy = $app->getPolicy(PasteDefaultEditCapability::CAPABILITY);
 
     return id(new PhabricatorPaste())
       ->setTitle('')
       ->setLanguage('')
       ->setStatus(self::STATUS_ACTIVE)
       ->setAuthorPHID($actor->getPHID())
       ->setViewPolicy($view_policy)
       ->setEditPolicy($edit_policy)
       ->setSpacePHID($actor->getDefaultSpacePHID())
       ->attachRawContent(null);
   }
 
   public static function getStatusNameMap() {
     return array(
       self::STATUS_ACTIVE => pht('Active'),
       self::STATUS_ARCHIVED => pht('Archived'),
     );
   }
 
   public function getURI() {
     return '/'.$this->getMonogram();
   }
 
   public function getMonogram() {
     return 'P'.$this->getID();
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_COLUMN_SCHEMA => array(
         'status' => 'text32',
         'title' => 'text255',
         'language' => 'text64',
         'mailKey' => 'bytes20',
         'parentPHID' => 'phid?',
 
         // T6203/NULLABILITY
         // Pastes should always have a view policy.
         'viewPolicy' => 'policy?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'parentPHID' => array(
           'columns' => array('parentPHID'),
         ),
         'authorPHID' => array(
           'columns' => array('authorPHID'),
         ),
         'key_dateCreated' => array(
           'columns' => array('dateCreated'),
         ),
         'key_language' => array(
           'columns' => array('language'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPastePastePHIDType::TYPECONST);
   }
 
   public function isArchived() {
     return ($this->getStatus() == self::STATUS_ARCHIVED);
   }
 
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
   public function getFullName() {
     $title = $this->getTitle();
     if (!$title) {
       $title = pht('(An Untitled Masterwork)');
     }
     return 'P'.$this->getID().' '.$title;
   }
 
   public function getContent() {
     return $this->assertAttached($this->content);
   }
 
   public function attachContent($content) {
     $this->content = $content;
     return $this;
   }
 
   public function getRawContent() {
     return $this->assertAttached($this->rawContent);
   }
 
   public function attachRawContent($raw_content) {
     $this->rawContent = $raw_content;
     return $this;
   }
 
   public function getSnippet() {
     return $this->assertAttached($this->snippet);
   }
 
   public function attachSnippet(PhabricatorPasteSnippet $snippet) {
     $this->snippet = $snippet;
     return $this;
   }
 
 /* -(  PhabricatorSubscribableInterface  )----------------------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($this->authorPHID == $phid);
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
   public function getUsersToNotifyOfTokenGiven() {
     return array(
       $this->getAuthorPHID(),
     );
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
       return $this->viewPolicy;
     } else if ($capability == PhabricatorPolicyCapability::CAN_EDIT) {
       return $this->editPolicy;
     }
     return PhabricatorPolicies::POLICY_NOONE;
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     return ($user->getPHID() == $this->getAuthorPHID());
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('The author of a paste can always view and edit it.');
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     if ($this->filePHID) {
       $file = id(new PhabricatorFileQuery())
         ->setViewer($engine->getViewer())
         ->withPHIDs(array($this->filePHID))
         ->executeOne();
       if ($file) {
         $engine->destroyObject($file);
       }
     }
 
     $this->delete();
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorPasteEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorPasteTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorSpacesInterface  )----------------------------------------- */
 
 
   public function getSpacePHID() {
     return $this->spacePHID;
   }
 
 
 /* -(  PhabricatorConduitResultInterface  )---------------------------------- */
 
 
   public function getFieldSpecificationsForConduit() {
     return array(
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('title')
         ->setType('string')
         ->setDescription(pht('The title of the paste.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('authorPHID')
         ->setType('phid')
         ->setDescription(pht('User PHID of the author.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('language')
         ->setType('string?')
         ->setDescription(pht('Language to use for syntax highlighting.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('status')
         ->setType('string')
-        ->setDescription(pht('Active or arhived status of the paste.')),
+        ->setDescription(pht('Active or archived status of the paste.')),
     );
   }
 
   public function getFieldValuesForConduit() {
     return array(
       'title' => $this->getTitle(),
       'authorPHID' => $this->getAuthorPHID(),
       'language' => nonempty($this->getLanguage(), null),
       'status' => $this->getStatus(),
     );
   }
 
   public function getConduitSearchAttachments() {
     return array(
       id(new PhabricatorPasteContentSearchEngineAttachment())
         ->setAttachmentKey('content'),
     );
   }
 
 }
diff --git a/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php b/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php
index 13884e1e8..2911056f5 100644
--- a/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php
+++ b/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php
@@ -1,38 +1,38 @@
 <?php
 
 final class UserWhoAmIConduitAPIMethod extends UserConduitAPIMethod {
 
   public function getAPIMethodName() {
     return 'user.whoami';
   }
 
   public function getMethodDescription() {
     return pht('Retrieve information about the logged-in user.');
   }
 
   protected function defineParamTypes() {
     return array();
   }
 
   protected function defineReturnType() {
     return 'nonempty dict<string, wild>';
   }
 
   public function getRequiredScope() {
-    return PhabricatorOAuthServerScope::SCOPE_WHOAMI;
+    return self::SCOPE_ALWAYS;
   }
 
   protected function execute(ConduitAPIRequest $request) {
     $person = id(new PhabricatorPeopleQuery())
       ->setViewer($request->getUser())
       ->needProfileImage(true)
       ->withPHIDs(array($request->getUser()->getPHID()))
       ->executeOne();
 
     return $this->buildUserInformationDictionary(
       $person,
       $with_email = true,
       $with_availability = false);
   }
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleApproveController.php b/src/applications/people/controller/PhabricatorPeopleApproveController.php
index a906b1565..a63682239 100644
--- a/src/applications/people/controller/PhabricatorPeopleApproveController.php
+++ b/src/applications/people/controller/PhabricatorPeopleApproveController.php
@@ -1,68 +1,65 @@
 <?php
 
 final class PhabricatorPeopleApproveController
   extends PhabricatorPeopleController {
 
   private $id;
 
   public function willProcessRequest(array $data) {
     $this->id = idx($data, 'id');
   }
 
   public function processRequest() {
 
     $request = $this->getRequest();
     $admin = $request->getUser();
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer($admin)
       ->withIDs(array($this->id))
       ->executeOne();
     if (!$user) {
       return new Aphront404Response();
     }
 
     $done_uri = $this->getApplicationURI('query/approval/');
 
     if ($request->isFormPost()) {
       id(new PhabricatorUserEditor())
         ->setActor($admin)
         ->approveUser($user, true);
 
       $title = pht(
         'Phabricator Account "%s" Approved',
         $user->getUsername());
 
       $body = sprintf(
         "%s\n\n  %s\n\n",
         pht(
           'Your Phabricator account (%s) has been approved by %s. You can '.
           'login here:',
           $user->getUsername(),
           $admin->getUsername()),
         PhabricatorEnv::getProductionURI('/'));
 
       $mail = id(new PhabricatorMetaMTAMail())
         ->addTos(array($user->getPHID()))
         ->addCCs(array($admin->getPHID()))
         ->setSubject('[Phabricator] '.$title)
         ->setForceDelivery(true)
         ->setBody($body)
         ->saveAndSend();
 
       return id(new AphrontRedirectResponse())->setURI($done_uri);
     }
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($admin)
+    return $this->newDialog()
       ->setTitle(pht('Confirm Approval'))
       ->appendChild(
         pht(
           'Allow %s to access this Phabricator install?',
           phutil_tag('strong', array(), $user->getUsername())))
       ->addCancelButton($done_uri)
       ->addSubmitButton(pht('Approve Account'));
-
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php
index 15a34d006..82a27e2f9 100644
--- a/src/applications/people/controller/PhabricatorPeopleCreateController.php
+++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php
@@ -1,108 +1,114 @@
 <?php
 
 final class PhabricatorPeopleCreateController
   extends PhabricatorPeopleController {
 
   public function handleRequest(AphrontRequest $request) {
     $admin = $request->getUser();
 
     id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
       $admin,
       $request,
       $this->getApplicationURI());
 
     $v_type = 'standard';
     if ($request->isFormPost()) {
       $v_type = $request->getStr('type');
 
       if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') {
         return id(new AphrontRedirectResponse())->setURI(
           $this->getApplicationURI('new/'.$v_type.'/'));
       }
     }
 
     $title = pht('Create New User');
 
     $standard_caption = pht(
       'Create a standard user account. These users can log in to Phabricator, '.
       'use the web interface and API, and receive email.');
 
     $standard_admin = pht(
       'Administrators are limited in their ability to access or edit these '.
       'accounts after account creation.');
 
     $bot_caption = pht(
       'Create a bot/script user account, to automate interactions with other '.
       'systems. These users can not use the web interface, but can use the '.
       'API.');
 
     $bot_admin = pht(
       'Administrators have greater access to edit these accounts.');
 
     $types = array();
 
     $can_create = $this->hasApplicationCapability(
       PeopleCreateUsersCapability::CAPABILITY);
     if ($can_create) {
       $types[] = array(
         'type' => 'standard',
         'name' => pht('Create Standard User'),
         'help' => pht('Create a standard user account.'),
       );
     }
 
     $types[] = array(
       'type' => 'bot',
       'name' => pht('Create Bot User'),
       'help' => pht('Create a new user for use with automated scripts.'),
     );
 
     $types[] = array(
       'type' => 'list',
       'name' => pht('Create Mailing List User'),
       'help' => pht(
         'Create a mailing list user to represent an existing, external '.
         'mailing list like a Google Group or a Mailman list.'),
     );
 
     $buttons = id(new AphrontFormRadioButtonControl())
       ->setLabel(pht('Account Type'))
       ->setName('type')
       ->setValue($v_type);
 
     foreach ($types as $type) {
       $buttons->addButton($type['type'], $type['name'], $type['help']);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($admin)
       ->appendRemarkupInstructions(
         pht(
           'Choose the type of user account to create. For a detailed '.
           'explanation of user account types, see [[ %s | User Guide: '.
           'Account Roles ]].',
           PhabricatorEnv::getDoclink('User Guide: Account Roles')))
       ->appendChild($buttons)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($this->getApplicationURI())
           ->setValue(pht('Continue')));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-user');
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('User'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
index 6a7049215..e611606a7 100644
--- a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
+++ b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
@@ -1,219 +1,231 @@
 <?php
 
 final class PhabricatorPeopleInviteSendController
   extends PhabricatorPeopleInviteController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $this->requireApplicationCapability(
       PeopleCreateUsersCapability::CAPABILITY);
 
     $is_confirm = false;
     $errors = array();
     $confirm_errors = array();
     $e_emails = true;
 
     $message = $request->getStr('message');
     $emails = $request->getStr('emails');
     $severity = PHUIInfoView::SEVERITY_ERROR;
     if ($request->isFormPost()) {
       // NOTE: We aren't using spaces as a delimiter here because email
       // addresses with names often include spaces.
       $email_list = preg_split('/[,;\n]+/', $emails);
       foreach ($email_list as $key => $email) {
         if (!strlen(trim($email))) {
           unset($email_list[$key]);
         }
       }
 
       if ($email_list) {
         $e_emails = null;
       } else {
         $e_emails = pht('Required');
         $errors[] = pht(
           'To send invites, you must enter at least one email address.');
       }
 
       if (!$errors) {
         $is_confirm = true;
 
         $actions = PhabricatorAuthInviteAction::newActionListFromAddresses(
           $viewer,
           $email_list);
 
         $any_valid = false;
         $all_valid = true;
         foreach ($actions as $action) {
           if ($action->willSend()) {
             $any_valid = true;
           } else {
             $all_valid = false;
           }
         }
 
         if (!$any_valid) {
           $confirm_errors[] = pht(
             'None of the provided addresses are valid invite recipients. '.
             'Review the table below for details. Revise the address list '.
             'to continue.');
         } else if ($all_valid) {
           $confirm_errors[] = pht(
             'All of the addresses appear to be valid invite recipients. '.
             'Confirm the actions below to continue.');
           $severity = PHUIInfoView::SEVERITY_NOTICE;
         } else {
           $confirm_errors[] = pht(
             'Some of the addresses you entered do not appear to be '.
             'valid recipients. Review the table below. You can revise '.
             'the address list, or ignore these errors and continue.');
           $severity = PHUIInfoView::SEVERITY_WARNING;
         }
 
         if ($any_valid && $request->getBool('confirm')) {
 
           // TODO: The copywriting on this mail could probably be more
           // engaging and we could have a fancy HTML version.
 
           $template = array();
           $template[] = pht(
             '%s has invited you to join Phabricator.',
             $viewer->getFullName());
 
           if (strlen(trim($message))) {
             $template[] = $message;
           }
 
           $template[] = pht(
             'To register an account and get started, follow this link:');
 
           // This isn't a variable; it will be replaced later on in the
           // daemons once they generate the URI.
           $template[] = '{$INVITE_URI}';
 
           $template[] = pht(
             'If you already have an account, you can follow the link to '.
             'quickly verify this email address.');
 
           $template = implode("\n\n", $template);
 
           foreach ($actions as $action) {
             if ($action->willSend()) {
               $action->sendInvite($viewer, $template);
             }
           }
 
           // TODO: This is a bit anticlimactic. We don't really have anything
           // to show the user because the action is happening in the background
           // and the invites won't exist yet. After T5166 we can show a
           // better progress bar.
           return id(new AphrontRedirectResponse())
             ->setURI($this->getApplicationURI());
         }
       }
     }
 
     if ($is_confirm) {
       $title = pht('Confirm Invites');
     } else {
       $title = pht('Invite Users');
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($is_confirm) {
       $crumbs->addTextCrumb(pht('Confirm'));
     } else {
       $crumbs->addTextCrumb(pht('Invite Users'));
     }
+    $crumbs->setBorder(true);
 
     $confirm_box = null;
+    $info_view = null;
     if ($is_confirm) {
 
       $handles = array();
       if ($actions) {
         $handles = $this->loadViewerHandles(mpull($actions, 'getUserPHID'));
       }
 
       $invite_table = id(new PhabricatorAuthInviteActionTableView())
         ->setUser($viewer)
         ->setInviteActions($actions)
         ->setHandles($handles);
 
       $confirm_form = null;
       if ($any_valid) {
         $confirm_form = id(new AphrontFormView())
           ->setUser($viewer)
           ->addHiddenInput('message', $message)
           ->addHiddenInput('emails', $emails)
           ->addHiddenInput('confirm', true)
           ->appendRemarkupInstructions(
             pht(
               'If everything looks good, click **Send Invitations** to '.
               'deliver email invitations these users. Otherwise, edit the '.
               'email list or personal message at the bottom of the page to '.
               'revise the invitations.'))
           ->appendChild(
             id(new AphrontFormSubmitControl())
               ->setValue(pht('Send Invitations')));
       }
 
+      $info_view = id(new PHUIInfoView())
+        ->setErrors($confirm_errors)
+        ->setSeverity($severity);
+
       $confirm_box = id(new PHUIObjectBoxView())
-        ->setInfoView(
-          id(new PHUIInfoView())
-            ->setErrors($confirm_errors)
-            ->setSeverity($severity))
         ->setHeaderText(pht('Confirm Invites'))
         ->setTable($invite_table)
-        ->appendChild($confirm_form);
+        ->appendChild($confirm_form)
+        ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendRemarkupInstructions(
         pht(
           'To invite users to Phabricator, enter their email addresses below. '.
           'Separate addresses with commas or newlines.'))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Email Addresses'))
           ->setName(pht('emails'))
           ->setValue($emails)
           ->setError($e_emails)
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))
       ->appendRemarkupInstructions(
         pht(
           'You can optionally include a heartfelt personal message in '.
           'the email.'))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Message'))
           ->setName(pht('message'))
           ->setValue($message))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(
             $is_confirm
               ? pht('Update Preview')
               : pht('Continue'))
           ->addCancelButton($this->getApplicationURI('invite/')));
 
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-group');
+
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(
         $is_confirm
           ? pht('Revise Invites')
           : pht('Invite Users'))
       ->setFormErrors($errors)
-      ->setForm($form);
+      ->setForm($form)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $info_view,
         $confirm_box,
         $box,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleLdapController.php b/src/applications/people/controller/PhabricatorPeopleLdapController.php
index 2e030ad58..1a6530ab3 100644
--- a/src/applications/people/controller/PhabricatorPeopleLdapController.php
+++ b/src/applications/people/controller/PhabricatorPeopleLdapController.php
@@ -1,216 +1,214 @@
 <?php
 
 final class PhabricatorPeopleLdapController
   extends PhabricatorPeopleController {
 
   public function handleRequest(AphrontRequest $request) {
     $this->requireApplicationCapability(
       PeopleCreateUsersCapability::CAPABILITY);
     $admin = $request->getUser();
 
     $content = array();
 
     $form = id(new AphrontFormView())
       ->setAction($request->getRequestURI()
         ->alter('search', 'true')->alter('import', null))
       ->setUser($admin)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('LDAP username'))
           ->setName('username'))
       ->appendChild(
         id(new AphrontFormPasswordControl())
           ->setDisableAutocomplete(true)
           ->setLabel(pht('Password'))
           ->setName('password'))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('LDAP query'))
           ->setCaption(pht('A filter such as %s.', '(objectClass=*)'))
           ->setName('query'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Search')));
 
     $panel = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Import LDAP Users'))
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Import Ldap Users'),
       $this->getApplicationURI('/ldap/'));
 
     $nav = $this->buildSideNavView();
-    $nav->setCrumbs($crumbs);
     $nav->selectFilter('ldap');
     $nav->appendChild($content);
 
     if ($request->getStr('import')) {
       $nav->appendChild($this->processImportRequest($request));
     }
 
     $nav->appendChild($panel);
 
     if ($request->getStr('search')) {
       $nav->appendChild($this->processSearchRequest($request));
     }
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title'  => pht('Import Ldap Users'),
-      ));
+    return $this->newPage()
+      ->setTitle(pht('Import Ldap Users'))
+      ->setCrumbs($crumbs)
+      ->setNavigation($nav);
   }
 
   private function processImportRequest($request) {
     $admin = $request->getUser();
     $usernames = $request->getArr('usernames');
     $emails = $request->getArr('email');
     $names = $request->getArr('name');
 
     $notice_view = new PHUIInfoView();
     $notice_view->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
     $notice_view->setTitle(pht('Import Successful'));
     $notice_view->setErrors(array(
       pht('Successfully imported users from LDAP'),
     ));
 
     $list = new PHUIObjectItemListView();
     $list->setNoDataString(pht('No users imported?'));
 
     foreach ($usernames as $username) {
       $user = new PhabricatorUser();
       $user->setUsername($username);
       $user->setRealname($names[$username]);
 
       $email_obj = id(new PhabricatorUserEmail())
         ->setAddress($emails[$username])
         ->setIsVerified(1);
       try {
         id(new PhabricatorUserEditor())
           ->setActor($admin)
           ->createNewUser($user, $email_obj);
 
         id(new PhabricatorExternalAccount())
           ->setUserPHID($user->getPHID())
           ->setAccountType('ldap')
           ->setAccountDomain('self')
           ->setAccountID($username)
           ->save();
 
         $header = pht('Successfully added %s', $username);
         $attribute = null;
         $color = 'fa-check green';
       } catch (Exception $ex) {
         $header = pht('Failed to add %s', $username);
         $attribute = $ex->getMessage();
         $color = 'fa-times red';
       }
 
       $item = id(new PHUIObjectItemView())
         ->setHeader($header)
         ->addAttribute($attribute)
         ->setStatusIcon($color);
 
       $list->addItem($item);
     }
 
     return array(
       $notice_view,
       $list,
     );
 
   }
 
   private function processSearchRequest($request) {
     $panel = new PHUIBoxView();
     $admin = $request->getUser();
 
     $search = $request->getStr('query');
 
     $ldap_provider = PhabricatorLDAPAuthProvider::getLDAPProvider();
     if (!$ldap_provider) {
       throw new Exception(pht('No LDAP provider enabled!'));
     }
 
     $ldap_adapter = $ldap_provider->getAdapter();
     $ldap_adapter->setLoginUsername($request->getStr('username'));
     $ldap_adapter->setLoginPassword(
       new PhutilOpaqueEnvelope($request->getStr('password')));
 
     // This causes us to connect and bind.
     // TODO: Clean up this discard mode stuff.
     DarkConsoleErrorLogPluginAPI::enableDiscardMode();
       $ldap_adapter->getAccountID();
     DarkConsoleErrorLogPluginAPI::disableDiscardMode();
 
     $results = $ldap_adapter->searchLDAP('%Q', $search);
 
     foreach ($results as $key => $record) {
       $account_id = $ldap_adapter->readLDAPRecordAccountID($record);
       if (!$account_id) {
         unset($results[$key]);
         continue;
       }
 
       $info = array(
         $account_id,
         $ldap_adapter->readLDAPRecordEmail($record),
         $ldap_adapter->readLDAPRecordRealName($record),
       );
       $results[$key] = $info;
       $results[$key][] = $this->renderUserInputs($info);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($admin);
 
     $table = new AphrontTableView($results);
     $table->setHeaders(
       array(
         pht('Username'),
         pht('Email'),
         pht('Real Name'),
         pht('Import?'),
       ));
     $form->appendChild($table);
     $form->setAction($request->getRequestURI()
       ->alter('import', 'true')->alter('search', null))
       ->appendChild(
         id(new AphrontFormSubmitControl())
         ->setValue(pht('Import')));
 
     $panel->appendChild($form);
 
     return $panel;
   }
 
   private function renderUserInputs($user) {
     $username = $user[0];
     return hsprintf(
       '%s%s%s',
       phutil_tag(
         'input',
         array(
           'type' => 'checkbox',
           'name' => 'usernames[]',
           'value' => $username,
         )),
       phutil_tag(
         'input',
         array(
           'type' => 'hidden',
           'name' => "email[$username]",
           'value' => $user[1],
         )),
       phutil_tag(
         'input',
         array(
           'type' => 'hidden',
           'name' => "name[$username]",
           'value' => $user[2],
         )));
   }
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php
index 2590129c0..60f9f47b5 100644
--- a/src/applications/people/controller/PhabricatorPeopleNewController.php
+++ b/src/applications/people/controller/PhabricatorPeopleNewController.php
@@ -1,229 +1,235 @@
 <?php
 
 final class PhabricatorPeopleNewController
   extends PhabricatorPeopleController {
 
   public function handleRequest(AphrontRequest $request) {
     $type = $request->getURIData('type');
     $admin = $request->getUser();
 
     $is_bot = false;
     $is_list = false;
     switch ($type) {
       case 'standard':
         $this->requireApplicationCapability(
           PeopleCreateUsersCapability::CAPABILITY);
         break;
       case 'bot':
         $is_bot = true;
         break;
       case 'list':
         $is_list = true;
         break;
       default:
         return new Aphront404Response();
     }
 
     $user = new PhabricatorUser();
     $require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');
 
     $e_username = true;
     $e_realname = $require_real_name ? true : null;
     $e_email    = true;
     $errors = array();
 
     $welcome_checked = true;
 
     $new_email = null;
 
     if ($request->isFormPost()) {
       $welcome_checked = $request->getInt('welcome');
 
       $user->setUsername($request->getStr('username'));
 
       $new_email = $request->getStr('email');
       if (!strlen($new_email)) {
         $errors[] = pht('Email is required.');
         $e_email = pht('Required');
       } else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
         $e_email = pht('Invalid');
         $errors[] = PhabricatorUserEmail::describeAllowedAddresses();
       } else {
         $e_email = null;
       }
 
       $user->setRealName($request->getStr('realname'));
 
       if (!strlen($user->getUsername())) {
         $errors[] = pht('Username is required.');
         $e_username = pht('Required');
       } else if (!PhabricatorUser::validateUsername($user->getUsername())) {
         $errors[] = PhabricatorUser::describeValidUsername();
         $e_username = pht('Invalid');
       } else {
         $e_username = null;
       }
 
       if (!strlen($user->getRealName()) && $require_real_name) {
         $errors[] = pht('Real name is required.');
         $e_realname = pht('Required');
       } else {
         $e_realname = null;
       }
 
       if (!$errors) {
         try {
 
           $email = id(new PhabricatorUserEmail())
             ->setAddress($new_email)
             ->setIsVerified(0);
 
           // Automatically approve the user, since an admin is creating them.
           $user->setIsApproved(1);
 
           // If the user is a bot or list, approve their email too.
           if ($is_bot || $is_list) {
             $email->setIsVerified(1);
           }
 
           id(new PhabricatorUserEditor())
             ->setActor($admin)
             ->createNewUser($user, $email);
 
           if ($is_bot) {
             id(new PhabricatorUserEditor())
               ->setActor($admin)
               ->makeSystemAgentUser($user, true);
           }
 
           if ($is_list) {
             id(new PhabricatorUserEditor())
               ->setActor($admin)
               ->makeMailingListUser($user, true);
           }
 
           if ($welcome_checked && !$is_bot && !$is_list) {
             $user->sendWelcomeEmail($admin);
           }
 
           $response = id(new AphrontRedirectResponse())
             ->setURI('/p/'.$user->getUsername().'/');
           return $response;
         } catch (AphrontDuplicateKeyQueryException $ex) {
           $errors[] = pht('Username and email must be unique.');
 
           $same_username = id(new PhabricatorUser())
             ->loadOneWhere('username = %s', $user->getUsername());
           $same_email = id(new PhabricatorUserEmail())
             ->loadOneWhere('address = %s', $new_email);
 
           if ($same_username) {
             $e_username = pht('Duplicate');
           }
 
           if ($same_email) {
             $e_email = pht('Duplicate');
           }
         }
       }
     }
 
     $form = id(new AphrontFormView())
       ->setUser($admin);
 
     if ($is_bot) {
       $form->appendRemarkupInstructions(
         pht('You are creating a new **bot** user account.'));
     } else if ($is_list) {
       $form->appendRemarkupInstructions(
         pht('You are creating a new **mailing list** user account.'));
     } else {
       $form->appendRemarkupInstructions(
         pht('You are creating a new **standard** user account.'));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Username'))
           ->setName('username')
           ->setValue($user->getUsername())
           ->setError($e_username))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Real Name'))
           ->setName('realname')
           ->setValue($user->getRealName())
           ->setError($e_realname))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Email'))
           ->setName('email')
           ->setValue($new_email)
           ->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
           ->setError($e_email));
 
     if (!$is_bot && !$is_list) {
       $form->appendChild(
         id(new AphrontFormCheckboxControl())
           ->addCheckbox(
             'welcome',
             1,
             pht('Send "Welcome to Phabricator" email with login instructions.'),
             $welcome_checked));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($this->getApplicationURI())
           ->setValue(pht('Create User')));
 
     if ($is_bot) {
       $form
         ->appendChild(id(new AphrontFormDividerControl()))
         ->appendRemarkupInstructions(
           pht(
             '**Why do bot accounts need an email address?**'.
             "\n\n".
             'Although bots do not normally receive email from Phabricator, '.
             'they can interact with other systems which require an email '.
             'address. Examples include:'.
             "\n\n".
             "  - If the account takes actions which //send// email, we need ".
             "    an address to use in the //From// header.\n".
             "  - If the account creates commits, Git and Mercurial require ".
             "    an email address for authorship.\n".
             "  - If you send email //to// Phabricator on behalf of the ".
             "    account, the address can identify the sender.\n".
             "  - Some internal authentication functions depend on accounts ".
             "    having an email address.\n".
             "\n\n".
             "The address will automatically be verified, so you do not need ".
             "to be able to receive mail at this address, and can enter some ".
             "invalid or nonexistent (but correctly formatted) address like ".
             "`bot@yourcompany.com` if you prefer."));
     }
 
 
     $title = pht('Create New User');
 
-    $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('User'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-user');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
index cb258d17a..9f132a18d 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
@@ -1,98 +1,107 @@
 <?php
 
 final class PhabricatorPeopleProfileEditController
   extends PhabricatorPeopleProfileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needProfileImage(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$user) {
       return new Aphront404Response();
     }
 
     $this->setUser($user);
 
     $done_uri = $this->getApplicationURI("manage/{$id}/");
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $user,
       PhabricatorCustomField::ROLE_EDIT);
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($user);
 
     $validation_exception = null;
     if ($request->isFormPost()) {
       $xactions = $field_list->buildFieldTransactionsFromRequest(
         new PhabricatorUserTransaction(),
         $request);
 
       $editor = id(new PhabricatorUserProfileEditor())
         ->setActor($viewer)
         ->setContentSource(
           PhabricatorContentSource::newFromRequest($request))
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($user, $xactions);
         return id(new AphrontRedirectResponse())->setURI($done_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
       }
     }
 
     $title = pht('Edit Profile');
 
     $form = id(new AphrontFormView())
       ->setUser($viewer);
 
     $field_list->appendFieldsToForm($form);
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($done_uri)
           ->setValue(pht('Save Profile')));
 
     $allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
     $note = null;
     if ($allow_public) {
       $note = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
         ->appendChild(pht(
           'Information on user profiles on this install is publicly '.
           'visible.'));
     }
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Edit Profile'))
+      ->setHeaderText(pht('Profile'))
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    if ($note) {
-      $form_box->setInfoView($note);
-    }
-
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Profile'));
+    $crumbs->setBorder(true);
 
     $nav = $this->getProfileMenu();
     $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE);
 
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Edit Profile: %s', $user->getFullName()))
+      ->setHeaderIcon('fa-pencil');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $note,
+        $form_box,
+      ));
+
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setNavigation($nav)
-      ->appendChild($form_box);
+      ->appendChild($view);
   }
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
index 51cf79ecf..08b438eaa 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
@@ -1,195 +1,195 @@
 <?php
 
 final class PhabricatorPeopleProfileManageController
   extends PhabricatorPeopleProfileController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needProfile(true)
       ->needProfileImage(true)
       ->needAvailability(true)
       ->executeOne();
     if (!$user) {
       return new Aphront404Response();
     }
 
     $this->setUser($user);
 
     $profile = $user->loadUserProfile();
     $picture = $user->getProfileImageURI();
 
     $profile_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon());
     $profile_icon = id(new PHUIIconView())
       ->setIcon($profile_icon);
     $profile_title = $profile->getDisplayTitle();
 
     $header = id(new PHUIHeaderView())
       ->setHeader($user->getFullName())
       ->setSubheader(array($profile_icon, $profile_title))
       ->setImage($picture)
       ->setProfileHeader(true);
 
     $curtain = $this->buildCurtain($user);
     $properties = $this->buildPropertyView($user);
     $name = $user->getUsername();
 
     $nav = $this->getProfileMenu();
     $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE);
 
     $timeline = $this->buildTransactionTimeline(
       $user,
       new PhabricatorPeopleTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Manage'));
     $crumbs->setBorder(true);
 
     $manage = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
-      ->addPropertySection(pht('DETAILS'), $properties)
+      ->addPropertySection(pht('Details'), $properties)
       ->setMainColumn(
         array(
           $timeline,
         ));
 
     return $this->newPage()
       ->setTitle(
         array(
           pht('Manage User'),
           $user->getUsername(),
         ))
       ->setNavigation($nav)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $manage,
         ));
   }
 
   private function buildPropertyView(PhabricatorUser $user) {
 
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($user);
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $user,
       PhabricatorCustomField::ROLE_VIEW);
     $field_list->appendFieldsToPropertyList($user, $viewer, $view);
 
     return $view;
   }
 
   private function buildCurtain(PhabricatorUser $user) {
     $viewer = $this->getViewer();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $user,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($user);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Profile'))
         ->setHref($this->getApplicationURI('editprofile/'.$user->getID().'/'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-picture-o')
         ->setName(pht('Edit Profile Picture'))
         ->setHref($this->getApplicationURI('picture/'.$user->getID().'/'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-wrench')
         ->setName(pht('Edit Settings'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit)
         ->setHref('/settings/'.$user->getID().'/'));
 
     if ($user->getIsAdmin()) {
       $empower_icon = 'fa-arrow-circle-o-down';
       $empower_name = pht('Remove Administrator');
     } else {
       $empower_icon = 'fa-arrow-circle-o-up';
       $empower_name = pht('Make Administrator');
     }
 
     $is_admin = $viewer->getIsAdmin();
     $is_self = ($user->getPHID() === $viewer->getPHID());
     $can_admin = ($is_admin && !$is_self);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon($empower_icon)
         ->setName($empower_name)
         ->setDisabled(!$can_admin)
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI('empower/'.$user->getID().'/')));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-tag')
         ->setName(pht('Change Username'))
         ->setDisabled(!$is_admin)
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI('rename/'.$user->getID().'/')));
 
     if ($user->getIsDisabled()) {
       $disable_icon = 'fa-check-circle-o';
       $disable_name = pht('Enable User');
     } else {
       $disable_icon = 'fa-ban';
       $disable_name = pht('Disable User');
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon($disable_icon)
         ->setName($disable_name)
         ->setDisabled(!$can_admin)
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI('disable/'.$user->getID().'/')));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-times')
         ->setName(pht('Delete User'))
         ->setDisabled(!$can_admin)
         ->setWorkflow(true)
         ->setHref($this->getApplicationURI('delete/'.$user->getID().'/')));
 
     $can_welcome = ($is_admin && $user->canEstablishWebSessions());
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-envelope')
         ->setName(pht('Send Welcome Email'))
         ->setWorkflow(true)
         ->setDisabled(!$can_welcome)
         ->setHref($this->getApplicationURI('welcome/'.$user->getID().'/')));
 
     return $curtain;
   }
 
 
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
index cf78dd9c0..29b229015 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
@@ -1,268 +1,278 @@
 <?php
 
 final class PhabricatorPeopleProfilePictureController
   extends PhabricatorPeopleProfileController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needProfileImage(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$user) {
       return new Aphront404Response();
     }
 
     $this->setUser($user);
     $name = $user->getUserName();
 
     $done_uri = '/p/'.$name.'/';
 
     $supported_formats = PhabricatorFile::getTransformableImageFormats();
     $e_file = true;
     $errors = array();
 
     if ($request->isFormPost()) {
       $phid = $request->getStr('phid');
       $is_default = false;
       if ($phid == PhabricatorPHIDConstants::PHID_VOID) {
         $phid = null;
         $is_default = true;
       } else if ($phid) {
         $file = id(new PhabricatorFileQuery())
           ->setViewer($viewer)
           ->withPHIDs(array($phid))
           ->executeOne();
       } else {
         if ($request->getFileExists('picture')) {
           $file = PhabricatorFile::newFromPHPUpload(
             $_FILES['picture'],
             array(
               'authorPHID' => $viewer->getPHID(),
               'canCDN' => true,
             ));
         } else {
           $e_file = pht('Required');
           $errors[] = pht(
             'You must choose a file when uploading a new profile picture.');
         }
       }
 
       if (!$errors && !$is_default) {
         if (!$file->isTransformableImage()) {
           $e_file = pht('Not Supported');
           $errors[] = pht(
             'This server only supports these image formats: %s.',
             implode(', ', $supported_formats));
         } else {
           $xform = PhabricatorFileTransform::getTransformByKey(
             PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
           $xformed = $xform->executeTransform($file);
         }
       }
 
       if (!$errors) {
         if ($is_default) {
           $user->setProfileImagePHID(null);
         } else {
           $user->setProfileImagePHID($xformed->getPHID());
           $xformed->attachToObject($user->getPHID());
         }
         $user->save();
         return id(new AphrontRedirectResponse())->setURI($done_uri);
       }
     }
 
     $title = pht('Edit Profile Picture');
 
     $form = id(new PHUIFormLayoutView())
       ->setUser($viewer);
 
     $default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png');
 
     $images = array();
 
     $current = $user->getProfileImagePHID();
     $has_current = false;
     if ($current) {
       $files = id(new PhabricatorFileQuery())
         ->setViewer($viewer)
         ->withPHIDs(array($current))
         ->execute();
       if ($files) {
         $file = head($files);
         if ($file->isTransformableImage()) {
           $has_current = true;
           $images[$current] = array(
             'uri' => $file->getBestURI(),
             'tip' => pht('Current Picture'),
           );
         }
       }
     }
 
     $builtins = array(
       'user1.png',
       'user2.png',
       'user3.png',
       'user4.png',
       'user5.png',
       'user6.png',
       'user7.png',
       'user8.png',
       'user9.png',
       );
     foreach ($builtins as $builtin) {
       $file = PhabricatorFile::loadBuiltin($viewer, $builtin);
       $images[$file->getPHID()] = array(
         'uri' => $file->getBestURI(),
         'tip' => pht('Builtin Image'),
       );
     }
 
     // Try to add external account images for any associated external accounts.
     $accounts = id(new PhabricatorExternalAccountQuery())
       ->setViewer($viewer)
       ->withUserPHIDs(array($user->getPHID()))
       ->needImages(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->execute();
 
     foreach ($accounts as $account) {
       $file = $account->getProfileImageFile();
       if ($account->getProfileImagePHID() != $file->getPHID()) {
         // This is a default image, just skip it.
         continue;
       }
 
       $provider = PhabricatorAuthProvider::getEnabledProviderByKey(
         $account->getProviderKey());
       if ($provider) {
         $tip = pht('Picture From %s', $provider->getProviderName());
       } else {
         $tip = pht('Picture From External Account');
       }
 
       if ($file->isTransformableImage()) {
         $images[$file->getPHID()] = array(
           'uri' => $file->getBestURI(),
           'tip' => $tip,
         );
       }
     }
 
     $images[PhabricatorPHIDConstants::PHID_VOID] = array(
       'uri' => $default_image->getBestURI(),
       'tip' => pht('Default Picture'),
     );
 
     require_celerity_resource('people-profile-css');
     Javelin::initBehavior('phabricator-tooltips', array());
 
     $buttons = array();
     foreach ($images as $phid => $spec) {
       $button = javelin_tag(
         'button',
         array(
           'class' => 'grey profile-image-button',
           'sigil' => 'has-tooltip',
           'meta' => array(
             'tip' => $spec['tip'],
             'size' => 300,
           ),
         ),
         phutil_tag(
           'img',
           array(
             'height' => 50,
             'width' => 50,
             'src' => $spec['uri'],
           )));
 
       $button = array(
         phutil_tag(
           'input',
           array(
             'type'  => 'hidden',
             'name'  => 'phid',
             'value' => $phid,
           )),
         $button,
       );
 
       $button = phabricator_form(
         $viewer,
         array(
           'class' => 'profile-image-form',
           'method' => 'POST',
         ),
         $button);
 
       $buttons[] = $button;
     }
 
     if ($has_current) {
       $form->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Current Picture'))
           ->setValue(array_shift($buttons)));
     }
 
     $form->appendChild(
       id(new AphrontFormMarkupControl())
         ->setLabel(pht('Use Picture'))
         ->setValue($buttons));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $upload_form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setEncType('multipart/form-data')
       ->appendChild(
         id(new AphrontFormFileControl())
           ->setName('picture')
           ->setLabel(pht('Upload Picture'))
           ->setError($e_file)
           ->setCaption(
             pht('Supported formats: %s', implode(', ', $supported_formats))))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($done_uri)
           ->setValue(pht('Upload Picture')));
 
     $upload_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Upload New Picture'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($upload_form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Profile Picture'));
+    $crumbs->setBorder(true);
 
     $nav = $this->getProfileMenu();
     $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE);
 
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Edit Profile Picture'))
+      ->setHeaderIcon('fa-camera');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $form_box,
+        $upload_box,
+      ));
+
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->setNavigation($nav)
-      ->appendChild(
-        array(
-          $form_box,
-          $upload_box,
-        ));
+      ->appendChild($view);
   }
 }
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
index cb9c558d5..f77933df9 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
@@ -1,279 +1,301 @@
 <?php
 
 final class PhabricatorPeopleProfileViewController
   extends PhabricatorPeopleProfileController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $username = $request->getURIData('username');
 
     $user = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withUsernames(array($username))
       ->needBadges(true)
       ->needProfileImage(true)
       ->needAvailability(true)
       ->executeOne();
     if (!$user) {
       return new Aphront404Response();
     }
 
     $this->setUser($user);
 
     $profile = $user->loadUserProfile();
     $picture = $user->getProfileImageURI();
 
     $profile_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon());
     $profile_icon = id(new PHUIIconView())
       ->setIcon($profile_icon);
     $profile_title = $profile->getDisplayTitle();
 
     $header = id(new PHUIHeaderView())
       ->setHeader($user->getFullName())
       ->setSubheader(array($profile_icon, $profile_title))
       ->setImage($picture)
       ->setProfileHeader(true);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $user,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     if ($can_edit) {
       $id = $user->getID();
       $header->setImageEditURL($this->getApplicationURI("picture/{$id}/"));
     }
 
     $properties = $this->buildPropertyView($user);
     $name = $user->getUsername();
 
     $feed = $this->buildPeopleFeed($user, $viewer);
     $feed = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Recent Activity'))
       ->addClass('project-view-feed')
       ->appendChild($feed);
 
     $projects = $this->buildProjectsView($user);
     $badges = $this->buildBadgesView($user);
     require_celerity_resource('project-view-css');
 
     $home = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->addClass('project-view-home')
       ->setMainColumn(
         array(
           $properties,
           $feed,
         ))
       ->setSideColumn(
         array(
           $projects,
           $badges,
         ));
 
     $nav = $this->getProfileMenu();
     $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_PROFILE);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
 
     return $this->newPage()
       ->setTitle($user->getUsername())
       ->setNavigation($nav)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $home,
         ));
   }
 
   private function buildPropertyView(
     PhabricatorUser $user) {
 
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($user);
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $user,
       PhabricatorCustomField::ROLE_VIEW);
     $field_list->appendFieldsToPropertyList($user, $viewer, $view);
 
     if (!$view->hasAnyProperties()) {
       return null;
     }
 
     $view = id(new PHUIObjectBoxView())
       ->appendChild($view)
       ->addClass('project-view-properties');
 
     return $view;
   }
 
   private function buildProjectsView(
     PhabricatorUser $user) {
 
     $viewer = $this->getViewer();
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->withMemberPHIDs(array($user->getPHID()))
       ->needImages(true)
       ->withStatuses(
         array(
           PhabricatorProjectStatus::STATUS_ACTIVE,
         ))
       ->execute();
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Projects'));
 
     if (!empty($projects)) {
       $limit = 5;
       $render_phids = array_slice($projects, 0, $limit);
       $list = id(new PhabricatorProjectListView())
         ->setUser($viewer)
         ->setProjects($render_phids);
 
       if (count($projects) > $limit) {
         $header_text = pht(
           'Projects (%s)',
           phutil_count($projects));
 
         $header = id(new PHUIHeaderView())
           ->setHeader($header_text)
           ->addActionLink(
             id(new PHUIButtonView())
               ->setTag('a')
               ->setIcon('fa-list-ul')
               ->setText(pht('View All'))
               ->setHref('/project/?member='.$user->getPHID()));
 
       }
 
     } else {
       $error = id(new PHUIBoxView())
         ->addClass('mlb')
         ->appendChild(pht('User does not belong to any projects.'));
       $list = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
         ->appendChild($error);
     }
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild($list)
       ->setBackground(PHUIObjectBoxView::GREY);
 
     return $box;
   }
 
   private function buildBadgesView(PhabricatorUser $user) {
 
     $viewer = $this->getViewer();
     $class = 'PhabricatorBadgesApplication';
 
     if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
       return null;
     }
 
-    $badge_phids = $user->getBadgePHIDs();
-    if ($badge_phids) {
-      $badges = id(new PhabricatorBadgesQuery())
+    $awards = array();
+    $badges = array();
+    if ($user->getBadgePHIDs()) {
+      $awards = id(new PhabricatorBadgesAwardQuery())
         ->setViewer($viewer)
-        ->withPHIDs($badge_phids)
-        ->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE))
+        ->withRecipientPHIDs(array($user->getPHID()))
         ->execute();
-    } else {
+      $awards = mpull($awards, null, 'getBadgePHID');
+
       $badges = array();
+      foreach ($awards as $award) {
+        $badge = $award->getBadge();
+        if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) {
+          $badges[$award->getBadgePHID()] = $badge;
+        }
+      }
     }
 
     if (count($badges)) {
       $flex = new PHUIBadgeBoxView();
+
       foreach ($badges as $badge) {
-        $item = id(new PHUIBadgeView())
-          ->setIcon($badge->getIcon())
-          ->setHeader($badge->getName())
-          ->setSubhead($badge->getFlavor())
-          ->setQuality($badge->getQuality());
-        $flex->addItem($item);
+        if ($badge) {
+          $awarder_info = array();
+
+          $award = idx($awards, $badge->getPHID(), null);
+          $awarder_phid = $award->getAwarderPHID();
+          $awarder_handle = $viewer->renderHandle($awarder_phid);
+
+          $awarder_info = pht(
+            'Awarded by %s',
+            $awarder_handle->render());
+
+          $item = id(new PHUIBadgeView())
+            ->setIcon($badge->getIcon())
+            ->setHeader($badge->getName())
+            ->setSubhead($badge->getFlavor())
+            ->setQuality($badge->getQuality())
+            ->addByLine($awarder_info);
+
+          $flex->addItem($item);
+        }
       }
     } else {
       $error = id(new PHUIBoxView())
         ->addClass('mlb')
         ->appendChild(pht('User does not have any badges.'));
       $flex = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
         ->appendChild($error);
     }
 
     // Best option?
     $badges = id(new PhabricatorBadgesQuery())
       ->setViewer($viewer)
       ->withStatuses(array(
         PhabricatorBadgesBadge::STATUS_ACTIVE,
       ))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->execute();
 
     $button = id(new PHUIButtonView())
       ->setTag('a')
       ->setIcon('fa-plus')
       ->setText(pht('Award'))
       ->setWorkflow(true)
       ->setHref('/badges/award/'.$user->getID().'/');
 
     $can_award = false;
     if (count($badges)) {
       $can_award = true;
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Badges'));
 
     if (count($badges)) {
       $header->addActionLink($button);
     }
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addClass('project-view-badges')
       ->appendChild($flex)
       ->setBackground(PHUIObjectBoxView::GREY);
 
     return $box;
   }
 
   private function buildPeopleFeed(
     PhabricatorUser $user,
     $viewer) {
 
     $query = new PhabricatorFeedQuery();
     $query->setFilterPHIDs(
       array(
         $user->getPHID(),
       ));
     $query->setLimit(100);
     $query->setViewer($viewer);
     $stories = $query->execute();
 
     $builder = new PhabricatorFeedBuilder($stories);
     $builder->setUser($viewer);
     $builder->setShowHovercards(true);
     $builder->setNoDataString(pht('To begin on such a grand journey, '.
       'requires but just a single step.'));
     $view = $builder->buildView();
 
     return $view->render();
 
   }
 
 }
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
index 0bdb9fab2..77feeb313 100644
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -1,480 +1,484 @@
 <?php
 
 final class PhabricatorPeopleQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $usernames;
   private $realnames;
   private $emails;
   private $phids;
   private $ids;
   private $dateCreatedAfter;
   private $dateCreatedBefore;
   private $isAdmin;
   private $isSystemAgent;
   private $isMailingList;
   private $isDisabled;
   private $isApproved;
   private $nameLike;
   private $nameTokens;
 
   private $needPrimaryEmail;
   private $needProfile;
   private $needProfileImage;
   private $needAvailability;
   private $needBadges;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
   public function withEmails(array $emails) {
     $this->emails = $emails;
     return $this;
   }
 
   public function withRealnames(array $realnames) {
     $this->realnames = $realnames;
     return $this;
   }
 
   public function withUsernames(array $usernames) {
     $this->usernames = $usernames;
     return $this;
   }
 
   public function withDateCreatedBefore($date_created_before) {
     $this->dateCreatedBefore = $date_created_before;
     return $this;
   }
 
   public function withDateCreatedAfter($date_created_after) {
     $this->dateCreatedAfter = $date_created_after;
     return $this;
   }
 
   public function withIsAdmin($admin) {
     $this->isAdmin = $admin;
     return $this;
   }
 
   public function withIsSystemAgent($system_agent) {
     $this->isSystemAgent = $system_agent;
     return $this;
   }
 
   public function withIsMailingList($mailing_list) {
     $this->isMailingList = $mailing_list;
     return $this;
   }
 
   public function withIsDisabled($disabled) {
     $this->isDisabled = $disabled;
     return $this;
   }
 
   public function withIsApproved($approved) {
     $this->isApproved = $approved;
     return $this;
   }
 
   public function withNameLike($like) {
     $this->nameLike = $like;
     return $this;
   }
 
   public function withNameTokens(array $tokens) {
     $this->nameTokens = array_values($tokens);
     return $this;
   }
 
   public function needPrimaryEmail($need) {
     $this->needPrimaryEmail = $need;
     return $this;
   }
 
   public function needProfile($need) {
     $this->needProfile = $need;
     return $this;
   }
 
   public function needProfileImage($need) {
     $this->needProfileImage = $need;
     return $this;
   }
 
   public function needAvailability($need) {
     $this->needAvailability = $need;
     return $this;
   }
 
   public function needBadges($need) {
     $this->needBadges = $need;
     return $this;
   }
 
   public function newResultObject() {
     return new PhabricatorUser();
   }
 
   protected function loadPage() {
     $table = new PhabricatorUser();
     $data = $this->loadStandardPageRows($table);
 
     if ($this->needPrimaryEmail) {
       $table->putInSet(new LiskDAOSet());
     }
 
     return $table->loadAllFromArray($data);
   }
 
   protected function didFilterPage(array $users) {
     if ($this->needProfile) {
       $user_list = mpull($users, null, 'getPHID');
       $profiles = new PhabricatorUserProfile();
       $profiles = $profiles->loadAllWhere(
         'userPHID IN (%Ls)',
         array_keys($user_list));
 
       $profiles = mpull($profiles, null, 'getUserPHID');
       foreach ($user_list as $user_phid => $user) {
         $profile = idx($profiles, $user_phid);
 
         if (!$profile) {
           $profile = PhabricatorUserProfile::initializeNewProfile($user);
         }
 
         $user->attachUserProfile($profile);
       }
     }
 
     if ($this->needBadges) {
       $awards = id(new PhabricatorBadgesAwardQuery())
         ->setViewer($this->getViewer())
         ->withRecipientPHIDs(mpull($users, 'getPHID'))
         ->execute();
 
       $awards = mgroup($awards, 'getRecipientPHID');
 
       foreach ($users as $user) {
         $user_awards = idx($awards, $user->getPHID(), array());
         $badge_phids = mpull($user_awards, 'getBadgePHID');
         $user->attachBadgePHIDs($badge_phids);
       }
     }
 
     if ($this->needProfileImage) {
       $rebuild = array();
       foreach ($users as $user) {
         $image_uri = $user->getProfileImageCache();
         if ($image_uri) {
           // This user has a valid cache, so we don't need to fetch any
           // data or rebuild anything.
 
           $user->attachProfileImageURI($image_uri);
           continue;
         }
 
         // This user's cache is invalid or missing, so we're going to rebuild
         // it.
         $rebuild[] = $user;
       }
 
       if ($rebuild) {
         $file_phids = mpull($rebuild, 'getProfileImagePHID');
         $file_phids = array_filter($file_phids);
 
         if ($file_phids) {
           // NOTE: We're using the omnipotent user here because older profile
           // images do not have the 'profile' flag, so they may not be visible
           // to the executing viewer. At some point, we could migrate to add
           // this flag and then use the real viewer, or just use the real
           // viewer after enough time has passed to limit the impact of old
           // data. The consequence of missing here is that we cache a default
           // image when a real image exists.
           $files = id(new PhabricatorFileQuery())
             ->setParentQuery($this)
             ->setViewer(PhabricatorUser::getOmnipotentUser())
             ->withPHIDs($file_phids)
             ->execute();
           $files = mpull($files, null, 'getPHID');
         } else {
           $files = array();
         }
 
         foreach ($rebuild as $user) {
           $image_phid = $user->getProfileImagePHID();
           if (isset($files[$image_phid])) {
             $image_uri = $files[$image_phid]->getBestURI();
           } else {
             $image_uri = PhabricatorUser::getDefaultProfileImageURI();
           }
 
           $user->writeProfileImageCache($image_uri);
           $user->attachProfileImageURI($image_uri);
         }
       }
     }
 
     if ($this->needAvailability) {
       $rebuild = array();
       foreach ($users as $user) {
         $cache = $user->getAvailabilityCache();
         if ($cache !== null) {
           $user->attachAvailability($cache);
         } else {
           $rebuild[] = $user;
         }
       }
 
       if ($rebuild) {
         $this->rebuildAvailabilityCache($rebuild);
       }
     }
 
     return $users;
   }
 
   protected function shouldGroupQueryResultRows() {
     if ($this->nameTokens) {
       return true;
     }
 
     return parent::shouldGroupQueryResultRows();
   }
 
   protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
     $joins = parent::buildJoinClauseParts($conn);
 
     if ($this->emails) {
       $email_table = new PhabricatorUserEmail();
       $joins[] = qsprintf(
         $conn,
         'JOIN %T email ON email.userPHID = user.PHID',
         $email_table->getTableName());
     }
 
     if ($this->nameTokens) {
       foreach ($this->nameTokens as $key => $token) {
         $token_table = 'token_'.$key;
         $joins[] = qsprintf(
           $conn,
           'JOIN %T %T ON %T.userID = user.id AND %T.token LIKE %>',
           PhabricatorUser::NAMETOKEN_TABLE,
           $token_table,
           $token_table,
           $token_table,
           $token);
       }
     }
 
     return  $joins;
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->usernames !== null) {
       $where[] = qsprintf(
         $conn,
         'user.userName IN (%Ls)',
         $this->usernames);
     }
 
     if ($this->emails !== null) {
       $where[] = qsprintf(
         $conn,
         'email.address IN (%Ls)',
         $this->emails);
     }
 
     if ($this->realnames !== null) {
       $where[] = qsprintf(
         $conn,
         'user.realName IN (%Ls)',
         $this->realnames);
     }
 
     if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'user.phid IN (%Ls)',
         $this->phids);
     }
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'user.id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->dateCreatedAfter) {
       $where[] = qsprintf(
         $conn,
         'user.dateCreated >= %d',
         $this->dateCreatedAfter);
     }
 
     if ($this->dateCreatedBefore) {
       $where[] = qsprintf(
         $conn,
         'user.dateCreated <= %d',
         $this->dateCreatedBefore);
     }
 
     if ($this->isAdmin !== null) {
       $where[] = qsprintf(
         $conn,
         'user.isAdmin = %d',
         (int)$this->isAdmin);
     }
 
     if ($this->isDisabled !== null) {
       $where[] = qsprintf(
         $conn,
         'user.isDisabled = %d',
         (int)$this->isDisabled);
     }
 
     if ($this->isApproved !== null) {
       $where[] = qsprintf(
         $conn,
         'user.isApproved = %d',
         (int)$this->isApproved);
     }
 
     if ($this->isSystemAgent !== null) {
       $where[] = qsprintf(
         $conn,
         'user.isSystemAgent = %d',
         (int)$this->isSystemAgent);
     }
 
     if ($this->isMailingList !== null) {
       $where[] = qsprintf(
         $conn,
         'user.isMailingList = %d',
         (int)$this->isMailingList);
     }
 
     if (strlen($this->nameLike)) {
       $where[] = qsprintf(
         $conn,
         'user.username LIKE %~ OR user.realname LIKE %~',
         $this->nameLike,
         $this->nameLike);
     }
 
     return $where;
   }
 
   protected function getPrimaryTableAlias() {
     return 'user';
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorPeopleApplication';
   }
 
   public function getOrderableColumns() {
     return parent::getOrderableColumns() + array(
       'username' => array(
         'table' => 'user',
         'column' => 'username',
         'type' => 'string',
         'reverse' => true,
         'unique' => true,
       ),
     );
   }
 
   protected function getPagingValueMap($cursor, array $keys) {
     $user = $this->loadCursorObject($cursor);
     return array(
       'id' => $user->getID(),
       'username' => $user->getUsername(),
     );
   }
 
   private function rebuildAvailabilityCache(array $rebuild) {
     $rebuild = mpull($rebuild, null, 'getPHID');
 
     // Limit the window we look at because far-future events are largely
     // irrelevant and this makes the cache cheaper to build and allows it to
     // self-heal over time.
     $min_range = PhabricatorTime::getNow();
     $max_range = $min_range + phutil_units('72 hours in seconds');
 
+    // NOTE: We don't need to generate ghosts here, because we only care if
+    // the user is attending, and you can't attend a ghost event: RSVP'ing
+    // to it creates a real event.
+
     $events = id(new PhabricatorCalendarEventQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withInvitedPHIDs(array_keys($rebuild))
       ->withIsCancelled(false)
       ->withDateRange($min_range, $max_range)
       ->execute();
 
     // Group all the events by invited user. Only examine events that users
     // are actually attending.
     $map = array();
     foreach ($events as $event) {
       foreach ($event->getInvitees() as $invitee) {
         if (!$invitee->isAttending()) {
           continue;
         }
 
         $invitee_phid = $invitee->getInviteePHID();
         if (!isset($rebuild[$invitee_phid])) {
           continue;
         }
 
         $map[$invitee_phid][] = $event;
       }
     }
 
     foreach ($rebuild as $phid => $user) {
       $events = idx($map, $phid, array());
 
       $cursor = $min_range;
       if ($events) {
         // Find the next time when the user has no meetings. If we move forward
         // because of an event, we check again for events after that one ends.
         while (true) {
           foreach ($events as $event) {
             $from = $event->getDateFromForCache();
             $to = $event->getDateTo();
             if (($from <= $cursor) && ($to > $cursor)) {
               $cursor = $to;
               continue 2;
             }
           }
           break;
         }
       }
 
       if ($cursor > $min_range) {
         $availability = array(
           'until' => $cursor,
         );
         $availability_ttl = $cursor;
       } else {
         $availability = array(
           'until' => null,
         );
         $availability_ttl = $max_range;
       }
 
       // Never TTL the cache to longer than the maximum range we examined.
       $availability_ttl = min($availability_ttl, $max_range);
 
       $user->writeAvailabilityCache($availability, $availability_ttl);
       $user->attachAvailability($availability);
     }
   }
 
 }
diff --git a/src/applications/people/storage/PhabricatorExternalAccount.php b/src/applications/people/storage/PhabricatorExternalAccount.php
index 12d5f545a..4bc0fcae9 100644
--- a/src/applications/people/storage/PhabricatorExternalAccount.php
+++ b/src/applications/people/storage/PhabricatorExternalAccount.php
@@ -1,165 +1,163 @@
 <?php
 
 final class PhabricatorExternalAccount extends PhabricatorUserDAO
   implements PhabricatorPolicyInterface {
 
   protected $userPHID;
   protected $accountType;
   protected $accountDomain;
   protected $accountSecret;
   protected $accountID;
   protected $displayName;
   protected $username;
   protected $realName;
   protected $email;
   protected $emailVerified = 0;
   protected $accountURI;
   protected $profileImagePHID;
   protected $properties = array();
 
   private $profileImageFile = self::ATTACHABLE;
 
   public function getProfileImageFile() {
     return $this->assertAttached($this->profileImageFile);
   }
 
   public function attachProfileImageFile(PhabricatorFile $file) {
     $this->profileImageFile = $file;
     return $this;
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPeopleExternalPHIDType::TYPECONST);
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'userPHID' => 'phid?',
         'accountType' => 'text16',
         'accountDomain' => 'text64',
         'accountSecret' => 'text?',
         'accountID' => 'text64',
         'displayName' => 'text255?',
         'username' => 'text255?',
         'realName' => 'text255?',
         'email' => 'text255?',
         'emailVerified' => 'bool',
         'profileImagePHID' => 'phid?',
         'accountURI' => 'text255?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
-        'key_phid' => null,
-        'phid' => array(
-          'columns' => array('phid'),
-          'unique' => true,
-        ),
         'account_details' => array(
           'columns' => array('accountType', 'accountDomain', 'accountID'),
           'unique' => true,
         ),
+        'key_user' => array(
+          'columns' => array('userPHID'),
+        ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function getPhabricatorUser() {
     $tmp_usr = id(new PhabricatorUser())
       ->makeEphemeral()
       ->setPHID($this->getPHID());
     return $tmp_usr;
   }
 
   public function getProviderKey() {
     return $this->getAccountType().':'.$this->getAccountDomain();
   }
 
   public function save() {
     if (!$this->getAccountSecret()) {
       $this->setAccountSecret(Filesystem::readRandomCharacters(32));
     }
     return parent::save();
   }
 
   public function setProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function isUsableForLogin() {
     $key = $this->getProviderKey();
     $provider = PhabricatorAuthProvider::getEnabledProviderByKey($key);
 
     if (!$provider) {
       return false;
     }
 
     if (!$provider->shouldAllowLogin()) {
       return false;
     }
 
     return true;
   }
 
   public function getDisplayName() {
     if (strlen($this->displayName)) {
       return $this->displayName;
     }
 
     // TODO: Figure out how much identifying information we're going to show
     // to users about external accounts. For now, just show a string which is
     // clearly not an error, but don't disclose any identifying information.
 
     $map = array(
       'email' => pht('Email User'),
     );
 
     $type = $this->getAccountType();
 
     return idx($map, $type, pht('"%s" User', $type));
   }
 
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_NOONE;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return ($viewer->getPHID() == $this->getUserPHID());
   }
 
   public function describeAutomaticCapability($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return null;
       case PhabricatorPolicyCapability::CAN_EDIT:
         return pht(
           'External accounts can only be edited by the account owner.');
     }
   }
 
 }
diff --git a/src/applications/phlux/controller/PhluxEditController.php b/src/applications/phlux/controller/PhluxEditController.php
index 775737c9b..f37795a75 100644
--- a/src/applications/phlux/controller/PhluxEditController.php
+++ b/src/applications/phlux/controller/PhluxEditController.php
@@ -1,177 +1,187 @@
 <?php
 
 final class PhluxEditController extends PhluxController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $key = $request->getURIData('key');
 
     $is_new = ($key === null);
     if ($is_new) {
       $var = new PhluxVariable();
       $var->setViewPolicy(PhabricatorPolicies::POLICY_USER);
       $var->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     } else {
       $var = id(new PhluxVariableQuery())
         ->setViewer($viewer)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->withKeys(array($key))
         ->executeOne();
       if (!$var) {
         return new Aphront404Response();
       }
       $view_uri = $this->getApplicationURI('/view/'.$key.'/');
     }
 
     $e_key = ($is_new ? true : null);
     $e_value = true;
     $errors = array();
 
     $key = $var->getVariableKey();
 
     $display_value = null;
     $value = $var->getVariableValue();
 
     if ($request->isFormPost()) {
       if ($is_new) {
         $key = $request->getStr('key');
         if (!strlen($key)) {
           $errors[] = pht('Variable key is required.');
           $e_key = pht('Required');
         } else if (!preg_match('/^[a-z0-9.-]+\z/', $key)) {
           $errors[] = pht(
             'Variable key "%s" must contain only lowercase letters, digits, '.
             'period, and hyphen.',
             $key);
           $e_key = pht('Invalid');
         }
       }
 
       $raw_value = $request->getStr('value');
       $value = json_decode($raw_value, true);
       if ($value === null && strtolower($raw_value) !== 'null') {
         $e_value = pht('Invalid');
         $errors[] = pht('Variable value must be valid JSON.');
         $display_value = $raw_value;
       }
 
       if (!$errors) {
         $editor = id(new PhluxVariableEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request);
 
         $xactions = array();
         $xactions[] = id(new PhluxTransaction())
           ->setTransactionType(PhluxTransaction::TYPE_EDIT_KEY)
           ->setNewValue($key);
 
         $xactions[] = id(new PhluxTransaction())
           ->setTransactionType(PhluxTransaction::TYPE_EDIT_VALUE)
           ->setNewValue($value);
 
         $xactions[] = id(new PhluxTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
           ->setNewValue($request->getStr('viewPolicy'));
 
         $xactions[] = id(new PhluxTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
           ->setNewValue($request->getStr('editPolicy'));
 
         try {
           $editor->applyTransactions($var, $xactions);
           $view_uri = $this->getApplicationURI('/view/'.$key.'/');
           return id(new AphrontRedirectResponse())->setURI($view_uri);
         } catch (AphrontDuplicateKeyQueryException $ex) {
           $e_key = pht('Not Unique');
           $errors[] = pht('Variable key must be unique.');
         }
       }
     }
 
     if ($display_value === null) {
       if (is_array($value) &&
           (array_keys($value) !== array_keys(array_values($value)))) {
         $json = new PhutilJSON();
         $display_value = $json->encodeFormatted($value);
       } else {
         $display_value = json_encode($value);
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($var)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setValue($var->getVariableKey())
           ->setLabel(pht('Key'))
           ->setName('key')
           ->setError($e_key)
           ->setCaption(pht('Lowercase letters, digits, dot and hyphen only.'))
           ->setDisabled(!$is_new))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setValue($display_value)
           ->setLabel(pht('Value'))
           ->setName('value')
           ->setCaption(pht('Enter value as JSON.'))
           ->setError($e_value))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($var)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($var)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicies($policies));
 
     if ($is_new) {
       $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Create Variable')));
     } else {
       $form->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Update Variable'))
           ->addCancelButton($view_uri));
     }
 
     $crumbs = $this->buildApplicationCrumbs();
 
     if ($is_new) {
       $title = pht('Create Variable');
       $crumbs->addTextCrumb($title, $request->getRequestURI());
+      $header_icon = 'fa-plus-square';
     } else {
-      $title = pht('Edit %s', $key);
+      $title = pht('Edit Variable: %s', $key);
+      $header_icon = 'fa-pencil';
       $crumbs->addTextCrumb($title, $request->getRequestURI());
     }
+    $crumbs->setBorder(true);
 
-    $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Variable'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-      ),
-      array(
-        'title' => $title,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $box,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/phlux/controller/PhluxListController.php b/src/applications/phlux/controller/PhluxListController.php
index 550f571a6..e75fb0999 100644
--- a/src/applications/phlux/controller/PhluxListController.php
+++ b/src/applications/phlux/controller/PhluxListController.php
@@ -1,46 +1,59 @@
 <?php
 
 final class PhluxListController extends PhluxController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $pager = new AphrontCursorPagerView();
     $pager->readFromRequest($request);
     $query = id(new PhluxVariableQuery())
       ->setViewer($viewer);
 
     $vars = $query->executeWithCursorPager($pager);
 
     $view = new PHUIObjectItemListView();
+    $view->setFlush(true);
     foreach ($vars as $var) {
       $key = $var->getVariableKey();
 
       $item = new PHUIObjectItemView();
       $item->setHeader($key);
       $item->setHref($this->getApplicationURI('/view/'.$key.'/'));
       $item->addIcon(
         'none',
         phabricator_datetime($var->getDateModified(), $viewer));
 
       $view->addItem($item);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
 
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText('Variables')
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->appendChild($view);
+
     $title = pht('Variable List');
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-copy');
 
     $crumbs->addTextCrumb($title, $this->getApplicationURI());
+    $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $view,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $box,
         $pager,
-      ),
-      array(
-        'title'  => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phlux/controller/PhluxViewController.php b/src/applications/phlux/controller/PhluxViewController.php
index 384e15b57..6ad0fe22c 100644
--- a/src/applications/phlux/controller/PhluxViewController.php
+++ b/src/applications/phlux/controller/PhluxViewController.php
@@ -1,74 +1,83 @@
 <?php
 
 final class PhluxViewController extends PhluxController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $key = $request->getURIData('key');
 
     $var = id(new PhluxVariableQuery())
       ->setViewer($viewer)
       ->withKeys(array($key))
       ->executeOne();
 
     if (!$var) {
       return new Aphront404Response();
     }
 
-    $crumbs = $this->buildApplicationCrumbs();
-
     $title = $var->getVariableKey();
 
+    $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title, $request->getRequestURI());
+    $crumbs->setBorder(true);
+
+    $curtain = $this->buildCurtainView($var);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setUser($viewer)
-      ->setPolicyObject($var);
-
-    $actions = id(new PhabricatorActionListView())
-      ->setUser($viewer)
-      ->setObject($var);
-
-    $can_edit = PhabricatorPolicyFilter::hasCapability(
-      $viewer,
-      $var,
-      PhabricatorPolicyCapability::CAN_EDIT);
-
-    $actions->addAction(
-      id(new PhabricatorActionView())
-        ->setIcon('fa-pencil')
-        ->setName(pht('Edit Variable'))
-        ->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/'))
-        ->setDisabled(!$can_edit)
-        ->setWorkflow(!$can_edit));
+      ->setPolicyObject($var)
+      ->setHeaderIcon('fa-copy');
 
     $display_value = json_encode($var->getVariableValue());
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
-      ->setObject($var)
-      ->setActionList($actions)
       ->addProperty(pht('Value'), $display_value);
 
     $timeline = $this->buildTransactionTimeline(
       $var,
       new PhluxTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
+      ->setHeaderText(pht('Details'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($properties);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setCurtain($curtain)
+      ->setMainColumn(array(
         $object_box,
         $timeline,
-      ),
-      array(
-        'title'  => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+  }
+
+  private function buildCurtainView(PhluxVariable $var) {
+    $viewer = $this->getViewer();
+
+    $curtain = $this->newCurtainView($var);
+
+    $can_edit = PhabricatorPolicyFilter::hasCapability(
+      $viewer,
+      $var,
+      PhabricatorPolicyCapability::CAN_EDIT);
+
+    $curtain->addAction(
+      id(new PhabricatorActionView())
+        ->setIcon('fa-pencil')
+        ->setName(pht('Edit Variable'))
+        ->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/'))
+        ->setDisabled(!$can_edit)
+        ->setWorkflow(!$can_edit));
+
+    return $curtain;
   }
 
 }
diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php
index 59d9273ea..a0e110000 100644
--- a/src/applications/phortune/controller/PhortuneMerchantViewController.php
+++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php
@@ -1,320 +1,320 @@
 <?php
 
 final class PhortuneMerchantViewController
   extends PhortuneMerchantController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $merchant = id(new PhortuneMerchantQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$merchant) {
       return new Aphront404Response();
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($merchant->getName());
     $crumbs->setBorder(true);
 
     $title = pht(
       'Merchant %d %s',
       $merchant->getID(),
       $merchant->getName());
 
     $header = id(new PHUIHeaderView())
       ->setHeader($merchant->getName())
       ->setUser($viewer)
       ->setPolicyObject($merchant)
       ->setHeaderIcon('fa-bank');
 
     $providers = id(new PhortunePaymentProviderConfigQuery())
       ->setViewer($viewer)
       ->withMerchantPHIDs(array($merchant->getPHID()))
       ->execute();
 
     $details = $this->buildDetailsView($merchant, $providers);
     $description = $this->buildDescriptionView($merchant);
     $curtain = $this->buildCurtainView($merchant);
 
     $provider_list = $this->buildProviderList(
       $merchant,
       $providers);
 
     $timeline = $this->buildTransactionTimeline(
       $merchant,
       new PhortuneMerchantTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $details,
         $description,
         $provider_list,
         $timeline,
       ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildDetailsView(
     PhortuneMerchant $merchant,
     array $providers) {
 
     $viewer = $this->getRequest()->getUser();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($merchant);
 
     $status_view = new PHUIStatusListView();
 
     $have_any = false;
     $any_test = false;
     foreach ($providers as $provider_config) {
       $provider = $provider_config->buildProvider();
       if ($provider->isEnabled()) {
         $have_any = true;
       }
       if (!$provider->isAcceptingLivePayments()) {
         $any_test = true;
       }
     }
 
     if ($have_any) {
       $status_view->addItem(
         id(new PHUIStatusItemView())
           ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
           ->setTarget(pht('Accepts Payments'))
           ->setNote(pht('This merchant can accept payments.')));
 
       if ($any_test) {
         $status_view->addItem(
           id(new PHUIStatusItemView())
             ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow')
             ->setTarget(pht('Test Mode'))
             ->setNote(pht('This merchant is accepting test payments.')));
       } else {
         $status_view->addItem(
           id(new PHUIStatusItemView())
           ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
             ->setTarget(pht('Live Mode'))
             ->setNote(pht('This merchant is accepting live payments.')));
       }
     } else if ($providers) {
       $status_view->addItem(
         id(new PHUIStatusItemView())
           ->setIcon(PHUIStatusItemView::ICON_REJECT, 'red')
           ->setTarget(pht('No Enabled Providers'))
           ->setNote(
             pht(
               'All of the payment providers for this merchant are '.
               'disabled.')));
     } else {
       $status_view->addItem(
         id(new PHUIStatusItemView())
           ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow')
           ->setTarget(pht('No Providers'))
           ->setNote(
             pht(
               'This merchant does not have any payment providers configured '.
               'yet, so it can not accept payments. Add a provider.')));
     }
 
     $view->addProperty(pht('Status'), $status_view);
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($view);
   }
 
   private function buildDescriptionView(PhortuneMerchant $merchant) {
     $viewer = $this->getViewer();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $description = $merchant->getDescription();
     if (strlen($description)) {
       $description = new PHUIRemarkupView($viewer, $description);
       $view->addTextContent($description);
       return id(new PHUIObjectBoxView())
-        ->setHeaderText(pht('DESCRIPTION'))
+        ->setHeaderText(pht('Description'))
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->appendChild($view);
     }
 
     return null;
   }
 
   private function buildCurtainView(PhortuneMerchant $merchant) {
     $viewer = $this->getRequest()->getUser();
     $id = $merchant->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $merchant,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($merchant);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Merchant'))
         ->setIcon('fa-pencil')
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit)
         ->setHref($this->getApplicationURI("merchant/edit/{$id}/")));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View Orders'))
         ->setIcon('fa-shopping-cart')
         ->setHref($this->getApplicationURI("merchant/orders/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View Subscriptions'))
         ->setIcon('fa-moon-o')
         ->setHref($this->getApplicationURI("merchant/{$id}/subscription/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('New Invoice'))
         ->setIcon('fa-fax')
         ->setHref($this->getApplicationURI("merchant/{$id}/invoice/new/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $member_phids = $merchant->getMemberPHIDs();
     $handles = $viewer->loadHandles($member_phids);
 
     $member_list = id(new PHUIObjectItemListView())
       ->setSimple(true);
 
     foreach ($member_phids as $member_phid) {
       $image_uri = $handles[$member_phid]->getImageURI();
       $image_href = $handles[$member_phid]->getURI();
       $person = $handles[$member_phid];
 
       $member = id(new PHUIObjectItemView())
         ->setImageURI($image_uri)
         ->setHref($image_href)
         ->setHeader($person->getFullName());
 
       $member_list->addItem($member);
     }
 
     $curtain->newPanel()
       ->setHeaderText(pht('Members'))
       ->appendChild($member_list);
 
     return $curtain;
   }
 
   private function buildProviderList(
     PhortuneMerchant $merchant,
     array $providers) {
 
     $viewer = $this->getRequest()->getUser();
     $id = $merchant->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $merchant,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $provider_list = id(new PHUIObjectItemListView())
       ->setFlush(true)
       ->setNoDataString(pht('This merchant has no payment providers.'));
 
     foreach ($providers as $provider_config) {
       $provider = $provider_config->buildProvider();
       $provider_id = $provider_config->getID();
 
       $item = id(new PHUIObjectItemView())
         ->setHeader($provider->getName());
 
       if ($provider->isEnabled()) {
         if ($provider->isAcceptingLivePayments()) {
           $item->setStatusIcon('fa-check green');
         } else {
           $item->setStatusIcon('fa-warning yellow');
           $item->addIcon('fa-exclamation-triangle', pht('Test Mode'));
         }
 
         $item->addAttribute($provider->getConfigureProvidesDescription());
       } else {
         // Don't show disabled providers to users who can't manage the merchant
         // account.
         if (!$can_edit) {
           continue;
         }
         $item->setDisabled(true);
         $item->addAttribute(
           phutil_tag('em', array(), pht('This payment provider is disabled.')));
       }
 
 
       if ($can_edit) {
         $edit_uri = $this->getApplicationURI(
           "/provider/edit/{$provider_id}/");
         $disable_uri = $this->getApplicationURI(
           "/provider/disable/{$provider_id}/");
 
         if ($provider->isEnabled()) {
           $disable_icon = 'fa-times';
           $disable_name = pht('Disable');
         } else {
           $disable_icon = 'fa-check';
           $disable_name = pht('Enable');
         }
 
         $item->addAction(
           id(new PHUIListItemView())
             ->setIcon($disable_icon)
             ->setHref($disable_uri)
             ->setName($disable_name)
             ->setWorkflow(true));
 
         $item->addAction(
           id(new PHUIListItemView())
             ->setIcon('fa-pencil')
             ->setHref($edit_uri)
             ->setName(pht('Edit')));
       }
 
       $provider_list->addItem($item);
     }
 
     $add_action = id(new PHUIButtonView())
       ->setTag('a')
       ->setHref($this->getApplicationURI('provider/edit/?merchantID='.$id))
       ->setText(pht('Add Payment Provider'))
       ->setDisabled(!$can_edit)
       ->setWorkflow(!$can_edit)
       ->setIcon('fa-plus');
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Payment Providers'))
       ->addActionLink($add_action);
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setObjectList($provider_list);
   }
 
 
 }
diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php
index 0bf022e37..a434a9453 100644
--- a/src/applications/phortune/controller/PhortuneProductViewController.php
+++ b/src/applications/phortune/controller/PhortuneProductViewController.php
@@ -1,58 +1,58 @@
 <?php
 
 final class PhortuneProductViewController extends PhortuneController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $product = id(new PhortuneProductQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$product) {
       return new Aphront404Response();
     }
 
     $title = pht('Product: %s', $product->getProductName());
 
     $header = id(new PHUIHeaderView())
       ->setHeader($product->getProductName())
       ->setHeaderIcon('fa-gift');
 
     $edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/');
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Products'),
       $this->getApplicationURI('product/'));
     $crumbs->addTextCrumb(
       pht('#%d', $product->getID()),
       $request->getRequestURI());
     $crumbs->setBorder(true);
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->addProperty(
         pht('Price'),
         $product->getPriceAsCurrency()->formatForDisplay());
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($properties);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setFooter(array(
         $object_box,
       ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
 
   }
 
 }
diff --git a/src/applications/phortune/controller/PhortuneProviderActionController.php b/src/applications/phortune/controller/PhortuneProviderActionController.php
index 52d5453f4..4d9b9f838 100644
--- a/src/applications/phortune/controller/PhortuneProviderActionController.php
+++ b/src/applications/phortune/controller/PhortuneProviderActionController.php
@@ -1,80 +1,81 @@
 <?php
 
 final class PhortuneProviderActionController
   extends PhortuneController {
 
   private $action;
 
   public function setAction($action) {
     $this->action = $action;
     return $this;
   }
 
   public function getAction() {
     return $this->action;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $this->setAction($request->getURIData('action'));
 
     $provider_config = id(new PhortunePaymentProviderConfigQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$provider_config) {
       return new Aphront404Response();
     }
 
     $provider = $provider_config->buildProvider();
 
     if (!$provider->canRespondToControllerAction($this->getAction())) {
       return new Aphront404Response();
     }
 
     $response = $provider->processControllerRequest($this, $request);
 
     if ($response instanceof AphrontResponse) {
       return $response;
     }
 
-    return $this->buildApplicationPage(
-      $response,
-      array(
-        'title' => pht('Phortune'),
-      ));
+    $title = pht('Phortune');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->appendChild($response);
+
   }
 
 
   public function loadCart($id) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     return id(new PhortuneCartQuery())
       ->setViewer($viewer)
       ->needPurchases(true)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
   }
 
   public function loadActiveCharge(PhortuneCart $cart) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     return id(new PhortuneChargeQuery())
       ->setViewer($viewer)
       ->withCartPHIDs(array($cart->getPHID()))
       ->withStatuses(
         array(
           PhortuneCharge::STATUS_CHARGING,
         ))
       ->executeOne();
   }
 
 }
diff --git a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
index 0e1bc55b6..0aea39613 100644
--- a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
+++ b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
@@ -1,209 +1,209 @@
 <?php
 
 final class PhortuneSubscriptionViewController extends PhortuneController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $authority = $this->loadMerchantAuthority();
 
     $subscription_query = id(new PhortuneSubscriptionQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->needTriggers(true);
 
     if ($authority) {
       $subscription_query->withMerchantPHIDs(array($authority->getPHID()));
     }
 
     $subscription = $subscription_query->executeOne();
     if (!$subscription) {
       return new Aphront404Response();
     }
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $subscription,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $merchant = $subscription->getMerchant();
     $account = $subscription->getAccount();
 
     $account_id = $account->getID();
     $subscription_id = $subscription->getID();
 
     $title = $subscription->getSubscriptionFullName();
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
       ->setHeaderIcon('fa-calendar-o');
 
     $curtain = $this->newCurtainView($subscription);
     $edit_uri = $subscription->getEditURI();
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setName(pht('Edit Subscription'))
         ->setHref($edit_uri)
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($authority) {
       $this->addMerchantCrumb($crumbs, $merchant);
     } else {
       $this->addAccountCrumb($crumbs, $account);
     }
     $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName());
     $crumbs->setBorder(true);
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $next_invoice = $subscription->getTrigger()->getNextEventPrediction();
     $properties->addProperty(
       pht('Next Invoice'),
       phabricator_datetime($next_invoice, $viewer));
 
     $default_method = $subscription->getDefaultPaymentMethodPHID();
     if ($default_method) {
       $handles = $this->loadViewerHandles(array($default_method));
       $autopay_method = $handles[$default_method]->renderLink();
     } else {
       $autopay_method = phutil_tag(
         'em',
         array(),
         pht('No Autopay Method Configured'));
     }
 
     $properties->addProperty(
       pht('Autopay With'),
       $autopay_method);
 
     $details = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($properties);
 
     $due_box = $this->buildDueInvoices($subscription, $authority);
     $invoice_box = $this->buildPastInvoices($subscription, $authority);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $details,
         $due_box,
         $invoice_box,
     ));
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildDueInvoices(
     PhortuneSubscription $subscription,
     $authority) {
     $viewer = $this->getViewer();
 
     $invoices = id(new PhortuneCartQuery())
       ->setViewer($viewer)
       ->withSubscriptionPHIDs(array($subscription->getPHID()))
       ->needPurchases(true)
       ->withInvoices(true)
       ->execute();
 
     $phids = array();
     foreach ($invoices as $invoice) {
       $phids[] = $invoice->getPHID();
       $phids[] = $invoice->getMerchantPHID();
       foreach ($invoice->getPurchases() as $purchase) {
         $phids[] = $purchase->getPHID();
       }
     }
     $handles = $this->loadViewerHandles($phids);
 
     $invoice_table = id(new PhortuneOrderTableView())
       ->setUser($viewer)
       ->setCarts($invoices)
       ->setIsInvoices(true)
       ->setIsMerchantView((bool)$authority)
       ->setHandles($handles);
 
     $invoice_header = id(new PHUIHeaderView())
       ->setHeader(pht('Invoices Due'));
 
     return id(new PHUIObjectBoxView())
       ->setHeader($invoice_header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($invoice_table);
   }
 
   private function buildPastInvoices(
     PhortuneSubscription $subscription,
     $authority) {
     $viewer = $this->getViewer();
 
     $invoices = id(new PhortuneCartQuery())
       ->setViewer($viewer)
       ->withSubscriptionPHIDs(array($subscription->getPHID()))
       ->needPurchases(true)
       ->withStatuses(
         array(
           PhortuneCart::STATUS_PURCHASING,
           PhortuneCart::STATUS_CHARGED,
           PhortuneCart::STATUS_HOLD,
           PhortuneCart::STATUS_REVIEW,
           PhortuneCart::STATUS_PURCHASED,
         ))
       ->setLimit(50)
       ->execute();
 
     $phids = array();
     foreach ($invoices as $invoice) {
       $phids[] = $invoice->getPHID();
       foreach ($invoice->getPurchases() as $purchase) {
         $phids[] = $purchase->getPHID();
       }
     }
     $handles = $this->loadViewerHandles($phids);
 
     $invoice_table = id(new PhortuneOrderTableView())
       ->setUser($viewer)
       ->setCarts($invoices)
       ->setHandles($handles);
 
     $account = $subscription->getAccount();
     $merchant = $subscription->getMerchant();
 
     $account_id = $account->getID();
     $merchant_id = $merchant->getID();
     $subscription_id = $subscription->getID();
 
     if ($authority) {
       $invoices_uri = $this->getApplicationURI(
         "merchant/{$merchant_id}/subscription/order/{$subscription_id}/");
     } else {
       $invoices_uri = $this->getApplicationURI(
         "{$account_id}/subscription/order/{$subscription_id}/");
     }
 
     $invoice_header = id(new PHUIHeaderView())
       ->setHeader(pht('Past Invoices'))
       ->addActionLink(
         id(new PHUIButtonView())
           ->setTag('a')
           ->setIcon('fa-list')
           ->setHref($invoices_uri)
           ->setText(pht('View All Invoices')));
 
     return id(new PHUIObjectBoxView())
       ->setHeader($invoice_header)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($invoice_table);
   }
 
 }
diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
index 290666e37..8a530075a 100644
--- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
+++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
@@ -1,59 +1,70 @@
 <?php
 
 final class PhabricatorXHPASTViewRunController
   extends PhabricatorXHPASTViewController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     if ($request->isFormPost()) {
       $source = $request->getStr('source');
 
       $future = PhutilXHPASTBinary::getParserFuture($source);
       $resolved = $future->resolve();
 
       // This is just to let it throw exceptions if stuff is broken.
       try {
         XHPASTTree::newFromDataAndResolvedExecFuture($source, $resolved);
       } catch (XHPASTSyntaxErrorException $ex) {
         // This is possibly expected.
       }
 
       list($err, $stdout, $stderr) = $resolved;
 
       $storage_tree = id(new PhabricatorXHPASTParseTree())
         ->setInput($source)
         ->setReturnCode($err)
         ->setStdout($stdout)
         ->setStderr($stderr)
         ->setAuthorPHID($viewer->getPHID())
         ->save();
 
       return id(new AphrontRedirectResponse())
         ->setURI('/xhpast/view/'.$storage_tree->getID().'/');
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Source'))
           ->setName('source')
           ->setValue("<?php\n\n")
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Parse')));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Generate XHP AST'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      $form_box,
-      array(
-        'title' => pht('XHPAST View'),
+    $title = pht('XHPAST View');
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-ambulance');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
+        $form_box,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php
index 7b0a16cc5..d511835ce 100644
--- a/src/applications/phragment/controller/PhragmentBrowseController.php
+++ b/src/applications/phragment/controller/PhragmentBrowseController.php
@@ -1,98 +1,95 @@
 <?php
 
 final class PhragmentBrowseController extends PhragmentController {
 
-  private $dblob;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $current = nonempty(last($parents), null);
 
     $path = '';
     if ($current !== null) {
       $path = $current->getPath();
     }
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     if ($this->hasApplicationCapability(
       PhragmentCanCreateCapability::CAPABILITY)) {
       $crumbs->addAction(
         id(new PHUIListItemView())
           ->setName(pht('Create Fragment'))
           ->setHref($this->getApplicationURI('/create/'.$path))
           ->setIcon('fa-plus-square'));
     }
 
     $current_box = $this->createCurrentFragmentView($current, false);
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $fragments = null;
     if ($current === null) {
       // Find all root fragments.
       $fragments = id(new PhragmentFragmentQuery())
         ->setViewer($this->getRequest()->getUser())
         ->needLatestVersion(true)
         ->withDepths(array(1))
         ->execute();
     } else {
       // Find all child fragments.
       $fragments = id(new PhragmentFragmentQuery())
         ->setViewer($this->getRequest()->getUser())
         ->needLatestVersion(true)
         ->withLeadingPath($current->getPath().'/')
         ->withDepths(array($current->getDepth() + 1))
         ->execute();
     }
 
     foreach ($fragments as $fragment) {
       $item = id(new PHUIObjectItemView());
       $item->setHeader($fragment->getName());
       $item->setHref($fragment->getURI());
       if (!$fragment->isDirectory()) {
         $item->addAttribute(pht(
           'Last Updated %s',
           phabricator_datetime(
             $fragment->getLatestVersion()->getDateCreated(),
             $viewer)));
         $item->addAttribute(pht(
           'Latest Version %s',
           $fragment->getLatestVersion()->getSequence()));
         if ($fragment->isDeleted()) {
           $item->setDisabled(true);
           $item->addAttribute(pht('Deleted'));
         }
       } else {
         $item->addAttribute(pht('Directory'));
       }
       $list->addItem($item);
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $current_box,
-        $list,
-      ),
-      array(
-        'title' => pht('Browse Fragments'),
-      ));
+    $title = pht('Browse Fragments');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $current_box,
+      $list,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentCreateController.php b/src/applications/phragment/controller/PhragmentCreateController.php
index 2a5b54538..946d7cc33 100644
--- a/src/applications/phragment/controller/PhragmentCreateController.php
+++ b/src/applications/phragment/controller/PhragmentCreateController.php
@@ -1,138 +1,135 @@
 <?php
 
 final class PhragmentCreateController extends PhragmentController {
 
-  private $dblob;
-
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
     $parent = null;
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     if (count($parents) !== 0) {
       $parent = idx($parents, count($parents) - 1, null);
     }
 
     $parent_path = '';
     if ($parent !== null) {
       $parent_path = $parent->getPath();
     }
     $parent_path = trim($parent_path, '/');
 
     $fragment = id(new PhragmentFragment());
 
     $error_view = null;
 
     if ($request->isFormPost()) {
       $errors = array();
 
       $v_name = $request->getStr('name');
       $v_fileid = $request->getInt('fileID');
       $v_viewpolicy = $request->getStr('viewPolicy');
       $v_editpolicy = $request->getStr('editPolicy');
 
       if (strpos($v_name, '/') !== false) {
         $errors[] = pht("The fragment name can not contain '/'.");
       }
 
       $file = id(new PhabricatorFileQuery())
         ->setViewer($viewer)
         ->withIDs(array($v_fileid))
         ->executeOne();
       if (!$file) {
         $errors[] = pht("The specified file doesn't exist.");
       }
 
       if (!count($errors)) {
         $depth = 1;
         if ($parent !== null) {
           $depth = $parent->getDepth() + 1;
         }
 
         PhragmentFragment::createFromFile(
           $viewer,
           $file,
           trim($parent_path.'/'.$v_name, '/'),
           $v_viewpolicy,
           $v_editpolicy);
 
         return id(new AphrontRedirectResponse())
           ->setURI('/phragment/browse/'.trim($parent_path.'/'.$v_name, '/'));
       } else {
         $error_view = id(new PHUIInfoView())
           ->setErrors($errors)
           ->setTitle(pht('Errors while creating fragment'));
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($fragment)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Parent Path'))
           ->setDisabled(true)
           ->setValue('/'.trim($parent_path.'/', '/')))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Name'))
           ->setName('name'))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('File ID'))
           ->setName('fileID'))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setName('viewPolicy')
           ->setPolicyObject($fragment)
           ->setPolicies($policies)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setName('editPolicy')
           ->setPolicyObject($fragment)
           ->setPolicies($policies)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Create Fragment'))
           ->addCancelButton(
             $this->getApplicationURI('browse/'.$parent_path)));
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('Create Fragment'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Create Fragment'))
       ->setForm($form);
 
     if ($error_view) {
       $box->setInfoView($error_view);
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-      ),
-      array(
-        'title' => pht('Create Fragment'),
-      ));
+    $title = pht('Create Fragments');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php
index 72e0000b8..4e16deb43 100644
--- a/src/applications/phragment/controller/PhragmentHistoryController.php
+++ b/src/applications/phragment/controller/PhragmentHistoryController.php
@@ -1,112 +1,109 @@
 <?php
 
 final class PhragmentHistoryController extends PhragmentController {
 
-  private $dblob;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $current = idx($parents, count($parents) - 1, null);
 
     $path = $current->getPath();
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     if ($this->hasApplicationCapability(
       PhragmentCanCreateCapability::CAPABILITY)) {
       $crumbs->addAction(
         id(new PHUIListItemView())
           ->setName(pht('Create Fragment'))
           ->setHref($this->getApplicationURI('/create/'.$path))
           ->setIcon('fa-plus-square'));
     }
 
     $current_box = $this->createCurrentFragmentView($current, true);
 
     $versions = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
       ->withFragmentPHIDs(array($current->getPHID()))
       ->execute();
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $file_phids = mpull($versions, 'getFilePHID');
     $files = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs($file_phids)
       ->execute();
     $files = mpull($files, null, 'getPHID');
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $current,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $first = true;
     foreach ($versions as $version) {
       $item = id(new PHUIObjectItemView());
       $item->setHeader(pht('Version %s', $version->getSequence()));
       $item->setHref($version->getURI());
       $item->addAttribute(phabricator_datetime(
         $version->getDateCreated(),
         $viewer));
 
       if ($version->getFilePHID() === null) {
         $item->setDisabled(true);
         $item->addAttribute(pht('Deletion'));
       }
 
       if (!$first && $can_edit) {
         $item->addAction(id(new PHUIListItemView())
           ->setIcon('fa-refresh')
           ->setRenderNameAsTooltip(true)
           ->setWorkflow(true)
           ->setName(pht('Revert to Here'))
           ->setHref($this->getApplicationURI(
             'revert/'.$version->getID().'/'.$current->getPath())));
       }
 
       $disabled = !isset($files[$version->getFilePHID()]);
       $action = id(new PHUIListItemView())
         ->setIcon('fa-download')
         ->setDisabled($disabled || !$this->isCorrectlyConfigured())
         ->setRenderNameAsTooltip(true)
         ->setName(pht('Download'));
       if (!$disabled && $this->isCorrectlyConfigured()) {
         $action->setHref($files[$version->getFilePHID()]
           ->getDownloadURI($version->getURI()));
       }
       $item->addAction($action);
 
       $list->addItem($item);
 
       $first = false;
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $current_box,
-        $list,
-      ),
-      array(
-        'title' => pht('Fragment History'),
-      ));
+    $title = pht('Fragment History');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $current_box,
+      $list,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php
index 5a0440389..5ad4b6dfe 100644
--- a/src/applications/phragment/controller/PhragmentPatchController.php
+++ b/src/applications/phragment/controller/PhragmentPatchController.php
@@ -1,104 +1,97 @@
 <?php
 
 final class PhragmentPatchController extends PhragmentController {
 
-  private $aid;
-  private $bid;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->aid = idx($data, 'aid', 0);
-    $this->bid = idx($data, 'bid', 0);
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $aid = $request->getURIData('aid');
+    $bid = $request->getURIData('bid');
 
     // If "aid" is "x", then it means the user wants to generate
     // a patch of an empty file to the version specified by "bid".
 
-    $ids = array($this->aid, $this->bid);
-    if ($this->aid === 'x') {
-      $ids = array($this->bid);
+    $ids = array($aid, $bid);
+    if ($aid === 'x') {
+      $ids = array($bid);
     }
 
     $versions = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
       ->withIDs($ids)
       ->execute();
 
     $version_a = null;
-    if ($this->aid !== 'x') {
-      $version_a = idx($versions, $this->aid, null);
+    if ($aid !== 'x') {
+      $version_a = idx($versions, $aid, null);
       if ($version_a === null) {
         return new Aphront404Response();
       }
     }
 
-    $version_b = idx($versions, $this->bid, null);
+    $version_b = idx($versions, $bid, null);
     if ($version_b === null) {
       return new Aphront404Response();
     }
 
     $file_phids = array();
     if ($version_a !== null) {
       $file_phids[] = $version_a->getFilePHID();
     }
     $file_phids[] = $version_b->getFilePHID();
 
     $files = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs($file_phids)
       ->execute();
     $files = mpull($files, null, 'getPHID');
 
     $file_a = null;
     if ($version_a != null) {
       $file_a = idx($files, $version_a->getFilePHID(), null);
     }
     $file_b = idx($files, $version_b->getFilePHID(), null);
 
     $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b);
 
     if ($patch === null) {
       // There are no differences between the two files, so we output
       // an empty patch.
       $patch = '';
     }
 
     $a_sequence = 'x';
     if ($version_a !== null) {
       $a_sequence = $version_a->getSequence();
     }
 
     $name =
       $version_b->getFragment()->getName().'.'.
       $a_sequence.'.'.
       $version_b->getSequence().'.patch';
 
     $return = $version_b->getURI();
     if ($request->getExists('return')) {
       $return = $request->getStr('return');
     }
 
     $result = PhabricatorFile::buildFromFileDataOrHash(
       $patch,
       array(
         'name' => $name,
         'mime-type' => 'text/plain',
         'ttl' => time() + 60 * 60 * 24,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $result->attachToObject($version_b->getFragmentPHID());
     unset($unguarded);
 
     return id(new AphrontRedirectResponse())
       ->setURI($result->getDownloadURI($return));
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentPolicyController.php b/src/applications/phragment/controller/PhragmentPolicyController.php
index 700c3edb5..edcde8099 100644
--- a/src/applications/phragment/controller/PhragmentPolicyController.php
+++ b/src/applications/phragment/controller/PhragmentPolicyController.php
@@ -1,109 +1,106 @@
 <?php
 
 final class PhragmentPolicyController extends PhragmentController {
 
-  private $dblob;
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
-
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = idx($parents, count($parents) - 1, null);
 
     $error_view = null;
 
     if ($request->isFormPost()) {
       $errors = array();
 
       $v_view_policy = $request->getStr('viewPolicy');
       $v_edit_policy = $request->getStr('editPolicy');
       $v_replace_children = $request->getBool('replacePoliciesOnChildren');
 
       $fragment->setViewPolicy($v_view_policy);
       $fragment->setEditPolicy($v_edit_policy);
 
       $fragment->save();
 
       if ($v_replace_children) {
         // If you can edit a fragment, you can forcibly set the policies
         // on child fragments, regardless of whether you can see them or not.
         $children = id(new PhragmentFragmentQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withLeadingPath($fragment->getPath().'/')
           ->execute();
         $children_phids = mpull($children, 'getPHID');
 
         $fragment->openTransaction();
           foreach ($children as $child) {
             $child->setViewPolicy($v_view_policy);
             $child->setEditPolicy($v_edit_policy);
             $child->save();
           }
         $fragment->saveTransaction();
       }
 
       return id(new AphrontRedirectResponse())
         ->setURI('/phragment/browse/'.$fragment->getPath());
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($fragment)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($fragment)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($fragment)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicies($policies))
       ->appendChild(
         id(new AphrontFormCheckboxControl())
           ->addCheckbox(
             'replacePoliciesOnChildren',
             'true',
             pht(
               'Replace policies on child fragments with '.
               'the policies above.')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save Fragment Policies'))
           ->addCancelButton(
             $this->getApplicationURI('browse/'.$fragment->getPath())));
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('Edit Fragment Policies'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Edit Fragment Policies: %s', $fragment->getPath()))
       ->setValidationException(null)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-      ),
-      array(
-        'title' => pht('Edit Fragment Policies'),
-      ));
+    $title = pht('Edit Fragment Policies');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentRevertController.php b/src/applications/phragment/controller/PhragmentRevertController.php
index 92da1f27a..e9d56eb11 100644
--- a/src/applications/phragment/controller/PhragmentRevertController.php
+++ b/src/applications/phragment/controller/PhragmentRevertController.php
@@ -1,87 +1,79 @@
 <?php
 
 final class PhragmentRevertController extends PhragmentController {
 
-  private $dblob;
-  private $id;
-
-  public function willProcessRequest(array $data) {
-    $this->dblob = $data['dblob'];
-    $this->id = $data['id'];
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $id = $request->getURIData('id');
+    $dblob = $request->getURIData('dblob');
 
     $fragment = id(new PhragmentFragmentQuery())
       ->setViewer($viewer)
-      ->withPaths(array($this->dblob))
+      ->withPaths(array($dblob))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if ($fragment === null) {
       return new Aphront404Response();
     }
 
     $version = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
       ->withFragmentPHIDs(array($fragment->getPHID()))
-      ->withIDs(array($this->id))
+      ->withIDs(array($id))
       ->executeOne();
     if ($version === null) {
       return new Aphront404Response();
     }
 
     if ($request->isDialogFormPost()) {
       $file_phid = $version->getFilePHID();
 
       $file = null;
       if ($file_phid !== null) {
         $file = id(new PhabricatorFileQuery())
           ->setViewer($viewer)
           ->withPHIDs(array($file_phid))
           ->executeOne();
         if ($file === null) {
           throw new Exception(
             pht('The file associated with this version was not found.'));
         }
       }
 
       if ($file === null) {
         $fragment->deleteFile($viewer);
       } else {
         $fragment->updateFromFile($viewer, $file);
       }
 
       return id(new AphrontRedirectResponse())
-        ->setURI($this->getApplicationURI('/history/'.$this->dblob));
+        ->setURI($this->getApplicationURI('/history/'.$dblob));
     }
 
     return $this->createDialog($fragment, $version);
   }
 
   public function createDialog(
     PhragmentFragment $fragment,
     PhragmentFragmentVersion $version) {
 
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+    $viewer = $this->getViewer();
 
     $dialog = id(new AphrontDialogView())
       ->setTitle(pht('Really revert this fragment?'))
-      ->setUser($request->getUser())
+      ->setUser($this->getViewer())
       ->addSubmitButton(pht('Revert'))
       ->addCancelButton(pht('Cancel'))
       ->appendParagraph(pht(
         'Reverting this fragment to version %d will create a new version of '.
         'the fragment. It will not delete any version history.',
         $version->getSequence(),
         $version->getSequence()));
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
index e13581928..c32be32cd 100644
--- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
@@ -1,173 +1,170 @@
 <?php
 
 final class PhragmentSnapshotCreateController extends PhragmentController {
 
-  private $dblob;
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
-
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = nonempty(last($parents), null);
     if ($fragment === null) {
       return new Aphront404Response();
     }
 
     PhabricatorPolicyFilter::requireCapability(
       $viewer,
       $fragment,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $children = id(new PhragmentFragmentQuery())
       ->setViewer($viewer)
       ->needLatestVersion(true)
       ->withLeadingPath($fragment->getPath().'/')
       ->execute();
 
     $errors = array();
     if ($request->isFormPost()) {
 
       $v_name = $request->getStr('name');
       if (strlen($v_name) === 0) {
         $errors[] = pht('You must specify a name.');
       }
       if (strpos($v_name, '/') !== false) {
         $errors[] = pht('Snapshot names can not contain "/".');
       }
 
       if (!count($errors)) {
         $snapshot = null;
 
         try {
           // Create the snapshot.
           $snapshot = id(new PhragmentSnapshot())
             ->setPrimaryFragmentPHID($fragment->getPHID())
             ->setName($v_name)
             ->save();
         } catch (AphrontDuplicateKeyQueryException $e) {
           $errors[] = pht('A snapshot with this name already exists.');
         }
 
         if (!count($errors)) {
           // Add the primary fragment.
           id(new PhragmentSnapshotChild())
             ->setSnapshotPHID($snapshot->getPHID())
             ->setFragmentPHID($fragment->getPHID())
             ->setFragmentVersionPHID($fragment->getLatestVersionPHID())
             ->save();
 
           // Add all of the child fragments.
           foreach ($children as $child) {
             id(new PhragmentSnapshotChild())
               ->setSnapshotPHID($snapshot->getPHID())
               ->setFragmentPHID($child->getPHID())
               ->setFragmentVersionPHID($child->getLatestVersionPHID())
               ->save();
           }
 
           return id(new AphrontRedirectResponse())
             ->setURI('/phragment/snapshot/view/'.$snapshot->getID());
         }
       }
     }
 
     $fragment_sequence = '-';
     if ($fragment->getLatestVersion() !== null) {
       $fragment_sequence = $fragment->getLatestVersion()->getSequence();
     }
 
     $rows = array();
     $rows[] = phutil_tag(
       'tr',
       array(),
       array(
         phutil_tag('th', array(), pht('Fragment')),
         phutil_tag('th', array(), pht('Version')),
       ));
     $rows[] = phutil_tag(
       'tr',
       array(),
       array(
         phutil_tag('td', array(), $fragment->getPath()),
         phutil_tag('td', array(), $fragment_sequence),
       ));
     foreach ($children as $child) {
       $sequence = '-';
       if ($child->getLatestVersion() !== null) {
         $sequence = $child->getLatestVersion()->getSequence();
       }
       $rows[] = phutil_tag(
         'tr',
         array(),
         array(
           phutil_tag('td', array(), $child->getPath()),
           phutil_tag('td', array(), $sequence),
         ));
     }
 
     $table = phutil_tag(
       'table',
       array('class' => 'remarkup-table'),
       $rows);
 
     $container = phutil_tag(
       'div',
       array('class' => 'phabricator-remarkup'),
       array(
         phutil_tag(
           'p',
           array(),
           pht(
             'The snapshot will contain the following fragments at '.
             'the specified versions: ')),
         $table,
       ));
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Fragment Path'))
           ->setDisabled(true)
           ->setValue('/'.$fragment->getPath()))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Snapshot Name'))
           ->setName('name'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Create Snapshot'))
           ->addCancelButton(
             $this->getApplicationURI('browse/'.$fragment->getPath())))
       ->appendChild(
         id(new PHUIFormDividerControl()))
       ->appendInstructions($container);
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('Create Snapshot'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Create Snapshot of %s', $fragment->getName()))
       ->setFormErrors($errors)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-      ),
-      array(
-        'title' => pht('Create Fragment'),
-      ));
+    $title = pht('Create Snapshot');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
index 715280a2c..8f112585d 100644
--- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
@@ -1,54 +1,47 @@
 <?php
 
 final class PhragmentSnapshotDeleteController extends PhragmentController {
 
-  private $id;
-
-  public function willProcessRequest(array $data) {
-    $this->id = $data['id'];
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $id = $request->getURIData('id');
 
     $snapshot = id(new PhragmentSnapshotQuery())
       ->setViewer($viewer)
       ->requireCapabilities(array(
         PhabricatorPolicyCapability::CAN_VIEW,
         PhabricatorPolicyCapability::CAN_EDIT,
       ))
-      ->withIDs(array($this->id))
+      ->withIDs(array($id))
       ->executeOne();
     if ($snapshot === null) {
       return new Aphront404Response();
     }
 
     if ($request->isDialogFormPost()) {
       $fragment_uri = $snapshot->getPrimaryFragment()->getURI();
 
       $snapshot->delete();
 
       return id(new AphrontRedirectResponse())
         ->setURI($fragment_uri);
     }
 
     return $this->createDialog();
   }
 
   public function createDialog() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+    $viewer = $this->getViewer();
 
     $dialog = id(new AphrontDialogView())
       ->setTitle(pht('Really delete this snapshot?'))
-      ->setUser($request->getUser())
+      ->setUser($this->getViewer())
       ->addSubmitButton(pht('Delete'))
       ->addCancelButton(pht('Cancel'))
       ->appendParagraph(pht(
         'Deleting this snapshot is a permanent operation. You can not '.
         'recover the state of the snapshot.'));
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
index 18d7df6a5..981d13974 100644
--- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
@@ -1,195 +1,188 @@
 <?php
 
 final class PhragmentSnapshotPromoteController extends PhragmentController {
 
-  private $dblob;
-  private $id;
   private $targetSnapshot;
   private $targetFragment;
   private $snapshots;
   private $options;
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', null);
-    $this->id = idx($data, 'id', null);
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $id = $request->getURIData('id');
+    $dblob = $request->getURIData('dblob');
 
     // When the user is promoting a snapshot to the latest version, the
     // identifier is a fragment path.
-    if ($this->dblob !== null) {
+    if ($dblob !== null) {
       $this->targetFragment = id(new PhragmentFragmentQuery())
         ->setViewer($viewer)
         ->requireCapabilities(array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
-        ->withPaths(array($this->dblob))
+        ->withPaths(array($dblob))
         ->executeOne();
       if ($this->targetFragment === null) {
         return new Aphront404Response();
       }
 
       $this->snapshots = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID()))
         ->execute();
     }
 
     // When the user is promoting a snapshot to another snapshot, the
     // identifier is another snapshot ID.
-    if ($this->id !== null) {
+    if ($id !== null) {
       $this->targetSnapshot = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->requireCapabilities(array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
-        ->withIDs(array($this->id))
+        ->withIDs(array($id))
         ->executeOne();
       if ($this->targetSnapshot === null) {
         return new Aphront404Response();
       }
 
       $this->snapshots = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->withPrimaryFragmentPHIDs(array(
           $this->targetSnapshot->getPrimaryFragmentPHID(),
         ))
         ->execute();
     }
 
     // If there's no identifier, just 404.
     if ($this->snapshots === null) {
       return new Aphront404Response();
     }
 
     // Work out what options the user has.
     $this->options = mpull(
       $this->snapshots,
       'getName',
       'getID');
-    if ($this->id !== null) {
-      unset($this->options[$this->id]);
+    if ($id !== null) {
+      unset($this->options[$id]);
     }
 
     // If there's no options, show a dialog telling the
     // user there are no snapshots to promote.
     if (count($this->options) === 0) {
       return id(new AphrontDialogResponse())->setDialog(
         id(new AphrontDialogView())
           ->setTitle(pht('No snapshots to promote'))
           ->appendParagraph(pht(
             'There are no snapshots available to promote.'))
-          ->setUser($request->getUser())
+          ->setUser($this->getViewer())
           ->addCancelButton(pht('Cancel')));
     }
 
     // Handle snapshot promotion.
     if ($request->isDialogFormPost()) {
       $snapshot = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->withIDs(array($request->getStr('snapshot')))
         ->executeOne();
       if ($snapshot === null) {
         return new Aphront404Response();
       }
 
       $snapshot->openTransaction();
         // Delete all existing child entries.
         $children = id(new PhragmentSnapshotChildQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withSnapshotPHIDs(array($snapshot->getPHID()))
           ->execute();
         foreach ($children as $child) {
           $child->delete();
         }
 
-        if ($this->id === null) {
+        if ($id === null) {
           // The user is promoting the snapshot to the latest version.
           $children = id(new PhragmentFragmentQuery())
             ->setViewer($viewer)
             ->needLatestVersion(true)
             ->withLeadingPath($this->targetFragment->getPath().'/')
             ->execute();
 
           // Add the primary fragment.
           id(new PhragmentSnapshotChild())
             ->setSnapshotPHID($snapshot->getPHID())
             ->setFragmentPHID($this->targetFragment->getPHID())
             ->setFragmentVersionPHID(
               $this->targetFragment->getLatestVersionPHID())
             ->save();
 
           // Add all of the child fragments.
           foreach ($children as $child) {
             id(new PhragmentSnapshotChild())
               ->setSnapshotPHID($snapshot->getPHID())
               ->setFragmentPHID($child->getPHID())
               ->setFragmentVersionPHID($child->getLatestVersionPHID())
               ->save();
           }
         } else {
           // The user is promoting the snapshot to another snapshot. We just
           // copy the other snapshot's child entries and change the snapshot
           // PHID to make it identical.
           $children = id(new PhragmentSnapshotChildQuery())
             ->setViewer($viewer)
             ->withSnapshotPHIDs(array($this->targetSnapshot->getPHID()))
             ->execute();
           foreach ($children as $child) {
             id(new PhragmentSnapshotChild())
               ->setSnapshotPHID($snapshot->getPHID())
               ->setFragmentPHID($child->getFragmentPHID())
               ->setFragmentVersionPHID($child->getFragmentVersionPHID())
               ->save();
           }
         }
       $snapshot->saveTransaction();
 
-      if ($this->id === null) {
+      if ($id === null) {
         return id(new AphrontRedirectResponse())
           ->setURI($this->targetFragment->getURI());
       } else {
         return id(new AphrontRedirectResponse())
           ->setURI($this->targetSnapshot->getURI());
       }
     }
 
-    return $this->createDialog();
+    return $this->createDialog($id);
   }
 
-  public function createDialog() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function createDialog($id) {
+    $viewer = $this->getViewer();
 
     $dialog = id(new AphrontDialogView())
       ->setTitle(pht('Promote which snapshot?'))
-      ->setUser($request->getUser())
+      ->setUser($this->getViewer())
       ->addSubmitButton(pht('Promote'))
       ->addCancelButton(pht('Cancel'));
 
-    if ($this->id === null) {
+    if ($id === null) {
       // The user is promoting a snapshot to the latest version.
       $dialog->appendParagraph(pht(
         'Select the snapshot you want to promote to the latest version:'));
     } else {
       // The user is promoting a snapshot to another snapshot.
       $dialog->appendParagraph(pht(
         "Select the snapshot you want to promote to '%s':",
         $this->targetSnapshot->getName()));
     }
 
     $dialog->appendChild(
       id(new AphrontFormSelectControl())
         ->setUser($viewer)
         ->setName('snapshot')
         ->setOptions($this->options));
 
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentSnapshotViewController.php b/src/applications/phragment/controller/PhragmentSnapshotViewController.php
index fe499fffa..052b5036f 100644
--- a/src/applications/phragment/controller/PhragmentSnapshotViewController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotViewController.php
@@ -1,149 +1,145 @@
 <?php
 
 final class PhragmentSnapshotViewController extends PhragmentController {
 
-  private $id;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->id = idx($data, 'id', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $id = $request->getURIData('id');
 
     $snapshot = id(new PhragmentSnapshotQuery())
       ->setViewer($viewer)
-      ->withIDs(array($this->id))
+      ->withIDs(array($id))
       ->executeOne();
     if ($snapshot === null) {
       return new Aphront404Response();
     }
 
     $box = $this->createSnapshotView($snapshot);
 
     $fragment = id(new PhragmentFragmentQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($snapshot->getPrimaryFragmentPHID()))
       ->executeOne();
     if ($fragment === null) {
       return new Aphront404Response();
     }
 
     $parents = $this->loadParentFragments($fragment->getPath());
     if ($parents === null) {
       return new Aphront404Response();
     }
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('"%s" Snapshot', $snapshot->getName()));
 
     $children = id(new PhragmentSnapshotChildQuery())
       ->setViewer($viewer)
       ->needFragments(true)
       ->needFragmentVersions(true)
       ->withSnapshotPHIDs(array($snapshot->getPHID()))
       ->execute();
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     foreach ($children as $child) {
       $item = id(new PHUIObjectItemView())
         ->setHeader($child->getFragment()->getPath());
 
       if ($child->getFragmentVersion() !== null) {
         $item
           ->setHref($child->getFragmentVersion()->getURI())
           ->addAttribute(pht(
             'Version %s',
             $child->getFragmentVersion()->getSequence()));
       } else {
         $item
           ->setHref($child->getFragment()->getURI())
           ->addAttribute(pht('Directory'));
       }
 
       $list->addItem($item);
     }
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-        $list,
-      ),
-      array(
-        'title' => pht('View Snapshot'),
-      ));
+    $title = pht('View Snapshot');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+      $list,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   protected function createSnapshotView($snapshot) {
     if ($snapshot === null) {
       return null;
     }
 
     $viewer = $this->getRequest()->getUser();
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('"%s" Snapshot', $snapshot->getName()))
       ->setPolicyObject($snapshot)
       ->setUser($viewer);
 
     $zip_uri = $this->getApplicationURI(
       'zip@'.$snapshot->getName().
       '/'.$snapshot->getPrimaryFragment()->getPath());
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $snapshot,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $actions = id(new PhabricatorActionListView())
       ->setUser($viewer)
       ->setObject($snapshot);
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Download Snapshot as ZIP'))
         ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
         ->setDisabled(!$this->isCorrectlyConfigured())
         ->setIcon('fa-floppy-o'));
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Delete Snapshot'))
         ->setHref($this->getApplicationURI(
           'snapshot/delete/'.$snapshot->getID().'/'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true)
         ->setIcon('fa-times'));
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Promote Another Snapshot to Here'))
         ->setHref($this->getApplicationURI(
           'snapshot/promote/'.$snapshot->getID().'/'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true)
         ->setIcon('fa-arrow-up'));
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($snapshot)
       ->setActionList($actions);
 
     $properties->addProperty(
       pht('Name'),
       $snapshot->getName());
     $properties->addProperty(
       pht('Fragment'),
       $viewer->renderHandle($snapshot->getPrimaryFragmentPHID()));
 
     return id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
   }
 }
diff --git a/src/applications/phragment/controller/PhragmentUpdateController.php b/src/applications/phragment/controller/PhragmentUpdateController.php
index 1b8d10e91..7fb91ccd4 100644
--- a/src/applications/phragment/controller/PhragmentUpdateController.php
+++ b/src/applications/phragment/controller/PhragmentUpdateController.php
@@ -1,83 +1,80 @@
 <?php
 
 final class PhragmentUpdateController extends PhragmentController {
 
-  private $dblob;
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
-
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = idx($parents, count($parents) - 1, null);
 
     $error_view = null;
 
     if ($request->isFormPost()) {
       $errors = array();
 
       $v_fileid = $request->getInt('fileID');
 
       $file = id(new PhabricatorFile())->load($v_fileid);
       if ($file === null) {
         $errors[] = pht('The specified file doesn\'t exist.');
       }
 
       if (!count($errors)) {
         // If the file is a ZIP archive (has application/zip mimetype)
         // then we extract the zip and apply versions for each of the
         // individual fragments, creating and deleting files as needed.
         if ($file->getMimeType() === 'application/zip') {
           $fragment->updateFromZIP($viewer, $file);
         } else {
           $fragment->updateFromFile($viewer, $file);
         }
 
         return id(new AphrontRedirectResponse())
           ->setURI('/phragment/browse/'.$fragment->getPath());
       } else {
         $error_view = id(new PHUIInfoView())
           ->setErrors($errors)
           ->setTitle(pht('Errors while updating fragment'));
       }
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('File ID'))
           ->setName('fileID'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Update Fragment'))
           ->addCancelButton(
             $this->getApplicationURI('browse/'.$fragment->getPath())));
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('Update Fragment'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Update Fragment: %s', $fragment->getPath()))
       ->setValidationException(null)
       ->setForm($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-      ),
-      array(
-        'title' => pht('Update Fragment'),
-      ));
+    $title = pht('Update Fragment');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentVersionController.php b/src/applications/phragment/controller/PhragmentVersionController.php
index 29b920a4e..2b1ae2bdf 100644
--- a/src/applications/phragment/controller/PhragmentVersionController.php
+++ b/src/applications/phragment/controller/PhragmentVersionController.php
@@ -1,131 +1,125 @@
 <?php
 
 final class PhragmentVersionController extends PhragmentController {
 
-  private $id;
-
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->id = idx($data, 'id', 0);
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $id = $request->getURIData('id');
 
     $version = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
-      ->withIDs(array($this->id))
+      ->withIDs(array($id))
       ->executeOne();
     if ($version === null) {
       return new Aphront404Response();
     }
 
     $parents = $this->loadParentFragments($version->getFragment()->getPath());
     if ($parents === null) {
       return new Aphront404Response();
     }
     $current = idx($parents, count($parents) - 1, null);
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('View Version %d', $version->getSequence()));
 
     $file = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($version->getFilePHID()))
       ->executeOne();
     if ($file !== null) {
       $file_uri = $file->getDownloadURI();
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht(
         '%s at version %d',
         $version->getFragment()->getName(),
         $version->getSequence()))
       ->setPolicyObject($version)
       ->setUser($viewer);
 
     $actions = id(new PhabricatorActionListView())
       ->setUser($viewer)
       ->setObject($version);
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Download Version'))
         ->setDisabled($file === null || !$this->isCorrectlyConfigured())
         ->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
         ->setIcon('fa-download'));
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($version)
       ->setActionList($actions);
     $properties->addProperty(
       pht('File'),
       $viewer->renderHandle($version->getFilePHID()));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $this->renderConfigurationWarningIfRequired(),
-        $box,
-        $this->renderPreviousVersionList($version),
-      ),
-      array(
-        'title' => pht('View Version'),
-      ));
+    $title = pht('View Version');
+
+    $view = array(
+      $this->renderConfigurationWarningIfRequired(),
+      $box,
+      $this->renderPreviousVersionList($version),
+    );
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function renderPreviousVersionList(
     PhragmentFragmentVersion $version) {
-
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+    $viewer = $this->getViewer();
 
     $previous_versions = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
       ->withFragmentPHIDs(array($version->getFragmentPHID()))
       ->withSequenceBefore($version->getSequence())
       ->execute();
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     foreach ($previous_versions as $previous_version) {
       $item = id(new PHUIObjectItemView());
       $item->setHeader(pht('Version %s', $previous_version->getSequence()));
       $item->setHref($previous_version->getURI());
       $item->addAttribute(phabricator_datetime(
         $previous_version->getDateCreated(),
         $viewer));
       $patch_uri = $this->getApplicationURI(
         'patch/'.$previous_version->getID().'/'.$version->getID());
       $item->addAction(id(new PHUIListItemView())
         ->setIcon('fa-file-o')
         ->setName(pht('Get Patch'))
         ->setHref($this->isCorrectlyConfigured() ? $patch_uri : null)
         ->setDisabled(!$this->isCorrectlyConfigured()));
       $list->addItem($item);
     }
 
     $item = id(new PHUIObjectItemView());
     $item->setHeader(pht('Prior to Version 0'));
     $item->addAttribute(pht('Prior to any content (empty file)'));
     $item->addAction(id(new PHUIListItemView())
       ->setIcon('fa-file-o')
       ->setName(pht('Get Patch'))
       ->setHref($this->getApplicationURI(
         'patch/x/'.$version->getID())));
     $list->addItem($item);
 
     return $list;
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php
index 0de9f4d34..75d464d9c 100644
--- a/src/applications/phragment/controller/PhragmentZIPController.php
+++ b/src/applications/phragment/controller/PhragmentZIPController.php
@@ -1,156 +1,153 @@
 <?php
 
 final class PhragmentZIPController extends PhragmentController {
 
-  private $dblob;
-  private $snapshot;
-
   private $snapshotCache;
 
   public function shouldAllowPublic() {
     return true;
   }
 
-  public function willProcessRequest(array $data) {
-    $this->dblob = idx($data, 'dblob', '');
-    $this->snapshot = idx($data, 'snapshot', null);
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $viewer = $request->getUser();
+  public function handleRequest(AphrontRequest $request) {
+    $viewer = $request->getViewer();
+    $dblob = $request->getURIData('dblob');
+    $snapshot = $request->getURIData('snapshot');
 
-    $parents = $this->loadParentFragments($this->dblob);
+    $parents = $this->loadParentFragments($dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = idx($parents, count($parents) - 1, null);
 
-    if ($this->snapshot !== null) {
+    if ($snapshot !== null) {
       $snapshot = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
-        ->withNames(array($this->snapshot))
+        ->withNames(array($snapshot))
         ->executeOne();
       if ($snapshot === null) {
         return new Aphront404Response();
       }
 
       $cache = id(new PhragmentSnapshotChildQuery())
         ->setViewer($viewer)
         ->needFragmentVersions(true)
         ->withSnapshotPHIDs(array($snapshot->getPHID()))
         ->execute();
       $this->snapshotCache = mpull(
         $cache,
         'getFragmentVersion',
         'getFragmentPHID');
     }
 
     $temp = new TempFile();
 
     $zip = null;
     try {
       $zip = new ZipArchive();
     } catch (Exception $e) {
       $dialog = new AphrontDialogView();
       $dialog->setUser($viewer);
 
       $inst = pht(
         'This system does not have the ZIP PHP extension installed. This '.
         'is required to download ZIPs from Phragment.');
 
       $dialog->setTitle(pht('ZIP Extension Not Installed'));
       $dialog->appendParagraph($inst);
 
-      $dialog->addCancelButton('/phragment/browse/'.$this->dblob);
+      $dialog->addCancelButton('/phragment/browse/'.$dblob);
       return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     if (!$zip->open((string)$temp, ZipArchive::CREATE)) {
       throw new Exception(pht('Unable to create ZIP archive!'));
     }
 
-    $mappings = $this->getFragmentMappings($fragment, $fragment->getPath());
+    $mappings = $this->getFragmentMappings(
+      $fragment, $fragment->getPath(), $snapshot);
 
     $phids = array();
     foreach ($mappings as $path => $file_phid) {
       $phids[] = $file_phid;
     }
 
     $files = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs($phids)
       ->execute();
     $files = mpull($files, null, 'getPHID');
     foreach ($mappings as $path => $file_phid) {
       if (!isset($files[$file_phid])) {
         // The path is most likely pointing to a deleted fragment, which
         // hence no longer has a file associated with it.
         unset($mappings[$path]);
         continue;
       }
       $mappings[$path] = $files[$file_phid];
     }
 
     foreach ($mappings as $path => $file) {
       if ($file !== null) {
         $zip->addFromString($path, $file->loadFileData());
       }
     }
     $zip->close();
 
     $zip_name = $fragment->getName();
     if (substr($zip_name, -4) !== '.zip') {
       $zip_name .= '.zip';
     }
 
     $data = Filesystem::readFile((string)$temp);
     $file = PhabricatorFile::buildFromFileDataOrHash(
       $data,
       array(
         'name' => $zip_name,
         'ttl' => time() + 60 * 60 * 24,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file->attachToObject($fragment->getPHID());
     unset($unguarded);
 
     $return = $fragment->getURI();
     if ($request->getExists('return')) {
       $return = $request->getStr('return');
     }
 
     return id(new AphrontRedirectResponse())
       ->setIsExternal(true)
       ->setURI($file->getDownloadURI($return));
   }
 
   /**
    * Returns a list of mappings like array('some/path.txt' => 'file PHID');
    */
-  private function getFragmentMappings(PhragmentFragment $current, $base_path) {
+  private function getFragmentMappings(
+    PhragmentFragment $current,
+    $base_path,
+    $snapshot) {
     $mappings = $current->getFragmentMappings(
       $this->getRequest()->getUser(),
       $base_path);
 
     $result = array();
     foreach ($mappings as $path => $fragment) {
-      $version = $this->getVersion($fragment);
+      $version = $this->getVersion($fragment, $snapshot);
       if ($version !== null) {
         $result[$path] = $version->getFilePHID();
       }
     }
     return $result;
   }
 
-  private function getVersion($fragment) {
-    if ($this->snapshot === null) {
+  private function getVersion($fragment, $snapshot) {
+    if ($snapshot === null) {
       return $fragment->getLatestVersion();
     } else {
       return idx($this->snapshotCache, $fragment->getPHID(), null);
     }
   }
 
 }
diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php
index 11d0b40df..5b1a81f94 100644
--- a/src/applications/phriction/controller/PhrictionDiffController.php
+++ b/src/applications/phriction/controller/PhrictionDiffController.php
@@ -1,295 +1,298 @@
 <?php
 
 final class PhrictionDiffController extends PhrictionController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $document = id(new PhrictionDocumentQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needContent(true)
       ->executeOne();
     if (!$document) {
       return new Aphront404Response();
     }
 
     $current = $document->getContent();
 
     $l = $request->getInt('l');
     $r = $request->getInt('r');
 
     $ref = $request->getStr('ref');
     if ($ref) {
       list($l, $r) = explode(',', $ref);
     }
 
     $content = id(new PhrictionContent())->loadAllWhere(
       'documentID = %d AND version IN (%Ld)',
       $document->getID(),
       array($l, $r));
     $content = mpull($content, null, 'getVersion');
 
     $content_l = idx($content, $l, null);
     $content_r = idx($content, $r, null);
 
     if (!$content_l || !$content_r) {
       return new Aphront404Response();
     }
 
     $text_l = $content_l->getContent();
     $text_r = $content_r->getContent();
 
     $text_l = phutil_utf8_hard_wrap($text_l, 80);
     $text_l = implode("\n", $text_l);
     $text_r = phutil_utf8_hard_wrap($text_r, 80);
     $text_r = implode("\n", $text_r);
 
     $engine = new PhabricatorDifferenceEngine();
     $changeset = $engine->generateChangesetFromFileContent($text_l, $text_r);
 
     $changeset->setFilename($content_r->getTitle());
 
     $changeset->setOldProperties(
       array(
         'Title'   => $content_l->getTitle(),
       ));
     $changeset->setNewProperties(
       array(
         'Title'   => $content_r->getTitle(),
       ));
 
     $whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
 
     $parser = id(new DifferentialChangesetParser())
       ->setUser($viewer)
       ->setChangeset($changeset)
       ->setRenderingReference("{$l},{$r}");
 
     $parser->readParametersFromRequest($request);
     $parser->setWhitespaceMode($whitespace_mode);
 
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($viewer);
     $engine->process();
     $parser->setMarkupEngine($engine);
 
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) =
       DifferentialChangesetParser::parseRangeSpecification($spec);
 
     $parser->setRange($range_s, $range_e);
     $parser->setMask($mask);
 
     if ($request->isAjax()) {
       return id(new PhabricatorChangesetResponse())
         ->setRenderedChangeset($parser->renderChangeset());
     }
 
     $changes = id(new DifferentialChangesetListView())
       ->setUser($this->getViewer())
       ->setChangesets(array($changeset))
       ->setVisibleChangesets(array($changeset))
       ->setRenderingReferences(array("{$l},{$r}"))
       ->setRenderURI('/phriction/diff/'.$document->getID().'/')
       ->setTitle(pht('Changes'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setParser($parser);
 
     require_celerity_resource('phriction-document-css');
 
     $slug = $document->getSlug();
 
     $revert_l = $this->renderRevertButton($content_l, $current);
     $revert_r = $this->renderRevertButton($content_r, $current);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumb_views = $this->renderBreadcrumbs($slug);
     foreach ($crumb_views as $view) {
       $crumbs->addCrumb($view);
     }
 
     $crumbs->addTextCrumb(
       pht('History'),
       PhrictionDocument::getSlugURI($slug, 'history'));
 
     $title = pht('Version %s vs %s', $l, $r);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title)
-      ->setTall(true);
+      ->setHeaderIcon('fa-history');
 
     $crumbs->addTextCrumb($title, $request->getRequestURI());
 
-
     $comparison_table = $this->renderComparisonTable(
       array(
         $content_r,
         $content_l,
       ));
 
     $navigation_table = null;
     if ($l + 1 == $r) {
       $nav_l = ($l > 1);
       $nav_r = ($r != $current->getVersion());
 
       $uri = $request->getRequestURI();
 
       if ($nav_l) {
         $link_l = phutil_tag(
           'a',
           array(
             'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1),
-            'class' => 'button simple',
+            'class' => 'button grey',
           ),
           pht("\xC2\xAB Previous Change"));
       } else {
         $link_l = phutil_tag(
           'a',
           array(
             'href' => '#',
             'class' => 'button grey disabled',
           ),
           pht('Original Change'));
       }
 
       $link_r = null;
       if ($nav_r) {
         $link_r = phutil_tag(
           'a',
           array(
             'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1),
-            'class' => 'button simple',
+            'class' => 'button grey',
           ),
           pht("Next Change \xC2\xBB"));
       } else {
         $link_r = phutil_tag(
           'a',
           array(
             'href' => '#',
             'class' => 'button grey disabled',
           ),
           pht('Most Recent Change'));
       }
 
       $navigation_table = phutil_tag(
         'table',
         array('class' => 'phriction-history-nav-table'),
         phutil_tag('tr', array(), array(
           phutil_tag('td', array('class' => 'nav-prev'), $link_l),
           phutil_tag('td', array('class' => 'nav-next'), $link_r),
         )));
     }
 
-
     $output = hsprintf(
       '<div class="phriction-document-history-diff">'.
         '%s%s'.
         '<table class="phriction-revert-table">'.
           '<tr><td>%s</td><td>%s</td>'.
         '</table>'.
       '</div>',
       $comparison_table->render(),
       $navigation_table,
       $revert_l,
       $revert_r);
 
-
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
+      ->setHeaderText(pht('Edits'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($output);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $crumbs->setBorder(true);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $object_box,
         $changes,
-      ),
-      array(
-        'title'     => pht('Document History'),
       ));
 
+    return $this->newPage()
+      ->setTitle(pht('Document History'))
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function renderRevertButton(
     PhrictionContent $content,
     PhrictionContent $current) {
 
     $document_id = $content->getDocumentID();
     $version = $content->getVersion();
 
     $hidden_statuses = array(
       PhrictionChangeType::CHANGE_DELETE    => true, // Silly
       PhrictionChangeType::CHANGE_MOVE_AWAY => true, // Plain silly
       PhrictionChangeType::CHANGE_STUB      => true, // Utterly silly
     );
     if (isset($hidden_statuses[$content->getChangeType()])) {
       // Don't show an edit/revert button for changes which deleted, moved or
       // stubbed the content since it's silly.
       return null;
     }
 
     if ($content->getID() == $current->getID()) {
       return phutil_tag(
         'a',
         array(
           'href'  => '/phriction/edit/'.$document_id.'/',
-          'class' => 'button simple',
+          'class' => 'button grey',
         ),
         pht('Edit Current Version'));
     }
 
 
     return phutil_tag(
       'a',
       array(
         'href'  => '/phriction/edit/'.$document_id.'/?revert='.$version,
-        'class' => 'button simple',
+        'class' => 'button grey',
       ),
       pht('Revert to Version %s...', $version));
   }
 
   private function renderComparisonTable(array $content) {
     assert_instances_of($content, 'PhrictionContent');
 
     $viewer = $this->getViewer();
 
     $phids = mpull($content, 'getAuthorPHID');
     $handles = $this->loadViewerHandles($phids);
 
     $list = new PHUIObjectItemListView();
 
     $first = true;
     foreach ($content as $c) {
       $author = $handles[$c->getAuthorPHID()]->renderLink();
       $item = id(new PHUIObjectItemView())
         ->setHeader(pht('%s by %s, %s',
           PhrictionChangeType::getChangeTypeLabel($c->getChangeType()),
           $author,
           pht('Version %s', $c->getVersion())))
         ->addAttribute(pht('%s %s',
           phabricator_date($c->getDateCreated(), $viewer),
           phabricator_time($c->getDateCreated(), $viewer)));
 
       if ($c->getDescription()) {
         $item->addAttribute($c->getDescription());
       }
 
       if ($first == true) {
         $item->setStatusIcon('fa-file green');
         $first = false;
       } else {
         $item->setStatusIcon('fa-file red');
       }
 
       $list->addItem($item);
     }
 
     return $list;
   }
 
 }
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
index 85bfa1a66..760188898 100644
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,481 +1,479 @@
 <?php
 
 final class PhrictionDocumentController
   extends PhrictionController {
 
   private $slug;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $this->slug = $request->getURIData('slug');
 
     $slug = PhabricatorSlug::normalize($this->slug);
     if ($slug != $this->slug) {
       $uri = PhrictionDocument::getSlugURI($slug);
       // Canonicalize pages to their one true URI.
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     require_celerity_resource('phriction-document-css');
 
     $document = id(new PhrictionDocumentQuery())
       ->setViewer($viewer)
       ->withSlugs(array($slug))
       ->executeOne();
 
     $version_note = null;
     $core_content = '';
     $move_notice = '';
     $properties = null;
     $content = null;
     $toc = null;
 
     if (!$document) {
 
       $document = PhrictionDocument::initializeNewDocument($viewer, $slug);
 
       if ($slug == '/') {
         $title = pht('Welcome to Phriction');
         $subtitle = pht('Phriction is a simple and easy to use wiki for '.
           'keeping track of documents and their changes.');
         $page_title = pht('Welcome');
         $create_text = pht('Edit this Document');
 
       } else {
         $title = pht('No Document Here');
         $subtitle = pht('There is no document here, but you may create it.');
         $page_title = pht('Page Not Found');
         $create_text = pht('Create this Document');
       }
 
       $create_uri = '/phriction/edit/?slug='.$slug;
       $create_button = id(new PHUIButtonView())
         ->setTag('a')
         ->setText($create_text)
         ->setHref($create_uri)
         ->setColor(PHUIButtonView::GREEN);
 
       $core_content = id(new PHUIBigInfoView())
         ->setIcon('fa-book')
         ->setTitle($title)
         ->setDescription($subtitle)
         ->addAction($create_button);
 
     } else {
       $version = $request->getInt('v');
 
       if ($version) {
         $content = id(new PhrictionContent())->loadOneWhere(
           'documentID = %d AND version = %d',
           $document->getID(),
           $version);
         if (!$content) {
           return new Aphront404Response();
         }
 
         if ($content->getID() != $document->getContentID()) {
           $vdate = phabricator_datetime($content->getDateCreated(), $viewer);
           $version_note = new PHUIInfoView();
           $version_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
           $version_note->appendChild(
             pht('You are viewing an older version of this document, as it '.
             'appeared on %s.', $vdate));
         }
       } else {
         $content = id(new PhrictionContent())->load($document->getContentID());
       }
       $page_title = $content->getTitle();
       $properties = $this
         ->buildPropertyListView($document, $content, $slug);
 
       $doc_status = $document->getStatus();
       $current_status = $content->getChangeType();
       if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
         $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
 
         $core_content = $content->renderContent($viewer);
         $toc = $this->getToc($content);
 
       } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
         $notice = new PHUIInfoView();
         $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
         $notice->setTitle(pht('Document Deleted'));
         $notice->appendChild(
           pht('This document has been deleted. You can edit it to put new '.
           'content here, or use history to revert to an earlier version.'));
         $core_content = $notice->render();
       } else if ($current_status == PhrictionChangeType::CHANGE_STUB) {
         $notice = new PHUIInfoView();
         $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
         $notice->setTitle(pht('Empty Document'));
         $notice->appendChild(
           pht('This document is empty. You can edit it to put some proper '.
           'content here.'));
         $core_content = $notice->render();
       } else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) {
         $new_doc_id = $content->getChangeRef();
         $slug_uri = null;
 
         // If the new document exists and the viewer can see it, provide a link
         // to it. Otherwise, render a generic message.
         $new_docs = id(new PhrictionDocumentQuery())
           ->setViewer($viewer)
           ->withIDs(array($new_doc_id))
           ->execute();
         if ($new_docs) {
           $new_doc = head($new_docs);
           $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
         }
 
         $notice = id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
 
         if ($slug_uri) {
           $notice->appendChild(
             phutil_tag(
               'p',
               array(),
               pht(
                 'This document has been moved to %s. You can edit it to put '.
                 'new content here, or use history to revert to an earlier '.
                 'version.',
                 phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
         } else {
           $notice->appendChild(
             phutil_tag(
               'p',
               array(),
               pht(
                 'This document has been moved. You can edit it to put new '.
                 'content here, or use history to revert to an earlier '.
                 'version.')));
         }
 
         $core_content = $notice->render();
       } else {
         throw new Exception(pht("Unknown document status '%s'!", $doc_status));
       }
 
       $move_notice = null;
       if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
         $from_doc_id = $content->getChangeRef();
 
         $slug_uri = null;
 
         // If the old document exists and is visible, provide a link to it.
         $from_docs = id(new PhrictionDocumentQuery())
           ->setViewer($viewer)
           ->withIDs(array($from_doc_id))
           ->execute();
         if ($from_docs) {
           $from_doc = head($from_docs);
           $slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
         }
 
         $move_notice = id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
 
         if ($slug_uri) {
           $move_notice->appendChild(
             pht(
               'This document was moved from %s.',
               phutil_tag('a', array('href' => $slug_uri), $slug_uri)));
         } else {
           // Render this for consistency, even though it's a bit silly.
           $move_notice->appendChild(
             pht('This document was moved from elsewhere.'));
         }
       }
     }
 
     $children = $this->renderDocumentChildren($slug);
 
     $actions = $this->buildActionView($viewer, $document);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumb_views = $this->renderBreadcrumbs($slug);
     foreach ($crumb_views as $view) {
       $crumbs->addCrumb($view);
     }
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setPolicyObject($document)
       ->setHeader($page_title)
       ->setActionList($actions);
 
     if ($content) {
       $header->setEpoch($content->getDateCreated());
     }
 
     $prop_list = null;
     if ($properties) {
       $prop_list = new PHUIPropertyGroupView();
       $prop_list->addPropertyList($properties);
     }
     $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list);
 
     $page_content = id(new PHUIDocumentViewPro())
       ->setHeader($header)
       ->setToc($toc)
       ->appendChild(
         array(
           $version_note,
           $move_notice,
           $core_content,
         ));
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs->render(),
+    return $this->newPage()
+      ->setTitle($page_title)
+      ->setCrumbs($crumbs)
+      ->setPageObjectPHIDs(array($document->getPHID()))
+      ->appendChild(array(
         $page_content,
         $prop_list,
         $children,
-      ),
-      array(
-        'pageObjects' => array($document->getPHID()),
-        'title' => $page_title,
       ));
 
   }
 
   private function buildPropertyListView(
     PhrictionDocument $document,
     PhrictionContent $content,
     $slug) {
 
     $viewer = $this->getRequest()->getUser();
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($document);
 
     $view->addProperty(
       pht('Last Author'),
       $viewer->renderHandle($content->getAuthorPHID()));
 
     return $view;
   }
 
   private function buildActionView(
     PhabricatorUser $viewer,
     PhrictionDocument $document) {
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $document,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $slug = PhabricatorSlug::normalize($this->slug);
 
     $action_view = id(new PhabricatorActionListView())
       ->setUser($viewer)
       ->setObject($document);
 
     if (!$document->getID()) {
       return $action_view->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Create This Document'))
           ->setIcon('fa-plus-square')
           ->setHref('/phriction/edit/?slug='.$slug));
     }
 
     $action_view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Document'))
         ->setDisabled(!$can_edit)
         ->setIcon('fa-pencil')
         ->setHref('/phriction/edit/'.$document->getID().'/'));
 
     if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
       $action_view->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Move Document'))
           ->setDisabled(!$can_edit)
           ->setIcon('fa-arrows')
           ->setHref('/phriction/move/'.$document->getID().'/')
           ->setWorkflow(true));
 
       $action_view->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Delete Document'))
           ->setDisabled(!$can_edit)
           ->setIcon('fa-times')
           ->setHref('/phriction/delete/'.$document->getID().'/')
           ->setWorkflow(true));
     }
 
     return
       $action_view->addAction(
         id(new PhabricatorActionView())
         ->setName(pht('View History'))
         ->setIcon('fa-list')
         ->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
   }
 
   private function renderDocumentChildren($slug) {
 
     $d_child = PhabricatorSlug::getDepth($slug) + 1;
     $d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
     $limit = 250;
 
     $query = id(new PhrictionDocumentQuery())
       ->setViewer($this->getRequest()->getUser())
       ->withDepths(array($d_child, $d_grandchild))
       ->withSlugPrefix($slug == '/' ? '' : $slug)
       ->withStatuses(array(
         PhrictionDocumentStatus::STATUS_EXISTS,
         PhrictionDocumentStatus::STATUS_STUB,
       ))
       ->setLimit($limit)
       ->setOrder(PhrictionDocumentQuery::ORDER_HIERARCHY)
       ->needContent(true);
 
     $children = $query->execute();
     if (!$children) {
       return;
     }
 
     // We're going to render in one of three modes to try to accommodate
     // different information scales:
     //
     //  - If we found fewer than $limit rows, we know we have all the children
     //    and grandchildren and there aren't all that many. We can just render
     //    everything.
     //  - If we found $limit rows but the results included some grandchildren,
     //    we just throw them out and render only the children, as we know we
     //    have them all.
     //  - If we found $limit rows and the results have no grandchildren, we
     //    have a ton of children. Render them and then let the user know that
     //    this is not an exhaustive list.
 
     if (count($children) == $limit) {
       $more_children = true;
       foreach ($children as $child) {
         if ($child->getDepth() == $d_grandchild) {
           $more_children = false;
         }
       }
       $show_grandchildren = false;
     } else {
       $show_grandchildren = true;
       $more_children = false;
     }
 
     $children_dicts = array();
     $grandchildren_dicts = array();
     foreach ($children as $key => $child) {
       $child_dict = array(
         'slug' => $child->getSlug(),
         'depth' => $child->getDepth(),
         'title' => $child->getContent()->getTitle(),
       );
       if ($child->getDepth() == $d_child) {
         $children_dicts[] = $child_dict;
         continue;
       } else {
         unset($children[$key]);
         if ($show_grandchildren) {
           $ancestors = PhabricatorSlug::getAncestry($child->getSlug());
           $grandchildren_dicts[end($ancestors)][] = $child_dict;
         }
       }
     }
 
     // Fill in any missing children.
     $known_slugs = mpull($children, null, 'getSlug');
     foreach ($grandchildren_dicts as $slug => $ignored) {
       if (empty($known_slugs[$slug])) {
         $children_dicts[] = array(
           'slug'    => $slug,
           'depth'   => $d_child,
           'title'   => PhabricatorSlug::getDefaultTitle($slug),
           'empty'   => true,
         );
       }
     }
 
     $children_dicts = isort($children_dicts, 'title');
 
     $list = array();
     foreach ($children_dicts as $child) {
       $list[] = hsprintf('<li class="remarkup-list-item">');
       $list[] = $this->renderChildDocumentLink($child);
       $grand = idx($grandchildren_dicts, $child['slug'], array());
       if ($grand) {
         $list[] = hsprintf('<ul class="remarkup-list">');
         foreach ($grand as $grandchild) {
           $list[] = hsprintf('<li class="remarkup-list-item">');
           $list[] = $this->renderChildDocumentLink($grandchild);
           $list[] = hsprintf('</li>');
         }
         $list[] = hsprintf('</ul>');
       }
       $list[] = hsprintf('</li>');
     }
     if ($more_children) {
       $list[] = phutil_tag(
         'li',
         array(
           'class' => 'remarkup-list-item',
         ),
         pht('More...'));
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Document Hierarchy'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild(phutil_tag(
         'div',
         array(
           'class' => 'phabricator-remarkup mlt mlb',
         ),
         phutil_tag(
           'ul',
           array(
             'class' => 'remarkup-list',
           ),
           $list)));
 
      return phutil_tag_div('phui-document-view-pro-box', $box);
   }
 
   private function renderChildDocumentLink(array $info) {
     $title = nonempty($info['title'], pht('(Untitled Document)'));
     $item = phutil_tag(
       'a',
       array(
         'href' => PhrictionDocument::getSlugURI($info['slug']),
       ),
       $title);
 
     if (isset($info['empty'])) {
       $item = phutil_tag('em', array(), $item);
     }
 
     return $item;
   }
 
   protected function getDocumentSlug() {
     return $this->slug;
   }
 
   protected function getToc(PhrictionContent $content) {
     $toc = $content->getRenderedTableOfContents();
     if ($toc) {
       $toc = phutil_tag_div('phui-document-toc-content', array(
         phutil_tag_div(
           'phui-document-toc-header',
           pht('Contents')),
         $toc,
       ));
     }
     return $toc;
   }
 
 }
diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php
index 466d721a0..fd88bf940 100644
--- a/src/applications/phriction/controller/PhrictionEditController.php
+++ b/src/applications/phriction/controller/PhrictionEditController.php
@@ -1,298 +1,309 @@
 <?php
 
 final class PhrictionEditController
   extends PhrictionController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $current_version = null;
     if ($id) {
       $document = id(new PhrictionDocumentQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->needContent(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$document) {
         return new Aphront404Response();
       }
 
       $current_version = $document->getContent()->getVersion();
 
       $revert = $request->getInt('revert');
       if ($revert) {
         $content = id(new PhrictionContent())->loadOneWhere(
           'documentID = %d AND version = %d',
           $document->getID(),
           $revert);
         if (!$content) {
           return new Aphront404Response();
         }
       } else {
         $content = $document->getContent();
       }
 
     } else {
       $slug = $request->getStr('slug');
       $slug = PhabricatorSlug::normalize($slug);
       if (!$slug) {
         return new Aphront404Response();
       }
 
       $document = id(new PhrictionDocumentQuery())
         ->setViewer($viewer)
         ->withSlugs(array($slug))
         ->needContent(true)
         ->executeOne();
 
       if ($document) {
         $content = $document->getContent();
         $current_version = $content->getVersion();
       } else {
         $document = PhrictionDocument::initializeNewDocument($viewer, $slug);
         $content = $document->getContent();
       }
     }
 
     if ($request->getBool('nodraft')) {
       $draft = null;
       $draft_key = null;
     } else {
       if ($document->getPHID()) {
         $draft_key = $document->getPHID().':'.$content->getVersion();
       } else {
         $draft_key = 'phriction:'.$content->getSlug();
       }
       $draft = id(new PhabricatorDraft())->loadOneWhere(
         'authorPHID = %s AND draftKey = %s',
         $viewer->getPHID(),
         $draft_key);
     }
 
     if ($draft &&
       strlen($draft->getDraft()) &&
       ($draft->getDraft() != $content->getContent())) {
       $content_text = $draft->getDraft();
 
       $discard = phutil_tag(
         'a',
         array(
           'href' => $request->getRequestURI()->alter('nodraft', true),
         ),
         pht('discard this draft'));
 
       $draft_note = new PHUIInfoView();
       $draft_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
       $draft_note->setTitle(pht('Recovered Draft'));
       $draft_note->appendChild(
         hsprintf(
           '<p>%s</p>',
           pht(
             'Showing a saved draft of your edits, you can %s.',
             $discard)));
     } else {
       $content_text = $content->getContent();
       $draft_note = null;
     }
 
     require_celerity_resource('phriction-document-css');
 
     $e_title = true;
     $e_content = true;
     $validation_exception = null;
     $notes = null;
     $title = $content->getTitle();
     $overwrite = false;
     $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID(
       $document->getPHID());
 
     if ($request->isFormPost()) {
 
       $title = $request->getStr('title');
       $content_text = $request->getStr('content');
       $notes = $request->getStr('description');
       $current_version = $request->getInt('contentVersion');
       $v_view = $request->getStr('viewPolicy');
       $v_edit = $request->getStr('editPolicy');
       $v_cc   = $request->getArr('cc');
 
       $xactions = array();
       $xactions[] = id(new PhrictionTransaction())
         ->setTransactionType(PhrictionTransaction::TYPE_TITLE)
         ->setNewValue($title);
       $xactions[] = id(new PhrictionTransaction())
         ->setTransactionType(PhrictionTransaction::TYPE_CONTENT)
         ->setNewValue($content_text);
       $xactions[] = id(new PhrictionTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($v_view);
       $xactions[] = id(new PhrictionTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
         ->setNewValue($v_edit);
       $xactions[] = id(new PhrictionTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
         ->setNewValue(array('=' => $v_cc));
 
       $editor = id(new PhrictionTransactionEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true)
         ->setDescription($notes)
         ->setProcessContentVersionError(!$request->getBool('overwrite'))
         ->setContentVersion($current_version);
 
       try {
         $editor->applyTransactions($document, $xactions);
 
         if ($draft) {
           $draft->delete();
         }
 
         $uri = PhrictionDocument::getSlugURI($document->getSlug());
         return id(new AphrontRedirectResponse())->setURI($uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
         $e_title = nonempty(
           $ex->getShortMessage(PhrictionTransaction::TYPE_TITLE),
           true);
         $e_content = nonempty(
           $ex->getShortMessage(PhrictionTransaction::TYPE_CONTENT),
           true);
 
         // if we're not supposed to process the content version error, then
         // overwrite that content...!
         if (!$editor->getProcessContentVersionError()) {
           $overwrite = true;
         }
 
         $document->setViewPolicy($v_view);
         $document->setEditPolicy($v_edit);
       }
     }
 
     if ($document->getID()) {
-      $panel_header = pht('Edit Phriction Document');
+      $panel_header = pht('Edit Document: %s', $content->getTitle());
       $page_title = pht('Edit Document');
+      $header_icon = 'fa-pencil';
       if ($overwrite) {
         $submit_button = pht('Overwrite Changes');
       } else {
         $submit_button = pht('Save Changes');
       }
     } else {
       $panel_header = pht('Create New Phriction Document');
       $submit_button = pht('Create Document');
       $page_title = pht('Create Document');
+      $header_icon = 'fa-plus-square';
     }
 
     $uri = $document->getSlug();
     $uri = PhrictionDocument::getSlugURI($uri);
     $uri = PhabricatorEnv::getProductionURI($uri);
 
     $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug());
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($document)
       ->execute();
     $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
     $edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('slug', $document->getSlug())
       ->addHiddenInput('nodraft', $request->getBool('nodraft'))
       ->addHiddenInput('contentVersion', $current_version)
       ->addHiddenInput('overwrite', $overwrite)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Title'))
           ->setValue($title)
           ->setError($e_title)
           ->setName('title'))
       ->appendChild(
         id(new AphrontFormStaticControl())
           ->setLabel(pht('URI'))
           ->setValue($uri))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setLabel(pht('Content'))
           ->setValue($content_text)
           ->setError($e_content)
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
           ->setName('content')
           ->setID('document-textarea')
           ->setUser($viewer))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Subscribers'))
           ->setName('cc')
           ->setValue($v_cc)
           ->setUser($viewer)
           ->setDatasource(new PhabricatorMetaMTAMailableDatasource()))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('viewPolicy')
           ->setPolicyObject($document)
           ->setCapability($view_capability)
           ->setPolicies($policies)
           ->setCaption(
             $document->describeAutomaticCapability($view_capability)))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setName('editPolicy')
           ->setPolicyObject($document)
           ->setCapability($edit_capability)
           ->setPolicies($policies)
           ->setCaption(
             $document->describeAutomaticCapability($edit_capability)))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Edit Notes'))
           ->setValue($notes)
           ->setError(null)
           ->setName('description'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($cancel_uri)
           ->setValue($submit_button));
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($panel_header)
+      ->setHeaderText(pht('Document'))
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $preview = id(new PHUIRemarkupPreviewPanel())
       ->setHeader($content->getTitle())
       ->setPreviewURI('/phriction/preview/')
       ->setControlID('document-textarea')
       ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT);
 
     $crumbs = $this->buildApplicationCrumbs();
     if ($document->getID()) {
       $crumbs->addTextCrumb(
         $content->getTitle(),
         PhrictionDocument::getSlugURI($document->getSlug()));
       $crumbs->addTextCrumb(pht('Edit'));
     } else {
       $crumbs->addTextCrumb(pht('Create'));
     }
+    $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($panel_header)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $draft_note,
         $form_box,
         $preview,
-      ),
-      array(
-        'title'   => $page_title,
       ));
+
+    return $this->newPage()
+      ->setTitle($page_title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php
index 5ce6c7225..9b0d3188a 100644
--- a/src/applications/phriction/controller/PhrictionHistoryController.php
+++ b/src/applications/phriction/controller/PhrictionHistoryController.php
@@ -1,172 +1,179 @@
 <?php
 
 final class PhrictionHistoryController
   extends PhrictionController {
 
-  private $slug;
-
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
-    $this->slug = $request->getURIData('slug');
+    $slug = $request->getURIData('slug');
 
     $document = id(new PhrictionDocumentQuery())
       ->setViewer($viewer)
-      ->withSlugs(array(PhabricatorSlug::normalize($this->slug)))
+      ->withSlugs(array(PhabricatorSlug::normalize($slug)))
       ->needContent(true)
       ->executeOne();
     if (!$document) {
       return new Aphront404Response();
     }
 
     $current = $document->getContent();
 
     $pager = new PHUIPagerView();
     $pager->setOffset($request->getInt('page'));
     $pager->setURI($request->getRequestURI(), 'page');
 
     $history = id(new PhrictionContent())->loadAllWhere(
       'documentID = %d ORDER BY version DESC LIMIT %d, %d',
       $document->getID(),
       $pager->getOffset(),
       $pager->getPageSize() + 1);
     $history = $pager->sliceResults($history);
 
     $author_phids = mpull($history, 'getAuthorPHID');
     $handles = $this->loadViewerHandles($author_phids);
 
     $list = new PHUIObjectItemListView();
     $list->setFlush(true);
 
     foreach ($history as $content) {
 
       $author = $handles[$content->getAuthorPHID()]->renderLink();
       $slug_uri = PhrictionDocument::getSlugURI($document->getSlug());
       $version = $content->getVersion();
 
       $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/');
 
       $vs_previous = null;
       if ($content->getVersion() != 1) {
         $vs_previous = $diff_uri
           ->alter('l', $content->getVersion() - 1)
           ->alter('r', $content->getVersion());
       }
 
       $vs_head = null;
       if ($content->getID() != $document->getContentID()) {
         $vs_head = $diff_uri
           ->alter('l', $content->getVersion())
           ->alter('r', $current->getVersion());
       }
 
       $change_type = PhrictionChangeType::getChangeTypeLabel(
         $content->getChangeType());
       switch ($content->getChangeType()) {
         case PhrictionChangeType::CHANGE_DELETE:
           $color = 'red';
           break;
         case PhrictionChangeType::CHANGE_EDIT:
           $color = 'lightbluetext';
           break;
         case PhrictionChangeType::CHANGE_MOVE_HERE:
             $color = 'yellow';
           break;
         case PhrictionChangeType::CHANGE_MOVE_AWAY:
             $color = 'orange';
           break;
         case PhrictionChangeType::CHANGE_STUB:
           $color = 'green';
           break;
         default:
           throw new Exception(pht('Unknown change type!'));
           break;
       }
 
       $item = id(new PHUIObjectItemView())
         ->setHeader(pht('%s by %s', $change_type, $author))
         ->setStatusIcon('fa-file '.$color)
         ->addAttribute(
           phutil_tag(
             'a',
             array(
               'href' => $slug_uri.'?v='.$version,
             ),
             pht('Version %s', $version)))
         ->addAttribute(pht('%s %s',
           phabricator_date($content->getDateCreated(), $viewer),
           phabricator_time($content->getDateCreated(), $viewer)));
 
       if ($content->getDescription()) {
         $item->addAttribute($content->getDescription());
       }
 
       if ($vs_previous) {
         $item->addIcon(
           'fa-reply',
           pht('Show Change'),
           array(
             'href' => $vs_previous,
           ));
       } else {
         $item->addIcon(
           'fa-reply grey',
           phutil_tag('em', array(), pht('No previous change')));
       }
 
       if ($vs_head) {
         $item->addIcon(
           'fa-reply-all',
           pht('Show Later Changes'),
           array(
             'href' => $vs_head,
           ));
       } else {
         $item->addIcon(
           'fa-reply-all grey',
           phutil_tag('em', array(), pht('No later changes')));
       }
 
       $list->addItem($item);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumb_views = $this->renderBreadcrumbs($document->getSlug());
     foreach ($crumb_views as $view) {
       $crumbs->addCrumb($view);
     }
     $crumbs->addTextCrumb(
       pht('History'),
       PhrictionDocument::getSlugURI($document->getSlug(), 'history'));
+    $crumbs->setBorder(true);
 
     $header = new PHUIHeaderView();
     $header->setHeader(phutil_tag(
         'a',
         array('href' => PhrictionDocument::getSlugURI($document->getSlug())),
         head($history)->getTitle()));
     $header->setSubheader(pht('Document History'));
 
     $obj_box = id(new PHUIObjectBoxView())
-      ->setHeader($header)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setObjectList($list);
 
     $pager = id(new PHUIBoxView())
       ->addClass('ml')
       ->appendChild($pager);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Document History: %s', head($history)->getTitle()))
+      ->setHeaderIcon('fa-history');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $obj_box,
         $pager,
-      ),
-      array(
-        'title'     => pht('Document History'),
       ));
 
+    $title = pht('Document History');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 }
diff --git a/src/applications/phriction/controller/PhrictionNewController.php b/src/applications/phriction/controller/PhrictionNewController.php
index c5659be6c..bcb8e317a 100644
--- a/src/applications/phriction/controller/PhrictionNewController.php
+++ b/src/applications/phriction/controller/PhrictionNewController.php
@@ -1,63 +1,59 @@
 <?php
 
 final class PhrictionNewController extends PhrictionController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $slug = PhabricatorSlug::normalize($request->getStr('slug'));
 
     if ($request->isFormPost()) {
       $document = id(new PhrictionDocumentQuery())
         ->setViewer($viewer)
         ->withSlugs(array($slug))
         ->executeOne();
       $prompt = $request->getStr('prompt', 'no');
       $document_exists = $document && $document->getStatus() ==
         PhrictionDocumentStatus::STATUS_EXISTS;
 
       if ($document_exists && $prompt == 'no') {
-        $dialog = new AphrontDialogView();
-        $dialog->setSubmitURI('/phriction/new/')
+        return $this->newDialog()
+          ->setSubmitURI('/phriction/new/')
           ->setTitle(pht('Edit Existing Document?'))
           ->setUser($viewer)
           ->appendChild(pht(
             'The document %s already exists. Do you want to edit it instead?',
             phutil_tag('tt', array(), $slug)))
           ->addHiddenInput('slug', $slug)
           ->addHiddenInput('prompt', 'yes')
           ->addCancelButton('/w/')
           ->addSubmitButton(pht('Edit Document'));
-
-        return id(new AphrontDialogResponse())->setDialog($dialog);
       }
 
       $uri  = '/phriction/edit/?slug='.$slug;
       return id(new AphrontRedirectResponse())
         ->setURI($uri);
     }
 
     if ($slug == '/') {
       $slug = '';
     }
 
     $view = id(new PHUIFormLayoutView())
       ->appendChild(id(new AphrontFormTextControl())
                        ->setLabel('/w/')
                        ->setValue($slug)
                        ->setName('slug'));
 
-    $dialog = id(new AphrontDialogView())
-      ->setUser($viewer)
+    return $this->newDialog()
       ->setTitle(pht('New Document'))
       ->setSubmitURI('/phriction/new/')
       ->appendChild(phutil_tag('p',
         array(),
         pht('Create a new document at')))
       ->appendChild($view)
       ->addSubmitButton(pht('Create'))
       ->addCancelButton('/w/');
 
-    return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php
index 4136c855d..3af53802e 100644
--- a/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php
+++ b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php
@@ -1,256 +1,266 @@
 <?php
 
 final class PhabricatorPhurlURLEditController
   extends PhabricatorPhurlController {
 
   public function handleRequest(AphrontRequest $request) {
     $id = $request->getURIData('id');
     $is_create = !$id;
 
     $viewer = $this->getViewer();
     $user_phid = $viewer->getPHID();
     $error_long_url = true;
     $error_alias = null;
     $validation_exception = null;
 
     $next_workflow = $request->getStr('next');
     $uri_query = $request->getStr('query');
 
     if ($is_create) {
       $this->requireApplicationCapability(
         PhabricatorPhurlURLCreateCapability::CAPABILITY);
 
       $url = PhabricatorPhurlURL::initializeNewPhurlURL(
         $viewer);
       $submit_label = pht('Create');
       $page_title = pht('Shorten URL');
+      $header_icon = 'fa-plus-square';
       $subscribers = array();
       $cancel_uri = $this->getApplicationURI();
     } else {
       $url = id(new PhabricatorPhurlURLQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
 
       if (!$url) {
         return new Aphront404Response();
       }
 
       $submit_label = pht('Update');
-      $page_title   = pht('Update URL');
+      $page_title   = pht('Edit URL: %s', $url->getName());
+      $header_icon = 'fa-pencil';
 
       $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
         $url->getPHID());
 
       $cancel_uri = '/U'.$url->getID();
     }
 
     if ($is_create) {
       $projects = array();
     } else {
       $projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $url->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $projects = array_reverse($projects);
     }
 
     $name = $url->getName();
     $long_url = $url->getLongURL();
     $alias = $url->getAlias();
     $description = $url->getDescription();
     $edit_policy = $url->getEditPolicy();
     $view_policy = $url->getViewPolicy();
     $space = $url->getSpacePHID();
 
     if ($request->isFormPost()) {
       $xactions = array();
       $name = $request->getStr('name');
       $long_url = $request->getStr('longURL');
       $alias = $request->getStr('alias');
       $projects = $request->getArr('projects');
       $description = $request->getStr('description');
       $subscribers = $request->getArr('subscribers');
       $edit_policy = $request->getStr('editPolicy');
       $view_policy = $request->getStr('viewPolicy');
       $space = $request->getStr('spacePHID');
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(
           PhabricatorPhurlURLTransaction::TYPE_NAME)
         ->setNewValue($name);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(
           PhabricatorPhurlURLTransaction::TYPE_URL)
         ->setNewValue($long_url);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(
           PhabricatorPhurlURLTransaction::TYPE_ALIAS)
         ->setNewValue($alias);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(
           PhabricatorTransactions::TYPE_SUBSCRIBERS)
         ->setNewValue(array('=' => array_fuse($subscribers)));
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(
           PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION)
         ->setNewValue($description);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
         ->setNewValue($view_policy);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
         ->setNewValue($edit_policy);
 
       $xactions[] = id(new PhabricatorPhurlURLTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
         ->setNewValue($space);
 
       $editor = id(new PhabricatorPhurlURLEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
         $xactions[] = id(new PhabricatorPhurlURLTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
           ->setMetadataValue('edge:type', $proj_edge_type)
           ->setNewValue(array('=' => array_fuse($projects)));
 
         $xactions = $editor->applyTransactions($url, $xactions);
         return id(new AphrontRedirectResponse())
           ->setURI($url->getURI());
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
         $error_long_url = $ex->getShortMessage(
           PhabricatorPhurlURLTransaction::TYPE_URL);
         $error_alias = $ex->getShortMessage(
           PhabricatorPhurlURLTransaction::TYPE_ALIAS);
       }
     }
 
     $current_policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($url)
       ->execute();
 
     $name = id(new AphrontFormTextControl())
       ->setLabel(pht('Name'))
       ->setName('name')
       ->setValue($name);
 
     $long_url = id(new AphrontFormTextControl())
       ->setLabel(pht('URL'))
       ->setName('longURL')
       ->setValue($long_url)
       ->setError($error_long_url);
 
     $alias = id(new AphrontFormTextControl())
       ->setLabel(pht('Alias'))
       ->setName('alias')
       ->setValue($alias)
       ->setError($error_alias);
 
     $projects = id(new AphrontFormTokenizerControl())
       ->setLabel(pht('Projects'))
       ->setName('projects')
       ->setValue($projects)
       ->setUser($viewer)
       ->setDatasource(new PhabricatorProjectDatasource());
 
     $description = id(new PhabricatorRemarkupControl())
       ->setLabel(pht('Description'))
       ->setName('description')
       ->setValue($description)
       ->setUser($viewer);
 
     $view_policies = id(new AphrontFormPolicyControl())
       ->setUser($viewer)
       ->setValue($view_policy)
       ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
       ->setPolicyObject($url)
       ->setPolicies($current_policies)
       ->setSpacePHID($space)
       ->setName('viewPolicy');
     $edit_policies = id(new AphrontFormPolicyControl())
       ->setUser($viewer)
       ->setValue($edit_policy)
       ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
       ->setPolicyObject($url)
       ->setPolicies($current_policies)
       ->setName('editPolicy');
 
     $subscribers = id(new AphrontFormTokenizerControl())
       ->setLabel(pht('Subscribers'))
       ->setName('subscribers')
       ->setValue($subscribers)
       ->setUser($viewer)
       ->setDatasource(new PhabricatorMetaMTAMailableDatasource());
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild($name)
       ->appendChild($long_url)
       ->appendChild($alias)
       ->appendControl($view_policies)
       ->appendControl($edit_policies)
       ->appendControl($subscribers)
       ->appendChild($projects)
       ->appendChild($description);
 
 
     if ($request->isAjax()) {
       return $this->newDialog()
         ->setTitle($page_title)
         ->setWidth(AphrontDialogView::WIDTH_FULL)
         ->appendForm($form)
         ->addCancelButton($cancel_uri)
         ->addSubmitButton($submit_label);
     }
 
     $submit = id(new AphrontFormSubmitControl())
       ->addCancelButton($cancel_uri)
       ->setValue($submit_label);
 
     $form->appendChild($submit);
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($page_title)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
 
     if (!$is_create) {
       $crumbs->addTextCrumb($url->getMonogram(), $url->getURI());
     } else {
       $crumbs->addTextCrumb(pht('Create URL'));
     }
 
     $crumbs->addTextCrumb($page_title);
+    $crumbs->setBorder(true);
 
     $object_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($page_title)
+      ->setHeaderText(pht('URL'))
       ->setValidationException($validation_exception)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($page_title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $object_box,
-      ),
-      array(
-        'title' => $page_title,
       ));
+
+    return $this->newPage()
+      ->setTitle($page_title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 }
diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php
index 946e35c85..4703adade 100644
--- a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php
+++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php
@@ -1,153 +1,153 @@
 <?php
 
 final class PhabricatorPhurlURLViewController
   extends PhabricatorPhurlController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $timeline = null;
 
     $url = id(new PhabricatorPhurlURLQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$url) {
       return new Aphront404Response();
     }
 
     $title = $url->getMonogram();
     $page_title = $title.' '.$url->getName();
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     $timeline = $this->buildTransactionTimeline(
       $url,
       new PhabricatorPhurlURLTransactionQuery());
 
     $header = $this->buildHeaderView($url);
     $curtain = $this->buildCurtain($url);
     $details = $this->buildPropertySectionView($url);
 
     $url_error = id(new PHUIInfoView())
       ->setErrors(array(pht('This URL is invalid due to a bad protocol.')))
       ->setIsHidden($url->isValid());
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     $add_comment_header = $is_serious
       ? pht('Add Comment')
       : pht('More Cowbell');
     $draft = PhabricatorDraft::newFromUserAndKey($viewer, $url->getPHID());
     $comment_uri = $this->getApplicationURI(
       '/url/comment/'.$url->getID().'/');
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($url->getPHID())
       ->setDraft($draft)
       ->setHeaderText($add_comment_header)
       ->setAction($comment_uri)
       ->setSubmitButtonName(pht('Add Comment'));
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn(array(
         $url_error,
         $details,
         $timeline,
         $add_comment_form,
       ));
 
     return $this->newPage()
       ->setTitle($page_title)
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs(array($url->getPHID()))
       ->appendChild(
         array(
           $view,
       ));
 
   }
 
   private function buildHeaderView(PhabricatorPhurlURL $url) {
     $viewer = $this->getViewer();
     $icon = 'fa-check';
     $color = 'bluegrey';
     $status = pht('Active');
     $id = $url->getID();
 
     $visit = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Visit URL'))
       ->setIcon('fa-external-link')
       ->setHref("u/{$id}")
       ->setDisabled(!$url->isValid());
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($url->getDisplayName())
       ->setStatus($icon, $color, $status)
       ->setPolicyObject($url)
       ->setHeaderIcon('fa-compress')
       ->addActionLink($visit);
 
     return $header;
   }
 
   private function buildCurtain(PhabricatorPhurlURL $url) {
     $viewer = $this->getViewer();
     $id = $url->getID();
 
     $curtain = $this->newCurtainView($url);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $url,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain
       ->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Edit'))
           ->setIcon('fa-pencil')
           ->setHref($this->getApplicationURI("url/edit/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(!$can_edit));
 
     return $curtain;
   }
 
   private function buildPropertySectionView(PhabricatorPhurlURL $url) {
     $viewer = $this->getViewer();
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $properties->addProperty(
       pht('Original URL'),
       $url->getLongURL());
 
     $properties->addProperty(
       pht('Alias'),
       $url->getAlias());
 
     $description = $url->getDescription();
     if (strlen($description)) {
       $description = new PHUIRemarkupView($viewer, $description);
       $properties->addSectionHeader(pht('Description'));
       $properties->addTextContent($description);
     }
 
     return id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($properties);
   }
 
 }
diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php
index a1105c6c8..529c39632 100644
--- a/src/applications/ponder/controller/PonderQuestionViewController.php
+++ b/src/applications/ponder/controller/PonderQuestionViewController.php
@@ -1,276 +1,276 @@
 <?php
 
 final class PonderQuestionViewController extends PonderController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $question = id(new PonderQuestionQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->needAnswers(true)
       ->needProjectPHIDs(true)
       ->executeOne();
     if (!$question) {
       return new Aphront404Response();
     }
 
     $answers = $this->buildAnswers($question);
 
     $answer_add_panel = id(new PonderAddAnswerView())
       ->setQuestion($question)
       ->setUser($viewer)
       ->setActionURI('/ponder/answer/add/');
 
     $header = new PHUIHeaderView();
     $header->setHeader($question->getTitle());
     $header->setUser($viewer);
     $header->setPolicyObject($question);
     $header->setHeaderIcon('fa-university');
 
     if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
       $header->setStatus('fa-square-o', 'bluegrey', pht('Open'));
     } else {
       $text = PonderQuestionStatus::getQuestionStatusFullName(
         $question->getStatus());
       $icon = PonderQuestionStatus::getQuestionStatusIcon(
         $question->getStatus());
       $header->setStatus($icon, 'dark', $text);
     }
 
     $curtain = $this->buildCurtain($question);
     $details = $this->buildPropertySectionView($question);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $question,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $content_id = celerity_generate_unique_node_id();
     $timeline = $this->buildTransactionTimeline(
       $question,
       id(new PonderQuestionTransactionQuery())
       ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
     $xactions = $timeline->getTransactions();
 
     $add_comment = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($question->getPHID())
       ->setShowPreview(false)
       ->setAction($this->getApplicationURI("/question/comment/{$id}/"))
       ->setSubmitButtonName(pht('Comment'));
 
     $add_comment = phutil_tag_div(
       'ponder-question-add-comment-view', $add_comment);
 
     $comment_view = phutil_tag(
       'div',
       array(
         'id' => $content_id,
         'style' => 'display: none;',
       ),
       array(
         $timeline,
         $add_comment,
       ));
 
     $footer = id(new PonderFooterView())
       ->setContentID($content_id)
       ->setCount(count($xactions));
 
     $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
     $crumbs->addTextCrumb('Q'.$id, '/Q'.$id);
     $crumbs->setBorder(true);
 
     $subheader = $this->buildSubheaderView($question);
 
     $answer_wiki = null;
     if ($question->getAnswerWiki()) {
       $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki());
       $answer_wiki = id(new PHUIObjectBoxView())
         ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
         ->setHeaderText(pht('ANSWER SUMMARY'))
         ->appendChild($wiki)
         ->addClass('ponder-answer-wiki');
     }
 
     require_celerity_resource('ponder-view-css');
 
     $ponder_content = phutil_tag(
       'div',
       array(
         'class'  => 'ponder-question-content',
       ),
       array(
         $answer_wiki,
         $footer,
         $comment_view,
         $answers,
         $answer_add_panel,
       ));
 
     $ponder_view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setSubheader($subheader)
       ->setCurtain($curtain)
       ->setMainColumn($ponder_content)
-      ->addPropertySection(pht('DETAILS'), $details)
+      ->addPropertySection(pht('Details'), $details)
       ->addClass('ponder-question-view');
 
     $page_objects = array_merge(
       array($question->getPHID()),
       mpull($question->getAnswers(), 'getPHID'));
 
     return $this->newPage()
       ->setTitle('Q'.$question->getID().' '.$question->getTitle())
       ->setCrumbs($crumbs)
       ->setPageObjectPHIDs($page_objects)
       ->appendChild($ponder_view);
   }
 
   private function buildCurtain(PonderQuestion $question) {
     $viewer = $this->getViewer();
     $id = $question->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $question,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($question);
 
     if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
       $name = pht('Close Question');
       $icon = 'fa-check-square-o';
     } else {
       $name = pht('Reopen Question');
       $icon = 'fa-square-o';
     }
 
     $curtain->addAction(
       id(new PhabricatorActionView())
       ->setIcon('fa-pencil')
       ->setName(pht('Edit Question'))
       ->setHref($this->getApplicationURI("/question/edit/{$id}/"))
       ->setDisabled(!$can_edit)
       ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName($name)
         ->setIcon($icon)
         ->setWorkflow(true)
         ->setDisabled(!$can_edit)
         ->setHref($this->getApplicationURI("/question/status/{$id}/")));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-list')
         ->setName(pht('View History'))
         ->setHref($this->getApplicationURI("/question/history/{$id}/")));
 
     return $curtain;
   }
 
   private function buildSubheaderView(
     PonderQuestion $question) {
     $viewer = $this->getViewer();
 
     $asker = $viewer->renderHandle($question->getAuthorPHID())->render();
     $date = phabricator_datetime($question->getDateCreated(), $viewer);
     $asker = phutil_tag('strong', array(), $asker);
 
     $author = id(new PhabricatorPeopleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($question->getAuthorPHID()))
       ->needProfileImage(true)
       ->executeOne();
 
     $image_uri = $author->getProfileImageURI();
     $image_href = '/p/'.$author->getUsername();
 
     $content = pht('Asked by %s on %s.', $asker, $date);
 
     return id(new PHUIHeadThingView())
       ->setImage($image_uri)
       ->setImageHref($image_href)
       ->setContent($content);
   }
 
   private function buildPropertySectionView(
     PonderQuestion $question) {
     $viewer = $this->getViewer();
 
     $question_details = PhabricatorMarkupEngine::renderOneObject(
       $question,
       $question->getMarkupField(),
       $viewer);
 
     if (!$question_details) {
       $question_details = phutil_tag(
         'em',
         array(),
         pht('No further details for this question.'));
     }
 
     $question_details = phutil_tag_div(
       'phabricator-remarkup ml', $question_details);
 
     return $question_details;
   }
 
   /**
    * This is fairly non-standard; building N timelines at once (N = number of
    * answers) is tricky business.
    *
    * TODO - re-factor this to ajax in one answer panel at a time in a more
    * standard fashion. This is necessary to scale this application.
    */
   private function buildAnswers(PonderQuestion $question) {
     $viewer = $this->getViewer();
     $answers = $question->getAnswers();
 
     if ($answers) {
       $author_phids = mpull($answers, 'getAuthorPHID');
       $handles = $this->loadViewerHandles($author_phids);
 
       $view = array();
       foreach ($answers as $answer) {
         $id = $answer->getID();
         $handle = $handles[$answer->getAuthorPHID()];
 
         $timeline = $this->buildTransactionTimeline(
           $answer,
           id(new PonderAnswerTransactionQuery())
           ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
         $xactions = $timeline->getTransactions();
 
         $view[] = id(new PonderAnswerView())
           ->setUser($viewer)
           ->setAnswer($answer)
           ->setTransactions($xactions)
           ->setTimeline($timeline)
           ->setHandle($handle);
 
       }
 
       $header = id(new PHUIHeaderView())
         ->setHeader('Answers');
 
 
       return id(new PHUIBoxView())
         ->addClass('ponder-answer-section')
         ->appendChild($header)
         ->appendChild($view);
     }
 
     return null;
 
   }
 
 }
diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
index e2b2f9688..5ead89603 100644
--- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
+++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
@@ -1,1473 +1,1468 @@
 <?php
 
 final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
   public function testViewProject() {
     $user = $this->createUser();
     $user->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $proj = $this->createProject($user);
 
     $proj = $this->refreshProject($proj, $user, true);
 
     $this->joinProject($proj, $user);
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $can_view = PhabricatorPolicyCapability::CAN_VIEW;
 
     // When the view policy is set to "users", any user can see the project.
     $this->assertTrue((bool)$this->refreshProject($proj, $user));
     $this->assertTrue((bool)$this->refreshProject($proj, $user2));
 
 
     // When the view policy is set to "no one", members can still see the
     // project.
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $this->assertTrue((bool)$this->refreshProject($proj, $user));
     $this->assertFalse((bool)$this->refreshProject($proj, $user2));
   }
 
   public function testIsViewerMemberOrWatcher() {
     $user1 = $this->createUser()
       ->save();
 
     $user2 = $this->createUser()
       ->save();
 
     $user3 = $this->createUser()
       ->save();
 
     $proj1 = $this->createProject($user1);
     $proj1 = $this->refreshProject($proj1, $user1);
 
     $this->joinProject($proj1, $user1);
     $this->joinProject($proj1, $user3);
     $this->watchProject($proj1, $user3);
 
     $proj1 = $this->refreshProject($proj1, $user1);
 
     $this->assertTrue($proj1->isUserMember($user1->getPHID()));
 
     $proj1 = $this->refreshProject($proj1, $user1, false, true);
 
     $this->assertTrue($proj1->isUserMember($user1->getPHID()));
     $this->assertFalse($proj1->isUserWatcher($user1->getPHID()));
 
     $proj1 = $this->refreshProject($proj1, $user1, true, false);
 
     $this->assertTrue($proj1->isUserMember($user1->getPHID()));
     $this->assertFalse($proj1->isUserMember($user2->getPHID()));
     $this->assertTrue($proj1->isUserMember($user3->getPHID()));
 
     $proj1 = $this->refreshProject($proj1, $user1, true, true);
 
     $this->assertTrue($proj1->isUserMember($user1->getPHID()));
     $this->assertFalse($proj1->isUserMember($user2->getPHID()));
     $this->assertTrue($proj1->isUserMember($user3->getPHID()));
 
     $this->assertFalse($proj1->isUserWatcher($user1->getPHID()));
     $this->assertFalse($proj1->isUserWatcher($user2->getPHID()));
     $this->assertTrue($proj1->isUserWatcher($user3->getPHID()));
   }
 
   public function testEditProject() {
     $user = $this->createUser();
     $user->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $proj = $this->createProject($user);
 
 
     // When edit and view policies are set to "user", anyone can edit.
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $this->assertTrue($this->attemptProjectEdit($proj, $user));
 
 
     // When edit policy is set to "no one", no one can edit.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $caught = null;
     try {
       $this->attemptProjectEdit($proj, $user);
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
   }
 
   public function testAncestorMembers() {
     $user1 = $this->createUser();
     $user1->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $parent = $this->createProject($user1);
     $child = $this->createProject($user1, $parent);
 
     $this->joinProject($child, $user1);
     $this->joinProject($child, $user2);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($user1)
       ->withPHIDs(array($child->getPHID()))
       ->needAncestorMembers(true)
       ->executeOne();
 
     $members = array_fuse($project->getParentProject()->getMemberPHIDs());
     ksort($members);
 
     $expect = array_fuse(
       array(
         $user1->getPHID(),
         $user2->getPHID(),
       ));
     ksort($expect);
 
     $this->assertEqual($expect, $members);
   }
 
   public function testAncestryQueries() {
     $user = $this->createUser();
     $user->save();
 
     $ancestor = $this->createProject($user);
     $parent = $this->createProject($user, $ancestor);
     $child = $this->createProject($user, $parent);
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withAncestorProjectPHIDs(array($ancestor->getPHID()))
       ->execute();
     $this->assertEqual(2, count($projects));
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withParentProjectPHIDs(array($ancestor->getPHID()))
       ->execute();
     $this->assertEqual(1, count($projects));
     $this->assertEqual(
       $parent->getPHID(),
       head($projects)->getPHID());
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withAncestorProjectPHIDs(array($ancestor->getPHID()))
       ->withDepthBetween(2, null)
       ->execute();
     $this->assertEqual(1, count($projects));
     $this->assertEqual(
       $child->getPHID(),
       head($projects)->getPHID());
 
     $parent2 = $this->createProject($user, $ancestor);
     $child2 = $this->createProject($user, $parent2);
     $grandchild2 = $this->createProject($user, $child2);
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withAncestorProjectPHIDs(array($ancestor->getPHID()))
       ->execute();
     $this->assertEqual(5, count($projects));
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withParentProjectPHIDs(array($ancestor->getPHID()))
       ->execute();
     $this->assertEqual(2, count($projects));
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withAncestorProjectPHIDs(array($ancestor->getPHID()))
       ->withDepthBetween(2, null)
       ->execute();
     $this->assertEqual(3, count($projects));
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withAncestorProjectPHIDs(array($ancestor->getPHID()))
       ->withDepthBetween(3, null)
       ->execute();
     $this->assertEqual(1, count($projects));
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(
         array(
           $child->getPHID(),
           $grandchild2->getPHID(),
         ))
       ->execute();
     $this->assertEqual(2, count($projects));
   }
 
   public function testMemberMaterialization() {
     $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
 
     $user = $this->createUser();
     $user->save();
 
     $parent = $this->createProject($user);
     $child = $this->createProject($user, $parent);
 
     $this->joinProject($child, $user);
 
     $parent_material = PhabricatorEdgeQuery::loadDestinationPHIDs(
       $parent->getPHID(),
       $material_type);
 
     $this->assertEqual(
       array($user->getPHID()),
       $parent_material);
   }
 
   public function testMilestones() {
     $user = $this->createUser();
     $user->save();
 
     $parent = $this->createProject($user);
 
     $m1 = $this->createProject($user, $parent, true);
     $m2 = $this->createProject($user, $parent, true);
     $m3 = $this->createProject($user, $parent, true);
 
     $this->assertEqual(1, $m1->getMilestoneNumber());
     $this->assertEqual(2, $m2->getMilestoneNumber());
     $this->assertEqual(3, $m3->getMilestoneNumber());
   }
 
   public function testMilestoneMembership() {
     $user = $this->createUser();
     $user->save();
 
     $parent = $this->createProject($user);
     $milestone = $this->createProject($user, $parent, true);
 
     $this->joinProject($parent, $user);
 
     $milestone = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($milestone->getPHID()))
       ->executeOne();
 
     $this->assertTrue($milestone->isUserMember($user->getPHID()));
 
     $milestone = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($milestone->getPHID()))
       ->needMembers(true)
       ->executeOne();
 
     $this->assertEqual(
       array($user->getPHID()),
       $milestone->getMemberPHIDs());
   }
 
   public function testSameSlugAsName() {
     // It should be OK to type the primary hashtag into "additional hashtags",
     // even if the primary hashtag doesn't exist yet because you're creating
     // or renaming the project.
 
     $user = $this->createUser();
     $user->save();
 
     $project = $this->createProject($user);
 
     // In this first case, set the name and slugs at the same time.
     $name = 'slugproject';
 
     $xactions = array();
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
       ->setNewValue($name);
     $this->applyTransactions($project, $user, $xactions);
 
     $xactions = array();
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($name));
     $this->applyTransactions($project, $user, $xactions);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($project->getPHID()))
       ->needSlugs(true)
       ->executeOne();
 
     $slugs = $project->getSlugs();
     $slugs = mpull($slugs, 'getSlug');
 
     $this->assertTrue(in_array($name, $slugs));
 
     // In this second case, set the name first and then the slugs separately.
     $name2 = 'slugproject2';
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
       ->setNewValue($name2);
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($name2));
 
     $this->applyTransactions($project, $user, $xactions);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($project->getPHID()))
       ->needSlugs(true)
       ->executeOne();
 
     $slugs = $project->getSlugs();
     $slugs = mpull($slugs, 'getSlug');
 
     $this->assertTrue(in_array($name2, $slugs));
   }
 
   public function testDuplicateSlugs() {
     // Creating a project with multiple duplicate slugs should succeed.
 
     $user = $this->createUser();
     $user->save();
 
     $project = $this->createProject($user);
 
     $input = 'duplicate';
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($input, $input));
 
     $this->applyTransactions($project, $user, $xactions);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($project->getPHID()))
       ->needSlugs(true)
       ->executeOne();
 
     $slugs = $project->getSlugs();
     $slugs = mpull($slugs, 'getSlug');
 
     $this->assertTrue(in_array($input, $slugs));
   }
 
   public function testNormalizeSlugs() {
     // When a user creates a project with slug "XxX360n0sc0perXxX", normalize
     // it before writing it.
 
     $user = $this->createUser();
     $user->save();
 
     $project = $this->createProject($user);
 
     $input = 'NoRmAlIzE';
     $expect = 'normalize';
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($input));
 
     $this->applyTransactions($project, $user, $xactions);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withPHIDs(array($project->getPHID()))
       ->needSlugs(true)
       ->executeOne();
 
     $slugs = $project->getSlugs();
     $slugs = mpull($slugs, 'getSlug');
 
     $this->assertTrue(in_array($expect, $slugs));
 
 
     // If another user tries to add the same slug in denormalized form, it
     // should be caught and fail, even though the database version of the slug
     // is normalized.
 
     $project2 = $this->createProject($user);
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($input));
 
     $caught = null;
     try {
       $this->applyTransactions($project2, $user, $xactions);
     } catch (PhabricatorApplicationTransactionValidationException $ex) {
       $caught = $ex;
     }
 
     $this->assertTrue((bool)$caught);
   }
 
   public function testProjectMembersVisibility() {
     // This is primarily testing that you can create a project and set the
     // visibility or edit policy to "Project Members" immediately.
 
     $user1 = $this->createUser();
     $user1->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $project = PhabricatorProject::initializeNewProject($user1);
     $name = pht('Test Project %d', mt_rand());
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
       ->setNewValue($name);
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
       ->setNewValue(
         id(new PhabricatorProjectMembersPolicyRule())
           ->getObjectPolicyFullKey());
 
     $edge_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
       ->setMetadataValue('edge:type', $edge_type)
       ->setNewValue(
         array(
           '=' => array($user1->getPHID() => $user1->getPHID()),
         ));
 
     $this->applyTransactions($project, $user1, $xactions);
 
     $this->assertTrue((bool)$this->refreshProject($project, $user1));
     $this->assertFalse((bool)$this->refreshProject($project, $user2));
 
     $this->leaveProject($project, $user1);
 
     $this->assertFalse((bool)$this->refreshProject($project, $user1));
   }
 
   public function testParentProject() {
     $user = $this->createUser();
     $user->save();
 
     $parent = $this->createProject($user);
     $child = $this->createProject($user, $parent);
 
     $this->assertTrue(true);
 
     $child = $this->refreshProject($child, $user);
 
     $this->assertEqual(
       $parent->getPHID(),
       $child->getParentProject()->getPHID());
 
     $this->assertEqual(1, (int)$child->getProjectDepth());
 
     $this->assertFalse(
       $child->isUserMember($user->getPHID()));
 
     $this->assertFalse(
       $child->getParentProject()->isUserMember($user->getPHID()));
 
     $this->joinProject($child, $user);
 
     $child = $this->refreshProject($child, $user);
 
     $this->assertTrue(
       $child->isUserMember($user->getPHID()));
 
     $this->assertTrue(
       $child->getParentProject()->isUserMember($user->getPHID()));
 
 
     // Test that hiding a parent hides the child.
 
     $user2 = $this->createUser();
     $user2->save();
 
     // Second user can see the project for now.
     $this->assertTrue((bool)$this->refreshProject($child, $user2));
 
     // Hide the parent.
     $this->setViewPolicy($parent, $user, $user->getPHID());
 
     // First user (who can see the parent because they are a member of
     // the child) can see the project.
     $this->assertTrue((bool)$this->refreshProject($child, $user));
 
     // Second user can not, because they can't see the parent.
     $this->assertFalse((bool)$this->refreshProject($child, $user2));
   }
 
   public function testSlugMaps() {
     // When querying by slugs, slugs should be normalized and the mapping
     // should be reported correctly.
     $user = $this->createUser();
     $user->save();
 
     $name = 'queryslugproject';
     $name2 = 'QUERYslugPROJECT';
     $slug = 'queryslugextra';
     $slug2 = 'QuErYSlUgExTrA';
 
     $project = PhabricatorProject::initializeNewProject($user);
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
       ->setNewValue($name);
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
       ->setNewValue(array($slug));
 
     $this->applyTransactions($project, $user, $xactions);
 
     $project_query = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withSlugs(array($name));
     $project_query->execute();
     $map = $project_query->getSlugMap();
 
     $this->assertEqual(
       array(
         $name => $project->getPHID(),
       ),
       ipull($map, 'projectPHID'));
 
     $project_query = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withSlugs(array($slug));
     $project_query->execute();
     $map = $project_query->getSlugMap();
 
     $this->assertEqual(
       array(
         $slug => $project->getPHID(),
       ),
       ipull($map, 'projectPHID'));
 
     $project_query = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withSlugs(array($name, $slug, $name2, $slug2));
     $project_query->execute();
     $map = $project_query->getSlugMap();
 
     $expect = array(
       $name => $project->getPHID(),
       $slug => $project->getPHID(),
       $name2 => $project->getPHID(),
       $slug2 => $project->getPHID(),
     );
 
     $actual = ipull($map, 'projectPHID');
 
     ksort($expect);
     ksort($actual);
 
     $this->assertEqual($expect, $actual);
 
     $expect = array(
       $name => $name,
       $slug => $slug,
       $name2 => $name,
       $slug2 => $slug,
     );
 
     $actual = ipull($map, 'slug');
 
     ksort($expect);
     ksort($actual);
 
     $this->assertEqual($expect, $actual);
   }
 
   public function testJoinLeaveProject() {
     $user = $this->createUser();
     $user->save();
 
     $proj = $this->createProjectWithNewAuthor();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       (bool)$proj,
       pht(
         'Assumption that projects are default visible '.
         'to any user when created.'));
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Arbitrary user not member of project.'));
 
     // Join the project.
     $this->joinProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join works.'));
 
 
     // Join the project again.
     $this->joinProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Joining an already-joined project is a no-op.'));
 
 
     // Leave the project.
     $this->leaveProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leave works.'));
 
 
     // Leave the project again.
     $this->leaveProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leaving an already-left project is a no-op.'));
 
 
     // If a user can't edit or join a project, joining fails.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $caught = null;
     try {
       $this->joinProject($proj, $user);
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($ex instanceof Exception);
 
 
     // If a user can edit a project, they can join.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->joinProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join allowed with edit permission.'));
     $this->leaveProject($proj, $user);
 
 
     // If a user can join a project, they can join, even if they can't edit.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->joinProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join allowed with join permission.'));
 
 
     // A user can leave a project even if they can't edit it or join.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->leaveProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leave allowed without any permission.'));
   }
 
 
   public function testComplexConstraints() {
     $user = $this->createUser();
     $user->save();
 
     $engineering = $this->createProject($user);
     $engineering_scan = $this->createProject($user, $engineering);
     $engineering_warp = $this->createProject($user, $engineering);
 
     $exploration = $this->createProject($user);
     $exploration_diplomacy = $this->createProject($user, $exploration);
 
     $task_engineering = $this->newTask(
       $user,
       array($engineering),
       pht('Engineering Only'));
 
     $task_exploration = $this->newTask(
       $user,
       array($exploration),
       pht('Exploration Only'));
 
     $task_warp_explore = $this->newTask(
       $user,
       array($engineering_warp, $exploration),
       pht('Warp to New Planet'));
 
     $task_diplomacy_scan = $this->newTask(
       $user,
       array($engineering_scan, $exploration_diplomacy),
       pht('Scan Diplomat'));
 
     $task_diplomacy = $this->newTask(
       $user,
       array($exploration_diplomacy),
       pht('Diplomatic Meeting'));
 
     $task_warp_scan = $this->newTask(
       $user,
       array($engineering_scan, $engineering_warp),
       pht('Scan Warp Drives'));
 
     $this->assertQueryByProjects(
       $user,
       array(
         $task_engineering,
         $task_warp_explore,
         $task_diplomacy_scan,
         $task_warp_scan,
       ),
       array($engineering),
       pht('All Engineering'));
 
     $this->assertQueryByProjects(
       $user,
       array(
         $task_diplomacy_scan,
         $task_warp_scan,
       ),
       array($engineering_scan),
       pht('All Scan'));
 
     $this->assertQueryByProjects(
       $user,
       array(
         $task_warp_explore,
         $task_diplomacy_scan,
       ),
       array($engineering, $exploration),
       pht('Engineering + Exploration'));
 
     // This is testing that a query for "Parent" and "Parent > Child" works
     // properly.
     $this->assertQueryByProjects(
       $user,
       array(
         $task_diplomacy_scan,
         $task_warp_scan,
       ),
       array($engineering, $engineering_scan),
       pht('Engineering + Scan'));
   }
 
   public function testTagAncestryConflicts() {
     $user = $this->createUser();
     $user->save();
 
     $stonework = $this->createProject($user);
     $stonework_masonry = $this->createProject($user, $stonework);
     $stonework_sculpting = $this->createProject($user, $stonework);
 
     $task = $this->newTask($user, array());
     $this->assertEqual(array(), $this->getTaskProjects($task));
 
     $this->addProjectTags($user, $task, array($stonework->getPHID()));
     $this->assertEqual(
       array(
         $stonework->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // Adding a descendant should remove the parent.
     $this->addProjectTags($user, $task, array($stonework_masonry->getPHID()));
     $this->assertEqual(
       array(
         $stonework_masonry->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // Adding an ancestor should remove the descendant.
     $this->addProjectTags($user, $task, array($stonework->getPHID()));
     $this->assertEqual(
       array(
         $stonework->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // Adding two tags in the same hierarchy which are not mutual ancestors
     // should remove the ancestor but otherwise work fine.
     $this->addProjectTags(
       $user,
       $task,
       array(
         $stonework_masonry->getPHID(),
         $stonework_sculpting->getPHID(),
       ));
 
     $expect = array(
       $stonework_masonry->getPHID(),
       $stonework_sculpting->getPHID(),
     );
     sort($expect);
 
     $this->assertEqual($expect,  $this->getTaskProjects($task));
   }
 
   public function testTagMilestoneConflicts() {
     $user = $this->createUser();
     $user->save();
 
     $stonework = $this->createProject($user);
     $stonework_1 = $this->createProject($user, $stonework, true);
     $stonework_2 = $this->createProject($user, $stonework, true);
 
     $task = $this->newTask($user, array());
     $this->assertEqual(array(), $this->getTaskProjects($task));
 
     $this->addProjectTags($user, $task, array($stonework->getPHID()));
     $this->assertEqual(
       array(
         $stonework->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // Adding a milesone should remove the parent.
     $this->addProjectTags($user, $task, array($stonework_1->getPHID()));
     $this->assertEqual(
       array(
         $stonework_1->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // Adding the parent should remove the milestone.
     $this->addProjectTags($user, $task, array($stonework->getPHID()));
     $this->assertEqual(
       array(
         $stonework->getPHID(),
       ),
       $this->getTaskProjects($task));
 
     // First, add one milestone.
     $this->addProjectTags($user, $task, array($stonework_1->getPHID()));
     // Now, adding a second milestone should remove the first milestone.
     $this->addProjectTags($user, $task, array($stonework_2->getPHID()));
     $this->assertEqual(
       array(
         $stonework_2->getPHID(),
       ),
       $this->getTaskProjects($task));
   }
 
   public function testBoardMoves() {
     $user = $this->createUser();
     $user->save();
 
     $board = $this->createProject($user);
 
     $backlog = $this->addColumn($user, $board, 0);
     $column = $this->addColumn($user, $board, 1);
 
     // New tasks should appear in the backlog.
     $task1 = $this->newTask($user, array($board));
     $expect = array(
       $backlog->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task1);
 
     // Moving a task should move it to the destination column.
     $this->moveToColumn($user, $board, $task1, $backlog, $column);
     $expect = array(
       $column->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task1);
 
     // Same thing again, with a new task.
     $task2 = $this->newTask($user, array($board));
     $expect = array(
       $backlog->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task2);
 
     // Move it, too.
     $this->moveToColumn($user, $board, $task2, $backlog, $column);
     $expect = array(
       $column->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task2);
 
     // Now the stuff should be in the column, in order, with the more recently
     // moved task on top.
     $expect = array(
       $task2->getPHID(),
       $task1->getPHID(),
     );
     $this->assertTasksInColumn($expect, $user, $board, $column);
 
     // Move the second task after the first task.
     $options = array(
       'afterPHID' => $task1->getPHID(),
     );
     $this->moveToColumn($user, $board, $task2, $column, $column, $options);
     $expect = array(
       $task1->getPHID(),
       $task2->getPHID(),
     );
     $this->assertTasksInColumn($expect, $user, $board, $column);
 
     // Move the second task before the first task.
     $options = array(
       'beforePHID' => $task1->getPHID(),
     );
     $this->moveToColumn($user, $board, $task2, $column, $column, $options);
     $expect = array(
       $task2->getPHID(),
       $task1->getPHID(),
     );
     $this->assertTasksInColumn($expect, $user, $board, $column);
   }
 
   public function testMilestoneMoves() {
     $user = $this->createUser();
     $user->save();
 
     $board = $this->createProject($user);
 
     $backlog = $this->addColumn($user, $board, 0);
 
     // Create a task into the backlog.
     $task = $this->newTask($user, array($board));
     $expect = array(
       $backlog->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task);
 
     $milestone = $this->createProject($user, $board, true);
 
     $this->addProjectTags($user, $task, array($milestone->getPHID()));
 
     // We just want the side effect of looking at the board: creation of the
     // milestone column.
     $this->loadColumns($user, $board, $task);
 
     $column = id(new PhabricatorProjectColumnQuery())
       ->setViewer($user)
       ->withProjectPHIDs(array($board->getPHID()))
       ->withProxyPHIDs(array($milestone->getPHID()))
       ->executeOne();
 
     $this->assertTrue((bool)$column);
 
     // Moving the task to the milestone should have moved it to the milestone
     // column.
     $expect = array(
       $column->getPHID(),
     );
     $this->assertColumns($expect, $user, $board, $task);
   }
 
   public function testColumnExtendedPolicies() {
     $user = $this->createUser();
     $user->save();
 
     $board = $this->createProject($user);
     $column = $this->addColumn($user, $board, 0);
 
     // At first, the user should be able to view and edit the column.
     $column = $this->refreshColumn($user, $column);
     $this->assertTrue((bool)$column);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $user,
       $column,
       PhabricatorPolicyCapability::CAN_EDIT);
     $this->assertTrue($can_edit);
 
     // Now, set the project edit policy to "Members of Project". This should
     // disable editing.
     $members_policy = id(new PhabricatorProjectMembersPolicyRule())
       ->getObjectPolicyFullKey();
     $board->setEditPolicy($members_policy)->save();
 
     $column = $this->refreshColumn($user, $column);
     $this->assertTrue((bool)$column);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $user,
       $column,
       PhabricatorPolicyCapability::CAN_EDIT);
     $this->assertFalse($can_edit);
 
     // Now, join the project. This should make the column editable again.
     $this->joinProject($board, $user);
 
     $column = $this->refreshColumn($user, $column);
     $this->assertTrue((bool)$column);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $user,
       $column,
       PhabricatorPolicyCapability::CAN_EDIT);
     $this->assertTrue($can_edit);
   }
 
   private function moveToColumn(
     PhabricatorUser $viewer,
     PhabricatorProject $board,
     ManiphestTask $task,
     PhabricatorProjectColumn $src,
     PhabricatorProjectColumn $dst,
     $options = null) {
 
     $xactions = array();
 
     if (!$options) {
       $options = array();
     }
 
+    $value = array(
+      'columnPHID' => $dst->getPHID(),
+    ) + $options;
+
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)
-      ->setOldValue(
-        array(
-          'projectPHID' => $board->getPHID(),
-          'columnPHIDs' => array($src->getPHID()),
-        ))
-      ->setNewValue(
-        array(
-          'projectPHID' => $board->getPHID(),
-          'columnPHIDs' => array($dst->getPHID()),
-        ) + $options);
+      ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
+      ->setNewValue(array($value));
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setContentSource($this->newContentSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($task, $xactions);
   }
 
   private function assertColumns(
     array $expect,
     PhabricatorUser $viewer,
     PhabricatorProject $board,
     ManiphestTask $task) {
     $column_phids = $this->loadColumns($viewer, $board, $task);
     $this->assertEqual($expect, $column_phids);
   }
 
   private function loadColumns(
     PhabricatorUser $viewer,
     PhabricatorProject $board,
     ManiphestTask $task) {
     $engine = id(new PhabricatorBoardLayoutEngine())
       ->setViewer($viewer)
       ->setBoardPHIDs(array($board->getPHID()))
       ->setObjectPHIDs(
         array(
           $task->getPHID(),
         ))
       ->executeLayout();
 
     $columns = $engine->getObjectColumns($board->getPHID(), $task->getPHID());
     $column_phids = mpull($columns, 'getPHID');
     $column_phids = array_values($column_phids);
 
     return $column_phids;
   }
 
   private function assertTasksInColumn(
     array $expect,
     PhabricatorUser $viewer,
     PhabricatorProject $board,
     PhabricatorProjectColumn $column) {
 
     $engine = id(new PhabricatorBoardLayoutEngine())
       ->setViewer($viewer)
       ->setBoardPHIDs(array($board->getPHID()))
       ->setObjectPHIDs($expect)
       ->executeLayout();
 
     $object_phids = $engine->getColumnObjectPHIDs(
       $board->getPHID(),
       $column->getPHID());
     $object_phids = array_values($object_phids);
 
     $this->assertEqual($expect, $object_phids);
   }
 
   private function addColumn(
     PhabricatorUser $viewer,
     PhabricatorProject $project,
     $sequence) {
 
     $project->setHasWorkboard(1)->save();
 
     return PhabricatorProjectColumn::initializeNewColumn($viewer)
       ->setSequence(0)
       ->setProperty('isDefault', ($sequence == 0))
       ->setProjectPHID($project->getPHID())
       ->save();
   }
 
   private function getTaskProjects(ManiphestTask $task) {
     $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
       $task->getPHID(),
       PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
 
     sort($project_phids);
 
     return $project_phids;
   }
 
   private function attemptProjectEdit(
     PhabricatorProject $proj,
     PhabricatorUser $user,
     $skip_refresh = false) {
 
     $proj = $this->refreshProject($proj, $user, true);
 
     $new_name = $proj->getName().' '.mt_rand();
 
     $xaction = new PhabricatorProjectTransaction();
     $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME);
     $xaction->setNewValue($new_name);
 
     $this->applyTransactions($proj, $user, array($xaction));
 
     return true;
   }
 
 
   private function addProjectTags(
     PhabricatorUser $viewer,
     ManiphestTask $task,
     array $phids) {
 
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
       ->setMetadataValue(
         'edge:type',
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
       ->setNewValue(
         array(
           '+' => array_fuse($phids),
         ));
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setContentSource($this->newContentSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($task, $xactions);
   }
 
   private function newTask(
     PhabricatorUser $viewer,
     array $projects,
     $name = null) {
 
     $task = ManiphestTask::initializeNewTask($viewer);
 
     if (!strlen($name)) {
       $name = pht('Test Task');
     }
 
     $xactions = array();
 
     $xactions[] = id(new ManiphestTransaction())
       ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
       ->setNewValue($name);
 
     if ($projects) {
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue(
           'edge:type',
           PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
         ->setNewValue(
           array(
             '=' => array_fuse(mpull($projects, 'getPHID')),
           ));
     }
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setContentSource($this->newContentSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($task, $xactions);
 
     return $task;
   }
 
   private function assertQueryByProjects(
     PhabricatorUser $viewer,
     array $expect,
     array $projects,
     $label = null) {
 
     $datasource = id(new PhabricatorProjectLogicalDatasource())
       ->setViewer($viewer);
 
     $project_phids = mpull($projects, 'getPHID');
     $constraints = $datasource->evaluateTokens($project_phids);
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer($viewer);
 
     $query->withEdgeLogicConstraints(
       PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
       $constraints);
 
     $tasks = $query->execute();
 
     $expect_phids = mpull($expect, 'getTitle', 'getPHID');
     ksort($expect_phids);
 
     $actual_phids = mpull($tasks, 'getTitle', 'getPHID');
     ksort($actual_phids);
 
     $this->assertEqual($expect_phids, $actual_phids, $label);
   }
 
   private function refreshProject(
     PhabricatorProject $project,
     PhabricatorUser $viewer,
     $need_members = false,
     $need_watchers = false) {
 
     $results = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->needMembers($need_members)
       ->needWatchers($need_watchers)
       ->withIDs(array($project->getID()))
       ->execute();
 
     if ($results) {
       return head($results);
     } else {
       return null;
     }
   }
 
   private function refreshColumn(
     PhabricatorUser $viewer,
     PhabricatorProjectColumn $column) {
 
     $results = id(new PhabricatorProjectColumnQuery())
       ->setViewer($viewer)
       ->withIDs(array($column->getID()))
       ->execute();
 
     if ($results) {
       return head($results);
     } else {
       return null;
     }
   }
 
   private function createProject(
     PhabricatorUser $user,
     PhabricatorProject $parent = null,
     $is_milestone = false) {
 
     $project = PhabricatorProject::initializeNewProject($user);
 
 
     $name = pht('Test Project %d', mt_rand());
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
       ->setNewValue($name);
 
     if ($parent) {
       if ($is_milestone) {
         $xactions[] = id(new PhabricatorProjectTransaction())
           ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
           ->setNewValue($parent->getPHID());
       } else {
         $xactions[] = id(new PhabricatorProjectTransaction())
           ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT)
           ->setNewValue($parent->getPHID());
       }
     }
 
     $this->applyTransactions($project, $user, $xactions);
 
     // Force these values immediately; they are normally updated by the
     // index engine.
     if ($parent) {
       if ($is_milestone) {
         $parent->setHasMilestones(1)->save();
       } else {
         $parent->setHasSubprojects(1)->save();
       }
     }
 
     return $project;
   }
 
   private function setViewPolicy(
     PhabricatorProject $project,
     PhabricatorUser $user,
     $policy) {
 
     $xactions = array();
 
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
       ->setNewValue($policy);
 
     $this->applyTransactions($project, $user, $xactions);
 
     return $project;
   }
 
   private function createProjectWithNewAuthor() {
     $author = $this->createUser();
     $author->save();
 
     $project = $this->createProject($author);
 
     return $project;
   }
 
   private function createUser() {
     $rand = mt_rand();
 
     $user = new PhabricatorUser();
     $user->setUsername('unittestuser'.$rand);
     $user->setRealName(pht('Unit Test User %d', $rand));
 
     return $user;
   }
 
   private function joinProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     return $this->joinOrLeaveProject($project, $user, '+');
   }
 
   private function leaveProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     return $this->joinOrLeaveProject($project, $user, '-');
   }
 
   private function watchProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     return $this->watchOrUnwatchProject($project, $user, '+');
   }
 
   private function unwatchProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     return $this->watchOrUnwatchProject($project, $user, '-');
   }
 
   private function joinOrLeaveProject(
     PhabricatorProject $project,
     PhabricatorUser $user,
     $operation) {
     return $this->applyProjectEdgeTransaction(
       $project,
       $user,
       $operation,
       PhabricatorProjectProjectHasMemberEdgeType::EDGECONST);
   }
 
   private function watchOrUnwatchProject(
     PhabricatorProject $project,
     PhabricatorUser $user,
     $operation) {
     return $this->applyProjectEdgeTransaction(
       $project,
       $user,
       $operation,
       PhabricatorObjectHasWatcherEdgeType::EDGECONST);
   }
 
   private function applyProjectEdgeTransaction(
     PhabricatorProject $project,
     PhabricatorUser $user,
     $operation,
     $edge_type) {
 
     $spec = array(
       $operation => array($user->getPHID() => $user->getPHID()),
     );
 
     $xactions = array();
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
       ->setMetadataValue('edge:type', $edge_type)
       ->setNewValue($spec);
 
     $this->applyTransactions($project, $user, $xactions);
 
     return $project;
   }
 
   private function applyTransactions(
     PhabricatorProject $project,
     PhabricatorUser $user,
     array $xactions) {
 
     $editor = id(new PhabricatorProjectTransactionEditor())
       ->setActor($user)
       ->setContentSource($this->newContentSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($project, $xactions);
   }
 
 
 }
diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php
index d84df87e9..c827f5aba 100644
--- a/src/applications/project/controller/PhabricatorProjectManageController.php
+++ b/src/applications/project/controller/PhabricatorProjectManageController.php
@@ -1,148 +1,148 @@
 <?php
 
 final class PhabricatorProjectManageController
   extends PhabricatorProjectController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $response = $this->loadProject();
     if ($response) {
       return $response;
     }
 
     $viewer = $request->getUser();
     $project = $this->getProject();
     $id = $project->getID();
     $picture = $project->getProfileImageURI();
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('Project History'))
       ->setUser($viewer)
       ->setPolicyObject($project)
       ->setImage($picture);
 
     if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
       $header->setStatus('fa-check', 'bluegrey', pht('Active'));
     } else {
       $header->setStatus('fa-ban', 'red', pht('Archived'));
     }
 
     $curtain = $this->buildCurtain($project);
     $properties = $this->buildPropertyListView($project);
 
     $timeline = $this->buildTransactionTimeline(
       $project,
       new PhabricatorProjectTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $nav = $this->getProfileMenu();
     $nav->selectFilter(PhabricatorProject::PANEL_MANAGE);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Manage'));
     $crumbs->setBorder(true);
 
     $manage = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
-      ->addPropertySection(pht('DETAILS'), $properties)
+      ->addPropertySection(pht('Details'), $properties)
       ->setMainColumn(
         array(
           $timeline,
         ));
 
     return $this->newPage()
       ->setNavigation($nav)
       ->setCrumbs($crumbs)
       ->setTitle(
         array(
           $project->getDisplayName(),
           pht('Manage'),
         ))
       ->appendChild(
         array(
           $manage,
         ));
   }
 
   private function buildCurtain(PhabricatorProject $project) {
     $viewer = $this->getViewer();
 
     $id = $project->getID();
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $project,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($project);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Details'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI("edit/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Menu'))
         ->setIcon('fa-th-list')
         ->setHref($this->getApplicationURI("{$id}/panel/configure/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Picture'))
         ->setIcon('fa-picture-o')
         ->setHref($this->getApplicationURI("picture/{$id}/"))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     if ($project->isArchived()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Activate Project'))
           ->setIcon('fa-check')
           ->setHref($this->getApplicationURI("archive/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Archive Project'))
           ->setIcon('fa-ban')
           ->setHref($this->getApplicationURI("archive/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
     }
 
     return $curtain;
   }
 
   private function buildPropertyListView(
     PhabricatorProject $project) {
     $viewer = $this->getViewer();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $view->addProperty(
       pht('Looks Like'),
       $viewer->renderHandle($project->getPHID())->setAsTag(true));
 
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $project,
       PhabricatorCustomField::ROLE_VIEW);
     $field_list->appendFieldsToPropertyList($project, $viewer, $view);
 
     return $view;
   }
 
 
 }
diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php
index d3540a178..e529fab61 100644
--- a/src/applications/project/controller/PhabricatorProjectMoveController.php
+++ b/src/applications/project/controller/PhabricatorProjectMoveController.php
@@ -1,233 +1,229 @@
 <?php
 
 final class PhabricatorProjectMoveController
   extends PhabricatorProjectController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $request->validateCSRF();
 
     $column_phid = $request->getStr('columnPHID');
     $object_phid = $request->getStr('objectPHID');
     $after_phid = $request->getStr('afterPHID');
     $before_phid = $request->getStr('beforePHID');
     $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
 
     $project = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
         ))
       ->withIDs(array($id))
       ->executeOne();
     if (!$project) {
       return new Aphront404Response();
     }
 
     $board_phid = $project->getPHID();
 
     $object = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($object_phid))
       ->needProjectPHIDs(true)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
 
     if (!$object) {
       return new Aphront404Response();
     }
 
     $columns = id(new PhabricatorProjectColumnQuery())
       ->setViewer($viewer)
       ->withProjectPHIDs(array($project->getPHID()))
       ->execute();
 
     $columns = mpull($columns, null, 'getPHID');
     $column = idx($columns, $column_phid);
     if (!$column) {
       // User is trying to drop this object into a nonexistent column, just kick
       // them out.
       return new Aphront404Response();
     }
 
     $engine = id(new PhabricatorBoardLayoutEngine())
       ->setViewer($viewer)
       ->setBoardPHIDs(array($board_phid))
       ->setObjectPHIDs(array($object_phid))
       ->executeLayout();
 
     $columns = $engine->getObjectColumns($board_phid, $object_phid);
     $old_column_phids = mpull($columns, 'getPHID');
 
     $xactions = array();
 
+    $order_params = array();
     if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
-      $order_params = array(
-        'afterPHID' => $after_phid,
-        'beforePHID' => $before_phid,
-      );
-    } else {
-      $order_params = array();
+      if ($after_phid) {
+        $order_params['afterPHID'] = $after_phid;
+      } else if ($before_phid) {
+        $order_params['beforePHID'] = $before_phid;
+      }
     }
 
     $xactions[] = id(new ManiphestTransaction())
-      ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)
+      ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
       ->setNewValue(
         array(
-          'columnPHIDs' => array($column->getPHID()),
-          'projectPHID' => $column->getProjectPHID(),
-        ) + $order_params)
-      ->setOldValue(
-        array(
-          'columnPHIDs' => $old_column_phids,
-          'projectPHID' => $column->getProjectPHID(),
+          array(
+            'columnPHID' => $column->getPHID(),
+          ) + $order_params,
         ));
 
     if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
       $priority_xactions = $this->getPriorityTransactions(
         $object,
         $after_phid,
         $before_phid);
       foreach ($priority_xactions as $xaction) {
         $xactions[] = $xaction;
       }
     }
 
     $proxy = $column->getProxy();
     if ($proxy) {
       // We're moving the task into a subproject or milestone column, so add
       // the subproject or milestone.
       $add_projects = array($proxy->getPHID());
     } else if ($project->getHasSubprojects() || $project->getHasMilestones()) {
       // We're moving the task into the "Backlog" column on the parent project,
       // so add the parent explicitly. This gets rid of any subproject or
       // milestone tags.
       $add_projects = array($project->getPHID());
     } else {
       $add_projects = array();
     }
 
     if ($add_projects) {
       $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
 
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $project_type)
         ->setNewValue(
           array(
             '+' => array_fuse($add_projects),
           ));
     }
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setContinueOnMissingFields(true)
       ->setContinueOnNoEffect(true)
       ->setContentSourceFromRequest($request);
 
     $editor->applyTransactions($object, $xactions);
 
     return $this->newCardResponse($board_phid, $object_phid);
   }
 
   private function getPriorityTransactions(
     ManiphestTask $task,
     $after_phid,
     $before_phid) {
 
     list($after_task, $before_task) = $this->loadPriorityTasks(
       $after_phid,
       $before_phid);
 
     $must_move = false;
     if ($after_task && !$task->isLowerPriorityThan($after_task)) {
       $must_move = true;
     }
 
     if ($before_task && !$task->isHigherPriorityThan($before_task)) {
       $must_move = true;
     }
 
     // The move doesn't require a priority change to be valid, so don't
     // change the priority since we are not being forced to.
     if (!$must_move) {
       return array();
     }
 
     $try = array(
       array($after_task, true),
       array($before_task, false),
     );
 
     $pri = null;
     $sub = null;
     foreach ($try as $spec) {
       list($task, $is_after) = $spec;
 
       if (!$task) {
         continue;
       }
 
       list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority(
         $task,
         $is_after);
     }
 
     $xactions = array();
     if ($pri !== null) {
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
         ->setNewValue($pri);
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
         ->setNewValue($sub);
     }
 
     return $xactions;
   }
 
   private function loadPriorityTasks($after_phid, $before_phid) {
     $viewer = $this->getViewer();
 
     $task_phids = array();
 
     if ($after_phid) {
       $task_phids[] = $after_phid;
     }
     if ($before_phid) {
       $task_phids[] = $before_phid;
     }
 
     if (!$task_phids) {
       return array(null, null);
     }
 
     $tasks = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withPHIDs($task_phids)
       ->execute();
     $tasks = mpull($tasks, null, 'getPHID');
 
     if ($after_phid) {
       $after_task = idx($tasks, $after_phid);
     } else {
       $after_task = null;
     }
 
     if ($before_phid) {
       $before_task = idx($tasks, $before_phid);
     } else {
       $before_task = null;
     }
 
     return array($after_task, $before_task);
   }
 
 }
diff --git a/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php
index cd481946b..ce9fbf332 100644
--- a/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php
+++ b/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php
@@ -1,75 +1,76 @@
 <?php
 
 final class PhabricatorProjectsEditEngineExtension
   extends PhabricatorEditEngineExtension {
 
   const EXTENSIONKEY = 'projects.projects';
 
   public function getExtensionPriority() {
     return 500;
   }
 
   public function isExtensionEnabled() {
     return PhabricatorApplication::isClassInstalled(
       'PhabricatorProjectApplication');
   }
 
   public function getExtensionName() {
     return pht('Projects');
   }
 
   public function supportsObject(
     PhabricatorEditEngine $engine,
     PhabricatorApplicationTransactionInterface $object) {
 
     return ($object instanceof PhabricatorProjectInterface);
   }
 
   public function buildCustomEditFields(
     PhabricatorEditEngine $engine,
     PhabricatorApplicationTransactionInterface $object) {
 
     $edge_type = PhabricatorTransactions::TYPE_EDGE;
     $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
 
     $object_phid = $object->getPHID();
     if ($object_phid) {
       $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $object_phid,
         $project_edge_type);
       $project_phids = array_reverse($project_phids);
     } else {
       $project_phids = array();
     }
 
     $projects_field = id(new PhabricatorProjectsEditField())
       ->setKey('projectPHIDs')
       ->setLabel(pht('Tags'))
       ->setEditTypeKey('projects')
       ->setAliases(array('project', 'projects', 'tag', 'tags'))
       ->setIsCopyable(true)
       ->setUseEdgeTransactions(true)
       ->setCommentActionLabel(pht('Change Project Tags'))
+      ->setCommentActionOrder(8000)
       ->setDescription(pht('Select project tags for the object.'))
       ->setTransactionType($edge_type)
       ->setMetadataValue('edge:type', $project_edge_type)
       ->setValue($project_phids);
 
     $projects_field->setViewer($engine->getViewer());
 
     $edit_add = $projects_field->getConduitEditType('projects.add')
       ->setConduitDescription(pht('Add project tags.'));
 
     $edit_set = $projects_field->getConduitEditType('projects.set')
       ->setConduitDescription(
         pht('Set project tags, overwriting current value.'));
 
     $edit_rem = $projects_field->getConduitEditType('projects.remove')
       ->setConduitDescription(pht('Remove project tags.'));
 
     return array(
       $projects_field,
     );
   }
 
 }
diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php
index 061886f6f..2172156e3 100644
--- a/src/applications/project/storage/PhabricatorProject.php
+++ b/src/applications/project/storage/PhabricatorProject.php
@@ -1,799 +1,799 @@
 <?php
 
 final class PhabricatorProject extends PhabricatorProjectDAO
   implements
     PhabricatorApplicationTransactionInterface,
     PhabricatorFlaggableInterface,
     PhabricatorPolicyInterface,
     PhabricatorExtendedPolicyInterface,
     PhabricatorCustomFieldInterface,
     PhabricatorDestructibleInterface,
     PhabricatorFulltextInterface,
     PhabricatorConduitResultInterface,
     PhabricatorColumnProxyInterface {
 
   protected $name;
   protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
   protected $authorPHID;
   protected $primarySlug;
   protected $profileImagePHID;
   protected $icon;
   protected $color;
   protected $mailKey;
 
   protected $viewPolicy;
   protected $editPolicy;
   protected $joinPolicy;
   protected $isMembershipLocked;
 
   protected $parentProjectPHID;
   protected $hasWorkboard;
   protected $hasMilestones;
   protected $hasSubprojects;
   protected $milestoneNumber;
 
   protected $projectPath;
   protected $projectDepth;
   protected $projectPathKey;
 
   protected $properties = array();
 
   private $memberPHIDs = self::ATTACHABLE;
   private $watcherPHIDs = self::ATTACHABLE;
   private $sparseWatchers = self::ATTACHABLE;
   private $sparseMembers = self::ATTACHABLE;
   private $customFields = self::ATTACHABLE;
   private $profileImageFile = self::ATTACHABLE;
   private $slugs = self::ATTACHABLE;
   private $parentProject = self::ATTACHABLE;
 
   const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
 
   const PANEL_PROFILE = 'project.profile';
   const PANEL_POINTS = 'project.points';
   const PANEL_WORKBOARD = 'project.workboard';
   const PANEL_MEMBERS = 'project.members';
   const PANEL_MANAGE = 'project.manage';
   const PANEL_MILESTONES = 'project.milestones';
   const PANEL_SUBPROJECTS = 'project.subprojects';
 
   public static function initializeNewProject(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withClasses(array('PhabricatorProjectApplication'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(
       ProjectDefaultViewCapability::CAPABILITY);
     $edit_policy = $app->getPolicy(
       ProjectDefaultEditCapability::CAPABILITY);
     $join_policy = $app->getPolicy(
       ProjectDefaultJoinCapability::CAPABILITY);
 
     $default_icon = PhabricatorProjectIconSet::getDefaultIconKey();
     $default_color = PhabricatorProjectIconSet::getDefaultColorKey();
 
     return id(new PhabricatorProject())
       ->setAuthorPHID($actor->getPHID())
       ->setIcon($default_icon)
       ->setColor($default_color)
       ->setViewPolicy($view_policy)
       ->setEditPolicy($edit_policy)
       ->setJoinPolicy($join_policy)
       ->setIsMembershipLocked(0)
       ->attachMemberPHIDs(array())
       ->attachSlugs(array())
       ->setHasWorkboard(0)
       ->setHasMilestones(0)
       ->setHasSubprojects(0)
       ->attachParentProject(null);
   }
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
       PhabricatorPolicyCapability::CAN_JOIN,
     );
   }
 
   public function getPolicy($capability) {
     if ($this->isMilestone()) {
       return $this->getParentProject()->getPolicy($capability);
     }
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
       case PhabricatorPolicyCapability::CAN_JOIN:
         return $this->getJoinPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     if ($this->isMilestone()) {
       return $this->getParentProject()->hasAutomaticCapability(
         $capability,
         $viewer);
     }
 
     $can_edit = PhabricatorPolicyCapability::CAN_EDIT;
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if ($this->isUserMember($viewer->getPHID())) {
           // Project members can always view a project.
           return true;
         }
         break;
       case PhabricatorPolicyCapability::CAN_EDIT:
         $parent = $this->getParentProject();
         if ($parent) {
           $can_edit_parent = PhabricatorPolicyFilter::hasCapability(
             $viewer,
             $parent,
             $can_edit);
           if ($can_edit_parent) {
             return true;
           }
         }
         break;
       case PhabricatorPolicyCapability::CAN_JOIN:
         if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) {
           // Project editors can always join a project.
           return true;
         }
         break;
     }
 
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
 
     // TODO: Clarify the additional rules that parent and subprojects imply.
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return pht('Members of a project can always view it.');
       case PhabricatorPolicyCapability::CAN_JOIN:
         return pht('Users who can edit a project can always join it.');
     }
     return null;
   }
 
   public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
     $extended = array();
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         $parent = $this->getParentProject();
         if ($parent) {
           $extended[] = array(
             $parent,
             PhabricatorPolicyCapability::CAN_VIEW,
           );
         }
         break;
     }
 
     return $extended;
   }
 
   public function isUserMember($user_phid) {
     if ($this->memberPHIDs !== self::ATTACHABLE) {
       return in_array($user_phid, $this->memberPHIDs);
     }
     return $this->assertAttachedKey($this->sparseMembers, $user_phid);
   }
 
   public function setIsUserMember($user_phid, $is_member) {
     if ($this->sparseMembers === self::ATTACHABLE) {
       $this->sparseMembers = array();
     }
     $this->sparseMembers[$user_phid] = $is_member;
     return $this;
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'sort128',
         'status' => 'text32',
         'primarySlug' => 'text128?',
         'isMembershipLocked' => 'bool',
         'profileImagePHID' => 'phid?',
         'icon' => 'text32',
         'color' => 'text32',
         'mailKey' => 'bytes20',
         'joinPolicy' => 'policy',
         'parentProjectPHID' => 'phid?',
         'hasWorkboard' => 'bool',
         'hasMilestones' => 'bool',
         'hasSubprojects' => 'bool',
         'milestoneNumber' => 'uint32?',
         'projectPath' => 'hashpath64',
         'projectDepth' => 'uint32',
         'projectPathKey' => 'bytes4',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_icon' => array(
           'columns' => array('icon'),
         ),
         'key_color' => array(
           'columns' => array('color'),
         ),
         'key_milestone' => array(
           'columns' => array('parentProjectPHID', 'milestoneNumber'),
           'unique' => true,
         ),
         'key_primaryslug' => array(
           'columns' => array('primarySlug'),
           'unique' => true,
         ),
         'key_path' => array(
           'columns' => array('projectPath', 'projectDepth'),
         ),
         'key_pathkey' => array(
           'columns' => array('projectPathKey'),
           'unique' => true,
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorProjectProjectPHIDType::TYPECONST);
   }
 
   public function attachMemberPHIDs(array $phids) {
     $this->memberPHIDs = $phids;
     return $this;
   }
 
   public function getMemberPHIDs() {
     return $this->assertAttached($this->memberPHIDs);
   }
 
   public function isArchived() {
     return ($this->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED);
   }
 
   public function getProfileImageURI() {
     return $this->getProfileImageFile()->getBestURI();
   }
 
   public function attachProfileImageFile(PhabricatorFile $file) {
     $this->profileImageFile = $file;
     return $this;
   }
 
   public function getProfileImageFile() {
     return $this->assertAttached($this->profileImageFile);
   }
 
 
   public function isUserWatcher($user_phid) {
     if ($this->watcherPHIDs !== self::ATTACHABLE) {
       return in_array($user_phid, $this->watcherPHIDs);
     }
     return $this->assertAttachedKey($this->sparseWatchers, $user_phid);
   }
 
   public function isUserAncestorWatcher($user_phid) {
     $is_watcher = $this->isUserWatcher($user_phid);
 
     if (!$is_watcher) {
       $parent = $this->getParentProject();
       if ($parent) {
         return $parent->isUserWatcher($user_phid);
       }
     }
 
     return $is_watcher;
   }
 
   public function getWatchedAncestorPHID($user_phid) {
     if ($this->isUserWatcher($user_phid)) {
       return $this->getPHID();
     }
 
     $parent = $this->getParentProject();
     if ($parent) {
       return $parent->getWatchedAncestorPHID($user_phid);
     }
 
     return null;
   }
 
   public function setIsUserWatcher($user_phid, $is_watcher) {
     if ($this->sparseWatchers === self::ATTACHABLE) {
       $this->sparseWatchers = array();
     }
     $this->sparseWatchers[$user_phid] = $is_watcher;
     return $this;
   }
 
   public function attachWatcherPHIDs(array $phids) {
     $this->watcherPHIDs = $phids;
     return $this;
   }
 
   public function getWatcherPHIDs() {
     return $this->assertAttached($this->watcherPHIDs);
   }
 
   public function getAllAncestorWatcherPHIDs() {
     $parent = $this->getParentProject();
     if ($parent) {
       $watchers = $parent->getAllAncestorWatcherPHIDs();
     } else {
       $watchers = array();
     }
 
     foreach ($this->getWatcherPHIDs() as $phid) {
       $watchers[$phid] = $phid;
     }
 
     return $watchers;
   }
 
   public function attachSlugs(array $slugs) {
     $this->slugs = $slugs;
     return $this;
   }
 
   public function getSlugs() {
     return $this->assertAttached($this->slugs);
   }
 
   public function getColor() {
     if ($this->isArchived()) {
       return PHUITagView::COLOR_DISABLED;
     }
 
     return $this->color;
   }
 
   public function getURI() {
     $id = $this->getID();
     return "/project/view/{$id}/";
   }
 
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
 
     if (!strlen($this->getPHID())) {
       $this->setPHID($this->generatePHID());
     }
 
     if (!strlen($this->getProjectPathKey())) {
       $hash = PhabricatorHash::digestForIndex($this->getPHID());
       $hash = substr($hash, 0, 4);
       $this->setProjectPathKey($hash);
     }
 
     $path = array();
     $depth = 0;
     if ($this->parentProjectPHID) {
       $parent = $this->getParentProject();
       $path[] = $parent->getProjectPath();
       $depth = $parent->getProjectDepth() + 1;
     }
     $path[] = $this->getProjectPathKey();
     $path = implode('', $path);
 
     $limit = self::getProjectDepthLimit();
     if ($depth >= $limit) {
       throw new Exception(pht('Project depth is too great.'));
     }
 
     $this->setProjectPath($path);
     $this->setProjectDepth($depth);
 
     $this->openTransaction();
       $result = parent::save();
       $this->updateDatasourceTokens();
     $this->saveTransaction();
 
     return $result;
   }
 
   public static function getProjectDepthLimit() {
     // This is limited by how many path hashes we can fit in the path
     // column.
     return 16;
   }
 
   public function updateDatasourceTokens() {
     $table = self::TABLE_DATASOURCE_TOKEN;
     $conn_w = $this->establishConnection('w');
     $id = $this->getID();
 
     $slugs = queryfx_all(
       $conn_w,
       'SELECT * FROM %T WHERE projectPHID = %s',
       id(new PhabricatorProjectSlug())->getTableName(),
       $this->getPHID());
 
     $all_strings = ipull($slugs, 'slug');
     $all_strings[] = $this->getDisplayName();
     $all_strings = implode(' ', $all_strings);
 
     $tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings);
 
     $sql = array();
     foreach ($tokens as $token) {
       $sql[] = qsprintf($conn_w, '(%d, %s)', $id, $token);
     }
 
     $this->openTransaction();
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE projectID = %d',
         $table,
         $id);
 
       foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
         queryfx(
           $conn_w,
           'INSERT INTO %T (projectID, token) VALUES %Q',
           $table,
           $chunk);
       }
     $this->saveTransaction();
   }
 
   public function isMilestone() {
     return ($this->getMilestoneNumber() !== null);
   }
 
   public function getParentProject() {
     return $this->assertAttached($this->parentProject);
   }
 
   public function attachParentProject(PhabricatorProject $project = null) {
     $this->parentProject = $project;
     return $this;
   }
 
   public function getAncestorProjectPaths() {
     $parts = array();
 
     $path = $this->getProjectPath();
     $parent_length = (strlen($path) - 4);
 
     for ($ii = $parent_length; $ii > 0; $ii -= 4) {
       $parts[] = substr($path, 0, $ii);
     }
 
     return $parts;
   }
 
   public function getAncestorProjects() {
     $ancestors = array();
 
     $cursor = $this->getParentProject();
     while ($cursor) {
       $ancestors[] = $cursor;
       $cursor = $cursor->getParentProject();
     }
 
     return $ancestors;
   }
 
   public function supportsEditMembers() {
     if ($this->isMilestone()) {
       return false;
     }
 
     if ($this->getHasSubprojects()) {
       return false;
     }
 
     return true;
   }
 
   public function supportsMilestones() {
     if ($this->isMilestone()) {
       return false;
     }
 
     return true;
   }
 
   public function supportsSubprojects() {
     if ($this->isMilestone()) {
       return false;
     }
 
     return true;
   }
 
   public function loadNextMilestoneNumber() {
     $current = queryfx_one(
       $this->establishConnection('w'),
       'SELECT MAX(milestoneNumber) n
         FROM %T
         WHERE parentProjectPHID = %s',
       $this->getTableName(),
       $this->getPHID());
 
     if (!$current) {
       $number = 1;
     } else {
       $number = (int)$current['n'] + 1;
     }
 
     return $number;
   }
 
   public function getDisplayName() {
     $name = $this->getName();
 
     // If this is a milestone, show it as "Parent > Sprint 99".
     if ($this->isMilestone()) {
       $name = pht(
         '%s (%s)',
         $this->getParentProject()->getName(),
         $name);
     }
 
     return $name;
   }
 
   public function getDisplayIconKey() {
     if ($this->isMilestone()) {
       $key = PhabricatorProjectIconSet::getMilestoneIconKey();
     } else {
       $key = $this->getIcon();
     }
 
     return $key;
   }
 
   public function getDisplayIconIcon() {
     $key = $this->getDisplayIconKey();
     return PhabricatorProjectIconSet::getIconIcon($key);
   }
 
   public function getDisplayIconName() {
     $key = $this->getDisplayIconKey();
     return PhabricatorProjectIconSet::getIconName($key);
   }
 
   public function getDisplayColor() {
     if ($this->isMilestone()) {
-      return PhabricatorProjectIconSet::getDefaultColorKey();
+      return $this->getParentProject()->getColor();
     }
 
     return $this->getColor();
   }
 
   public function getDisplayIconComposeIcon() {
     $icon = $this->getDisplayIconIcon();
     return $icon;
   }
 
   public function getDisplayIconComposeColor() {
     $color = $this->getDisplayColor();
 
     $map = array(
       'grey' => 'charcoal',
       'checkered' => 'backdrop',
     );
 
     return idx($map, $color, $color);
   }
 
   public function getProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function setProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getDefaultWorkboardSort() {
     return $this->getProperty('workboard.sort.default');
   }
 
   public function setDefaultWorkboardSort($sort) {
     return $this->setProperty('workboard.sort.default', $sort);
   }
 
   public function getDefaultWorkboardFilter() {
     return $this->getProperty('workboard.filter.default');
   }
 
   public function setDefaultWorkboardFilter($filter) {
     return $this->setProperty('workboard.filter.default', $filter);
   }
 
   public function getWorkboardBackgroundColor() {
     return $this->getProperty('workboard.background');
   }
 
   public function setWorkboardBackgroundColor($color) {
     return $this->setProperty('workboard.background', $color);
   }
 
   public function getDisplayWorkboardBackgroundColor() {
     $color = $this->getWorkboardBackgroundColor();
 
     if ($color === null) {
       $parent = $this->getParentProject();
       if ($parent) {
         return $parent->getDisplayWorkboardBackgroundColor();
       }
     }
 
     if ($color === 'none') {
       $color = null;
     }
 
     return $color;
   }
 
 
 /* -(  PhabricatorCustomFieldInterface  )------------------------------------ */
 
 
   public function getCustomFieldSpecificationForRole($role) {
     return PhabricatorEnv::getEnvConfig('projects.fields');
   }
 
   public function getCustomFieldBaseClass() {
     return 'PhabricatorProjectCustomField';
   }
 
   public function getCustomFields() {
     return $this->assertAttached($this->customFields);
   }
 
   public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
     $this->customFields = $fields;
     return $this;
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorProjectTransactionEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorProjectTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $this->delete();
 
       $columns = id(new PhabricatorProjectColumn())
         ->loadAllWhere('projectPHID = %s', $this->getPHID());
       foreach ($columns as $column) {
         $engine->destroyObject($column);
       }
 
       $slugs = id(new PhabricatorProjectSlug())
         ->loadAllWhere('projectPHID = %s', $this->getPHID());
       foreach ($slugs as $slug) {
         $slug->delete();
       }
 
     $this->saveTransaction();
   }
 
 
 /* -(  PhabricatorFulltextInterface  )--------------------------------------- */
 
 
   public function newFulltextEngine() {
     return new PhabricatorProjectFulltextEngine();
   }
 
 
 /* -(  PhabricatorConduitResultInterface  )---------------------------------- */
 
 
   public function getFieldSpecificationsForConduit() {
     return array(
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('name')
         ->setType('string')
         ->setDescription(pht('The name of the project.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('slug')
         ->setType('string')
         ->setDescription(pht('Primary slug/hashtag.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('icon')
         ->setType('map<string, wild>')
         ->setDescription(pht('Information about the project icon.')),
       id(new PhabricatorConduitSearchFieldSpecification())
         ->setKey('color')
         ->setType('map<string, wild>')
         ->setDescription(pht('Information about the project color.')),
     );
   }
 
   public function getFieldValuesForConduit() {
     $color_key = $this->getColor();
     $color_name = PhabricatorProjectIconSet::getColorName($color_key);
 
     return array(
       'name' => $this->getName(),
       'slug' => $this->getPrimarySlug(),
       'icon' => array(
         'key' => $this->getDisplayIconKey(),
         'name' => $this->getDisplayIconName(),
         'icon' => $this->getDisplayIconIcon(),
       ),
       'color' => array(
         'key' => $color_key,
         'name' => $color_name,
       ),
     );
   }
 
   public function getConduitSearchAttachments() {
     return array(
       id(new PhabricatorProjectsMembersSearchEngineAttachment())
         ->setAttachmentKey('members'),
       id(new PhabricatorProjectsWatchersSearchEngineAttachment())
         ->setAttachmentKey('watchers'),
     );
   }
 
 
 /* -(  PhabricatorColumnProxyInterface  )------------------------------------ */
 
 
   public function getProxyColumnName() {
     return $this->getName();
   }
 
   public function getProxyColumnIcon() {
     return $this->getDisplayIconIcon();
   }
 
   public function getProxyColumnClass() {
     if ($this->isMilestone()) {
       return 'phui-workboard-column-milestone';
     }
 
     return null;
   }
 
 
 }
diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php
index 28aed0f5a..660aafcc1 100644
--- a/src/applications/project/storage/PhabricatorProjectColumn.php
+++ b/src/applications/project/storage/PhabricatorProjectColumn.php
@@ -1,264 +1,262 @@
 <?php
 
 final class PhabricatorProjectColumn
   extends PhabricatorProjectDAO
   implements
     PhabricatorApplicationTransactionInterface,
     PhabricatorPolicyInterface,
     PhabricatorDestructibleInterface,
     PhabricatorExtendedPolicyInterface {
 
   const STATUS_ACTIVE = 0;
   const STATUS_HIDDEN = 1;
 
   const DEFAULT_ORDER = 'natural';
   const ORDER_NATURAL = 'natural';
   const ORDER_PRIORITY = 'priority';
 
   protected $name;
   protected $status;
   protected $projectPHID;
   protected $proxyPHID;
   protected $sequence;
   protected $properties = array();
 
   private $project = self::ATTACHABLE;
   private $proxy = self::ATTACHABLE;
 
   public static function initializeNewColumn(PhabricatorUser $user) {
     return id(new PhabricatorProjectColumn())
       ->setName('')
       ->setStatus(self::STATUS_ACTIVE)
       ->attachProxy(null);
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'properties' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'text255',
         'status' => 'uint32',
         'sequence' => 'uint32',
         'proxyPHID' => 'phid?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_status' => array(
           'columns' => array('projectPHID', 'status', 'sequence'),
         ),
         'key_sequence' => array(
           'columns' => array('projectPHID', 'sequence'),
         ),
         'key_proxy' => array(
           'columns' => array('projectPHID', 'proxyPHID'),
           'unique' => true,
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorProjectColumnPHIDType::TYPECONST);
   }
 
   public function attachProject(PhabricatorProject $project) {
     $this->project = $project;
     return $this;
   }
 
   public function getProject() {
     return $this->assertAttached($this->project);
   }
 
   public function attachProxy($proxy) {
     $this->proxy = $proxy;
     return $this;
   }
 
   public function getProxy() {
     return $this->assertAttached($this->proxy);
   }
 
   public function isDefaultColumn() {
     return (bool)$this->getProperty('isDefault');
   }
 
   public function isHidden() {
     $proxy = $this->getProxy();
     if ($proxy) {
       return $proxy->isArchived();
     }
 
     return ($this->getStatus() == self::STATUS_HIDDEN);
   }
 
   public function getDisplayName() {
     $proxy = $this->getProxy();
     if ($proxy) {
       return $proxy->getProxyColumnName();
     }
 
     $name = $this->getName();
     if (strlen($name)) {
       return $name;
     }
 
     if ($this->isDefaultColumn()) {
       return pht('Backlog');
     }
 
     return pht('Unnamed Column');
   }
 
   public function getDisplayType() {
     if ($this->isDefaultColumn()) {
       return pht('(Default)');
     }
     if ($this->isHidden()) {
       return pht('(Hidden)');
     }
 
     return null;
   }
 
   public function getDisplayClass() {
     $proxy = $this->getProxy();
     if ($proxy) {
       return $proxy->getProxyColumnClass();
     }
 
     return null;
   }
 
   public function getHeaderIcon() {
     $proxy = $this->getProxy();
     if ($proxy) {
       return $proxy->getProxyColumnIcon();
     }
 
     if ($this->isHidden()) {
       return 'fa-eye-slash';
     }
 
     return null;
   }
 
   public function getProperty($key, $default = null) {
     return idx($this->properties, $key, $default);
   }
 
   public function setProperty($key, $value) {
     $this->properties[$key] = $value;
     return $this;
   }
 
   public function getPointLimit() {
     return $this->getProperty('pointLimit');
   }
 
   public function setPointLimit($limit) {
     $this->setProperty('pointLimit', $limit);
     return $this;
   }
 
   public function getOrderingKey() {
     $proxy = $this->getProxy();
 
     // Normal columns and subproject columns go first, in a user-controlled
     // order.
 
-    // All the milestone columns go last, in reverse order (newest on the
-    // left) so that you don't have to scroll across older milestones to get
-    // to the newest ones.
+    // All the milestone columns go last, in their sequential order.
 
     if (!$proxy || !$proxy->isMilestone()) {
       $group = 'A';
       $sequence = $this->getSequence();
     } else {
       $group = 'B';
-      $sequence = (10000000 - $proxy->getMilestoneNumber());
+      $sequence = $proxy->getMilestoneNumber();
     }
 
     return sprintf('%s%012d', $group, $sequence);
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhabricatorProjectColumnTransactionEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhabricatorProjectColumnTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     // NOTE: Column policies are enforced as an extended policy which makes
     // them the same as the project's policies.
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_USER;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return $this->getProject()->hasAutomaticCapability(
       $capability,
       $viewer);
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('Users must be able to see a project to see its board.');
   }
 
 
 /* -(  PhabricatorExtendedPolicyInterface  )--------------------------------- */
 
 
   public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
     return array(
       array($this->getProject(), $capability),
     );
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
     $this->delete();
     $this->saveTransaction();
   }
 
 }
diff --git a/src/applications/releeph/controller/ReleephController.php b/src/applications/releeph/controller/ReleephController.php
index 83c94debe..f48d2858e 100644
--- a/src/applications/releeph/controller/ReleephController.php
+++ b/src/applications/releeph/controller/ReleephController.php
@@ -1,50 +1,37 @@
 <?php
 
 abstract class ReleephController extends PhabricatorController {
 
-  public function buildStandardPageResponse($view, array $data) {
-    $page = $this->buildStandardPageView();
-
-    $page->setApplicationName(pht('Releeph'));
-    $page->setBaseURI('/releeph/');
-    $page->setTitle(idx($data, 'title'));
-    $page->setGlyph("\xD3\x82");
-    $page->appendChild($view);
-
-    $response = new AphrontWebpageResponse();
-    return $response->setContent($page->render());
-  }
-
   public function buildSideNavView($for_app = false) {
     $user = $this->getRequest()->getUser();
 
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
 
     if ($for_app) {
       $nav->addFilter('project/create/', pht('Create Product'));
     }
 
     id(new ReleephProductSearchEngine())
       ->setViewer($user)
       ->addNavigationItems($nav->getMenu());
 
     $nav->selectFilter(null);
 
     return $nav;
   }
 
   public function buildApplicationMenu() {
     return $this->buildSideNavView(true)->getMenu();
   }
 
 
   protected function getProductViewURI(ReleephProject $product) {
     return $this->getApplicationURI('project/'.$product->getID().'/');
   }
 
   protected function getBranchViewURI(ReleephBranch $branch) {
     return $this->getApplicationURI('branch/'.$branch->getID().'/');
   }
 
 }
diff --git a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
index d13383cc3..e03e432d1 100644
--- a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
+++ b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
@@ -1,124 +1,133 @@
 <?php
 
 final class ReleephBranchCreateController extends ReleephProductController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('projectID');
 
     $product = id(new ReleephProductQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$product) {
       return new Aphront404Response();
     }
     $this->setProduct($product);
 
 
     $cut_point = $request->getStr('cutPoint');
     $symbolic_name = $request->getStr('symbolicName');
 
     if (!$cut_point) {
       $repository = $product->getRepository();
       switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
           $cut_point = $product->getTrunkBranch();
           break;
       }
     }
 
     $e_cut = true;
     $errors = array();
 
     $branch_date_control = id(new AphrontFormDateControl())
       ->setUser($request->getUser())
       ->setName('templateDate')
       ->setLabel(pht('Date'))
       ->setCaption(pht('The date used for filling out the branch template.'))
       ->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY);
     $branch_date = $branch_date_control->readValueFromRequest($request);
 
     if ($request->isFormPost()) {
       $cut_commit = null;
       if (!$cut_point) {
         $e_cut = pht('Required');
         $errors[] = pht('You must give a branch cut point');
       } else {
         try {
           $finder = id(new ReleephCommitFinder())
             ->setUser($request->getUser())
             ->setReleephProject($product);
           $cut_commit = $finder->fromPartial($cut_point);
         } catch (Exception $e) {
           $e_cut = pht('Invalid');
           $errors[] = $e->getMessage();
         }
       }
 
       if (!$errors) {
         $branch = id(new ReleephBranchEditor())
           ->setReleephProject($product)
           ->setActor($request->getUser())
           ->newBranchFromCommit(
             $cut_commit,
             $branch_date,
             $symbolic_name);
 
         $branch_uri = $this->getApplicationURI('branch/'.$branch->getID());
 
         return id(new AphrontRedirectResponse())
           ->setURI($branch_uri);
       }
     }
 
     $product_uri = $this->getProductViewURI($product);
 
     $form = id(new AphrontFormView())
       ->setUser($request->getUser())
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Symbolic Name'))
           ->setName('symbolicName')
           ->setValue($symbolic_name)
           ->setCaption(pht(
             'Mutable alternate name, for easy reference, (e.g. "LATEST")')))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Cut point'))
           ->setName('cutPoint')
           ->setValue($cut_point)
           ->setError($e_cut)
           ->setCaption(pht(
             'A commit ID for your repo type, or a Diffusion ID like "rE123"')))
       ->appendChild($branch_date_control)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Cut Branch'))
           ->addCancelButton($product_uri));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('New Branch'))
+      ->setHeaderText(pht('Branch'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
+    $title = pht('New Branch');
+
     $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->addTextCrumb(pht('New Branch'));
-
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('New Branch'),
-      ));
+    $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-plus-square');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 }
diff --git a/src/applications/releeph/controller/branch/ReleephBranchEditController.php b/src/applications/releeph/controller/branch/ReleephBranchEditController.php
index 6d66f5d9d..9d34e7866 100644
--- a/src/applications/releeph/controller/branch/ReleephBranchEditController.php
+++ b/src/applications/releeph/controller/branch/ReleephBranchEditController.php
@@ -1,108 +1,114 @@
 <?php
 
 final class ReleephBranchEditController extends ReleephBranchController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('branchID');
 
     $branch = id(new ReleephBranchQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->withIDs(array($id))
       ->executeOne();
     if (!$branch) {
       return new Aphront404Response();
     }
     $this->setBranch($branch);
 
     $symbolic_name = $request->getStr(
       'symbolicName',
       $branch->getSymbolicName());
 
     if ($request->isFormPost()) {
       $existing_with_same_symbolic_name =
         id(new ReleephBranch())
           ->loadOneWhere(
               'id != %d AND releephProjectID = %d AND symbolicName = %s',
               $branch->getID(),
               $branch->getReleephProjectID(),
               $symbolic_name);
 
       $branch->openTransaction();
       $branch->setSymbolicName($symbolic_name);
 
       if ($existing_with_same_symbolic_name) {
         $existing_with_same_symbolic_name
           ->setSymbolicName(null)
           ->save();
       }
 
       $branch->save();
       $branch->saveTransaction();
 
       return id(new AphrontRedirectResponse())
         ->setURI($this->getBranchViewURI($branch));
     }
 
     $phids = array();
 
     $phids[] = $creator_phid = $branch->getCreatedByUserPHID();
     $phids[] = $cut_commit_phid = $branch->getCutPointCommitPHID();
 
     $handles = id(new PhabricatorHandleQuery())
       ->setViewer($request->getUser())
       ->withPHIDs($phids)
       ->execute();
 
     $form = id(new AphrontFormView())
       ->setUser($request->getUser())
       ->appendChild(
         id(new AphrontFormStaticControl())
         ->setLabel(pht('Branch Name'))
         ->setValue($branch->getName()))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Cut Point'))
           ->setValue($handles[$cut_commit_phid]->renderLink()))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Created By'))
           ->setValue($handles[$creator_phid]->renderLink()))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Symbolic Name'))
           ->setName('symbolicName')
           ->setValue($symbolic_name)
           ->setCaption(pht(
             'Mutable alternate name, for easy reference, (e.g. "LATEST")')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($this->getBranchViewURI($branch))
           ->setValue(pht('Save Branch')));
 
     $title = pht(
-      'Edit Branch %s',
+      'Edit Branch: %s',
       $branch->getDisplayNameWithDetail());
 
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Branch'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+      ->appendChild($form);
+
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit'));
+    $crumbs->setBorder(true);
 
-    $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
-      ->appendChild($form);
+    $header = id(new PHUIHeaderView())
+      ->setHeader(pht('Edit Branch'))
+      ->setHeaderIcon('fa-pencil');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 }
diff --git a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php
index a77cdf8fb..5a07a5c87 100644
--- a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php
+++ b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php
@@ -1,41 +1,41 @@
 <?php
 
 final class ReleephBranchHistoryController extends ReleephBranchController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('branchID');
 
     $branch = id(new ReleephBranchQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$branch) {
       return new Aphront404Response();
     }
     $this->setBranch($branch);
 
     $timeline = $this->buildTransactionTimeline(
       $branch,
       new ReleephBranchTransactionQuery());
     $timeline
       ->setShouldTerminate(true);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('History'));
+    $crumbs->setBorder(true);
+
+    $title = pht('Branch History');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($timeline);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $timeline,
-      ),
-      array(
-        'title' => pht('Branch History'),
-      ));
   }
 
 }
diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php
index 2aedf8cdf..12da2ea3f 100644
--- a/src/applications/releeph/controller/product/ReleephProductCreateController.php
+++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php
@@ -1,132 +1,140 @@
 <?php
 
 final class ReleephProductCreateController extends ReleephProductController {
 
   public function handleRequest(AphrontRequest $request) {
     $name = trim($request->getStr('name'));
     $trunk_branch = trim($request->getStr('trunkBranch'));
     $repository_phid = $request->getStr('repositoryPHID');
 
     $e_name = true;
     $e_trunk_branch = true;
     $errors = array();
 
     if ($request->isFormPost()) {
       if (!$name) {
         $e_name = pht('Required');
         $errors[] = pht(
           'Your product should have a simple, descriptive name.');
       }
 
       if (!$trunk_branch) {
         $e_trunk_branch = pht('Required');
         $errors[] = pht(
           'You must specify which branch you will be picking from.');
       }
 
       $pr_repository = id(new PhabricatorRepositoryQuery())
         ->setViewer($request->getUser())
         ->withPHIDs(array($repository_phid))
         ->executeOne();
 
 
       if (!$errors) {
         $releeph_product = id(new ReleephProject())
           ->setName($name)
           ->setTrunkBranch($trunk_branch)
           ->setRepositoryPHID($pr_repository->getPHID())
           ->setCreatedByUserPHID($request->getUser()->getPHID())
           ->setIsActive(1);
 
         try {
           $releeph_product->save();
 
           return id(new AphrontRedirectResponse())
             ->setURI($releeph_product->getURI());
         } catch (AphrontDuplicateKeyQueryException $ex) {
           $e_name = pht('Not Unique');
           $errors[] = pht('Another product already uses this name.');
         }
       }
     }
 
     $repo_options = $this->getRepositorySelectOptions();
 
     $product_name_input = id(new AphrontFormTextControl())
       ->setLabel(pht('Name'))
       ->setDisableAutocomplete(true)
       ->setName('name')
       ->setValue($name)
       ->setError($e_name)
       ->setCaption(pht('A name like "Thrift" but not "Thrift releases".'));
 
     $repository_input = id(new AphrontFormSelectControl())
       ->setLabel(pht('Repository'))
       ->setName('repositoryPHID')
       ->setValue($repository_phid)
       ->setOptions($repo_options);
 
     $branch_name_preview = id(new ReleephBranchPreviewView())
       ->setLabel(pht('Example Branch'))
       ->addControl('projectName', $product_name_input)
       ->addControl('repositoryPHID', $repository_input)
       ->addStatic('template', '')
       ->addStatic('isSymbolic', false);
 
     $form = id(new AphrontFormView())
       ->setUser($request->getUser())
       ->appendChild($product_name_input)
       ->appendChild($repository_input)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Trunk'))
           ->setName('trunkBranch')
           ->setValue($trunk_branch)
           ->setError($e_trunk_branch)
           ->setCaption(pht(
             'The development branch, from which requests will be picked.')))
       ->appendChild($branch_name_preview)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton('/releeph/project/')
           ->setValue(pht('Create Release Product')));
 
-    $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Create New Product'))
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Product'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
+    $title = pht('Create New Product');
+
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('New Product'));
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-plus-square');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-      ),
-      array(
-        'title' => pht('Create New Product'),
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
   }
 
   private function getRepositorySelectOptions() {
     $repos = id(new PhabricatorRepositoryQuery())
       ->setViewer($this->getRequest()->getUser())
       ->execute();
 
     $repos = msort($repos, 'getName');
     $repos = mpull($repos, null, 'getID');
 
     $choices = array();
 
     foreach ($repos as $repo_id => $repo) {
       $repo_name = $repo->getName();
       $display = $repo->getDisplayName();
       $choices[$repo->getPHID()] = "{$display} ({$repo_name})";
     }
 
     asort($choices);
     return $choices;
   }
 
 }
diff --git a/src/applications/releeph/controller/product/ReleephProductEditController.php b/src/applications/releeph/controller/product/ReleephProductEditController.php
index 6a58a39bd..7938f0d93 100644
--- a/src/applications/releeph/controller/product/ReleephProductEditController.php
+++ b/src/applications/releeph/controller/product/ReleephProductEditController.php
@@ -1,267 +1,275 @@
 <?php
 
 final class ReleephProductEditController extends ReleephProductController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('projectID');
 
     $product = id(new ReleephProductQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
     if (!$product) {
       return new Aphront404Response();
     }
     $this->setProduct($product);
 
     $e_name = true;
     $e_trunk_branch = true;
     $e_branch_template = false;
     $errors = array();
 
     $product_name = $request->getStr('name', $product->getName());
 
     $trunk_branch = $request->getStr('trunkBranch', $product->getTrunkBranch());
     $branch_template = $request->getStr('branchTemplate');
     if ($branch_template === null) {
       $branch_template = $product->getDetail('branchTemplate');
     }
     $pick_failure_instructions = $request->getStr('pickFailureInstructions',
       $product->getDetail('pick_failure_instructions'));
     $test_paths = $request->getStr('testPaths');
     if ($test_paths !== null) {
       $test_paths = array_filter(explode("\n", $test_paths));
     } else {
       $test_paths = $product->getDetail('testPaths', array());
     }
 
     $repository_phid = $product->getRepositoryPHID();
 
     if ($request->isFormPost()) {
       $pusher_phids = $request->getArr('pushers');
 
       if (!$product_name) {
         $e_name = pht('Required');
         $errors[] =
           pht('Your Releeph product should have a simple descriptive name.');
       }
 
       if (!$trunk_branch) {
         $e_trunk_branch = pht('Required');
         $errors[] =
           pht('You must specify which branch you will be picking from.');
       }
 
       $other_releeph_products = id(new ReleephProject())
         ->loadAllWhere('id != %d', $product->getID());
       $other_releeph_product_names = mpull($other_releeph_products,
         'getName', 'getID');
 
       if (in_array($product_name, $other_releeph_product_names)) {
         $errors[] = pht('Releeph product name %s is already taken',
           $product_name);
       }
 
       foreach ($test_paths as $test_path) {
         $result = @preg_match($test_path, '');
         $is_a_valid_regexp = $result !== false;
         if (!$is_a_valid_regexp) {
           $errors[] = pht('Please provide a valid regular expression: '.
             '%s is not valid', $test_path);
         }
       }
 
       $product
         ->setName($product_name)
         ->setTrunkBranch($trunk_branch)
         ->setDetail('pushers', $pusher_phids)
         ->setDetail('pick_failure_instructions', $pick_failure_instructions)
         ->setDetail('branchTemplate', $branch_template)
         ->setDetail('testPaths', $test_paths);
 
       $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor(
         $repository_phid,
         $viewer);
 
       if ($branch_template) {
         list($branch_name, $template_errors) = id(new ReleephBranchTemplate())
           ->setCommitHandle($fake_commit_handle)
           ->setReleephProjectName($product_name)
           ->interpolate($branch_template);
 
         if ($template_errors) {
           $e_branch_template = pht('Whoopsies!');
           foreach ($template_errors as $template_error) {
             $errors[] = pht('Template error: %s', $template_error);
           }
         }
       }
 
       if (!$errors) {
         $product->save();
 
         return id(new AphrontRedirectResponse())->setURI($product->getURI());
       }
     }
 
     $pusher_phids = $request->getArr(
       'pushers',
       $product->getDetail('pushers', array()));
 
     $form = id(new AphrontFormView())
       ->setUser($request->getUser())
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Name'))
           ->setName('name')
           ->setValue($product_name)
           ->setError($e_name)
           ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')))
       ->appendChild(
         id(new AphrontFormStaticControl())
           ->setLabel(pht('Repository'))
           ->setValue(
             $product->getRepository()->getName()))
       ->appendChild(
         id(new AphrontFormStaticControl())
           ->setLabel(pht('Repository'))
           ->setValue(
             $product->getRepository()->getName()))
       ->appendChild(
         id(new AphrontFormStaticControl())
           ->setLabel(pht('Releeph Project PHID'))
           ->setValue(
             $product->getPHID()))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Trunk'))
           ->setValue($trunk_branch)
           ->setName('trunkBranch')
           ->setError($e_trunk_branch))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Pick Instructions'))
           ->setValue($pick_failure_instructions)
           ->setName('pickFailureInstructions')
           ->setCaption(
             pht('Instructions for pick failures, which will be used '.
             'in emails generated by failed picks')))
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Tests paths'))
           ->setValue(implode("\n", $test_paths))
           ->setName('testPaths')
           ->setCaption(
             pht('List of strings that all test files contain in their path '.
             'in this project. One string per line. '.
             'Examples: \'__tests__\', \'/javatests/\'...')));
 
     $branch_template_input = id(new AphrontFormTextControl())
       ->setName('branchTemplate')
       ->setValue($branch_template)
       ->setLabel(pht('Branch Template'))
       ->setError($e_branch_template)
       ->setCaption(
         pht("Leave this blank to use your installation's default."));
 
     $branch_template_preview = id(new ReleephBranchPreviewView())
       ->setLabel(pht('Preview'))
       ->addControl('template', $branch_template_input)
       ->addStatic('repositoryPHID', $repository_phid)
       ->addStatic('isSymbolic', false)
       ->addStatic('projectName', $product->getName());
 
     $form
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Pushers'))
           ->setName('pushers')
           ->setDatasource(new PhabricatorPeopleDatasource())
           ->setValue($pusher_phids))
       ->appendChild($branch_template_input)
       ->appendChild($branch_template_preview)
       ->appendRemarkupInstructions($this->getBranchHelpText());
 
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton('/releeph/product/')
           ->setValue(pht('Save')));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('Edit Releeph Product'))
+      ->setHeaderText(pht('Product'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
+    $title = pht('Edit Product');
+
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Product'));
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-pencil');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
 
-    return $this->buildStandardPageResponse(
-      array(
-        $crumbs,
-        $box,
-      ),
-      array(
-        'title' => pht('Edit Releeph Product'),
-        'device' => true,
-      ));
   }
 
   private function getBranchHelpText() {
     return <<<EOTEXT
 
 ==== Interpolations ====
 
 | Code  | Meaning
 | ----- | -------
 | `%P`  | The name of your product, with spaces changed to "-".
 | `%p`  | Like %P, but all lowercase.
 | `%Y`  | The four digit year associated with the branch date.
 | `%m`  | The two digit month.
 | `%d`  | The two digit day.
 | `%v`  | The handle of the commit where the branch was cut ("rXYZa4b3c2d1").
 | `%V`  | The abbreviated commit id where the branch was cut ("a4b3c2d1").
 | `%..` | Any other sequence interpreted by `strftime()`.
 | `%%`  | A literal percent sign.
 
 
 ==== Tips for Branch Templates ====
 
 Use a directory to separate your release branches from other branches:
 
   lang=none
   releases/%Y-%M-%d-%v
   => releases/2012-30-16-rHERGE32cd512a52b7
 
 Include a second hierarchy if you share your repository with other products:
 
   lang=none
   releases/%P/%p-release-%Y%m%d-%V
   => releases/Tintin/tintin-release-20121116-32cd512a52b7
 
 Keep your branch names simple, avoiding strange punctuation, most of which is
 forbidden or escaped anyway:
 
   lang=none, counterexample
   releases//..clown-releases..//`date --iso=seconds`-$(sudo halt)
 
 Include the date early in your template, in an order which sorts properly:
 
   lang=none
   releases/%Y%m%d-%v
   => releases/20121116-rHERGE32cd512a52b7 (good!)
 
   releases/%V-%m.%d.%Y
   => releases/32cd512a52b7-11.16.2012 (awful!)
 
 
 EOTEXT;
   }
 
 }
diff --git a/src/applications/releeph/controller/product/ReleephProductHistoryController.php b/src/applications/releeph/controller/product/ReleephProductHistoryController.php
index ebe9f1572..12d0d0b5c 100644
--- a/src/applications/releeph/controller/product/ReleephProductHistoryController.php
+++ b/src/applications/releeph/controller/product/ReleephProductHistoryController.php
@@ -1,41 +1,39 @@
 <?php
 
 final class ReleephProductHistoryController extends ReleephProductController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('projectID');
 
     $product = id(new ReleephProductQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$product) {
       return new Aphront404Response();
     }
     $this->setProduct($product);
 
     $timeline = $this->buildTransactionTimeline(
       $product,
       new ReleephProductTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('History'));
     $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $timeline,
-      ),
-      array(
-        'title' => pht('Product History'),
-      ));
+    $title = pht('Product History');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($timeline);
   }
 
 }
diff --git a/src/applications/releeph/controller/request/ReleephRequestEditController.php b/src/applications/releeph/controller/request/ReleephRequestEditController.php
index d5f518734..af7adc2c8 100644
--- a/src/applications/releeph/controller/request/ReleephRequestEditController.php
+++ b/src/applications/releeph/controller/request/ReleephRequestEditController.php
@@ -1,308 +1,320 @@
 <?php
 
 final class ReleephRequestEditController extends ReleephBranchController {
 
   public function handleRequest(AphrontRequest $request) {
     $action = $request->getURIData('action');
     $request_id = $request->getURIData('requestID');
     $branch_id = $request->getURIData('branchID');
     $viewer = $request->getViewer();
 
     if ($request_id) {
       $pull = id(new ReleephRequestQuery())
         ->setViewer($viewer)
         ->withIDs(array($request_id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$pull) {
         return new Aphront404Response();
       }
 
       $branch = $pull->getBranch();
 
       $is_edit = true;
     } else {
       $branch = id(new ReleephBranchQuery())
         ->setViewer($viewer)
         ->withIDs(array($branch_id))
         ->executeOne();
       if (!$branch) {
         return new Aphront404Response();
       }
 
       $pull = id(new ReleephRequest())
         ->setRequestUserPHID($viewer->getPHID())
         ->setBranchID($branch->getID())
         ->setInBranch(0)
         ->attachBranch($branch);
 
       $is_edit = false;
     }
     $this->setBranch($branch);
 
     $product = $branch->getProduct();
 
     $request_identifier = $request->getStr('requestIdentifierRaw');
     $e_request_identifier = true;
 
     // Load all the ReleephFieldSpecifications
     $selector = $branch->getProduct()->getReleephFieldSelector();
     $fields = $selector->getFieldSpecifications();
     foreach ($fields as $field) {
       $field
         ->setReleephProject($product)
         ->setReleephBranch($branch)
         ->setReleephRequest($pull);
     }
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $pull,
       PhabricatorCustomField::ROLE_EDIT);
     foreach ($field_list->getFields() as $field) {
       $field
         ->setReleephProject($product)
         ->setReleephBranch($branch)
         ->setReleephRequest($pull);
     }
     $field_list->readFieldsFromStorage($pull);
 
 
     if ($branch_id) {
       $cancel_uri = $this->getApplicationURI('branch/'.$branch_id.'/');
     } else {
       $cancel_uri = '/'.$pull->getMonogram();
     }
 
     // Make edits
     $errors = array();
     if ($request->isFormPost()) {
       $xactions = array();
 
       // The commit-identifier being requested...
       if (!$is_edit) {
         if ($request_identifier ===
           ReleephRequestTypeaheadControl::PLACEHOLDER) {
 
           $errors[] = pht('No commit ID was provided.');
           $e_request_identifier = pht('Required');
         } else {
           $pr_commit = null;
           $finder = id(new ReleephCommitFinder())
             ->setUser($viewer)
             ->setReleephProject($product);
           try {
             $pr_commit = $finder->fromPartial($request_identifier);
           } catch (Exception $e) {
             $e_request_identifier = pht('Invalid');
             $errors[] = pht(
               'Request %s is probably not a valid commit.',
               $request_identifier);
             $errors[] = $e->getMessage();
           }
 
           if (!$errors) {
             $object_phid = $finder->getRequestedObjectPHID();
             if (!$object_phid) {
               $object_phid = $pr_commit->getPHID();
             }
 
             $pull->setRequestedObjectPHID($object_phid);
           }
         }
 
         if (!$errors) {
           $existing = id(new ReleephRequest())
             ->loadOneWhere('requestCommitPHID = %s AND branchID = %d',
                 $pr_commit->getPHID(), $branch->getID());
           if ($existing) {
             return id(new AphrontRedirectResponse())
               ->setURI('/releeph/request/edit/'.$existing->getID().
                 '?existing=1');
           }
 
           $xactions[] = id(new ReleephRequestTransaction())
             ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
             ->setNewValue($pr_commit->getPHID());
 
           $xactions[] = id(new ReleephRequestTransaction())
             ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
             // To help hide these implicit intents...
             ->setMetadataValue('isRQCreate', true)
             ->setMetadataValue('userPHID', $viewer->getPHID())
             ->setMetadataValue(
               'isAuthoritative',
               $product->isAuthoritative($viewer))
             ->setNewValue(ReleephRequest::INTENT_WANT);
         }
       }
 
       // TODO: This should happen implicitly while building transactions
       // instead.
       foreach ($field_list->getFields() as $field) {
         $field->readValueFromRequest($request);
       }
 
       if (!$errors) {
         foreach ($fields as $field) {
           if ($field->isEditable()) {
             try {
               $data = $request->getRequestData();
               $value = idx($data, $field->getRequiredStorageKey());
               $field->validate($value);
               $xactions[] = id(new ReleephRequestTransaction())
                 ->setTransactionType(ReleephRequestTransaction::TYPE_EDIT_FIELD)
                 ->setMetadataValue('fieldClass', get_class($field))
                 ->setNewValue($value);
             } catch (ReleephFieldParseException $ex) {
               $errors[] = $ex->getMessage();
             }
           }
         }
       }
 
       if (!$errors) {
         $editor = id(new ReleephRequestTransactionalEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request);
         $editor->applyTransactions($pull, $xactions);
         return id(new AphrontRedirectResponse())->setURI($cancel_uri);
       }
     }
 
     $handle_phids = array(
       $pull->getRequestUserPHID(),
       $pull->getRequestCommitPHID(),
     );
     $handle_phids = array_filter($handle_phids);
     if ($handle_phids) {
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs($handle_phids)
         ->execute();
     } else {
       $handles = array();
     }
 
     $age_string = '';
     if ($is_edit) {
       $age_string = phutil_format_relative_time(
         time() - $pull->getDateCreated()).' ago';
     }
 
     // Warn the user if we've been redirected here because we tried to
     // re-request something.
     $notice_view = null;
     if ($request->getInt('existing')) {
       $notice_messages = array(
         pht('You are editing an existing pick request!'),
         pht(
           'Requested %s by %s',
           $age_string,
           $handles[$pull->getRequestUserPHID()]->renderLink()),
       );
       $notice_view = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
         ->setErrors($notice_messages);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer);
 
     if ($is_edit) {
       $form
         ->appendChild(
           id(new AphrontFormMarkupControl())
             ->setLabel(pht('Original Commit'))
             ->setValue(
               $handles[$pull->getRequestCommitPHID()]->renderLink()))
         ->appendChild(
           id(new AphrontFormMarkupControl())
             ->setLabel(pht('Requestor'))
             ->setValue(hsprintf(
               '%s %s',
               $handles[$pull->getRequestUserPHID()]->renderLink(),
               $age_string)));
     } else {
       $origin = null;
       $diff_rev_id = $request->getStr('D');
       if ($diff_rev_id) {
         $diff_rev = id(new DifferentialRevisionQuery())
           ->setViewer($viewer)
           ->withIDs(array($diff_rev_id))
           ->executeOne();
         $origin = '/D'.$diff_rev->getID();
         $title = sprintf(
           'D%d: %s',
           $diff_rev_id,
           $diff_rev->getTitle());
         $form
           ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id)
           ->appendChild(
             id(new AphrontFormStaticControl())
               ->setLabel(pht('Diff'))
               ->setValue($title));
       } else {
         $origin = $branch->getURI();
         $repo = $product->getRepository();
         $branch_cut_point = id(new PhabricatorRepositoryCommit())
           ->loadOneWhere(
               'phid = %s',
               $branch->getCutPointCommitPHID());
         $form->appendChild(
           id(new ReleephRequestTypeaheadControl())
             ->setName('requestIdentifierRaw')
             ->setLabel(pht('Commit ID'))
             ->setRepo($repo)
             ->setValue($request_identifier)
             ->setError($e_request_identifier)
             ->setStartTime($branch_cut_point->getEpoch())
             ->setCaption(
               pht(
                 'Start typing to autocomplete on commit title, '.
                 'or give a Phabricator commit identifier like rFOO1234.')));
       }
     }
 
     $field_list->appendFieldsToForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
 
     if ($is_edit) {
       $title = pht('Edit Pull Request');
       $submit_name = pht('Save');
+      $header_icon = 'fa-pencil';
 
       $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram());
       $crumbs->addTextCrumb(pht('Edit'));
     } else {
       $title = pht('Create Pull Request');
       $submit_name = pht('Create Pull Request');
+      $header_icon = 'fa-plus-square';
 
       $crumbs->addTextCrumb(pht('New Pull Request'));
     }
 
     $form->appendChild(
       id(new AphrontFormSubmitControl())
         ->addCancelButton($cancel_uri, pht('Cancel'))
         ->setValue($submit_name));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Request'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $notice_view,
         $box,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 }
diff --git a/src/applications/releeph/controller/request/ReleephRequestViewController.php b/src/applications/releeph/controller/request/ReleephRequestViewController.php
index 694505cd2..c404e3157 100644
--- a/src/applications/releeph/controller/request/ReleephRequestViewController.php
+++ b/src/applications/releeph/controller/request/ReleephRequestViewController.php
@@ -1,93 +1,101 @@
 <?php
 
 final class ReleephRequestViewController
   extends ReleephBranchController {
 
   public function handleRequest(AphrontRequest $request) {
     $id = $request->getURIData('requestID');
     $viewer = $request->getViewer();
 
     $pull = id(new ReleephRequestQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$pull) {
       return new Aphront404Response();
     }
     $this->setBranch($pull->getBranch());
 
     // Redirect older URIs to new "Y" URIs.
     // TODO: Get rid of this eventually.
     $actual_path = $request->getRequestURI()->getPath();
     $expect_path = '/'.$pull->getMonogram();
     if ($actual_path != $expect_path) {
       return id(new AphrontRedirectResponse())->setURI($expect_path);
     }
 
     // TODO: Break this 1:1 stuff?
     $branch = $pull->getBranch();
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $pull,
       PhabricatorCustomField::ROLE_VIEW);
 
     $field_list
       ->setViewer($viewer)
       ->readFieldsFromStorage($pull);
 
     // TODO: This should be more modern and general.
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer);
     foreach ($field_list->getFields() as $field) {
       if ($field->shouldMarkup()) {
         $field->setMarkupEngine($engine);
       }
     }
     $engine->process();
 
     $pull_box = id(new ReleephRequestView())
       ->setUser($viewer)
       ->setCustomFields($field_list)
       ->setPullRequest($pull);
 
     $timeline = $this->buildTransactionTimeline(
       $pull,
       new ReleephRequestTransactionQuery());
 
     $add_comment_header = pht('Plea or Yield');
 
     $draft = PhabricatorDraft::newFromUserAndKey(
       $viewer,
       $pull->getPHID());
 
     $title = hsprintf(
       '%s %s',
       $pull->getMonogram(),
       $pull->getSummaryForDisplay());
 
     $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($pull->getPHID())
       ->setDraft($draft)
       ->setHeaderText($add_comment_header)
       ->setAction($this->getApplicationURI(
         '/request/comment/'.$pull->getID().'/'))
       ->setSubmitButtonName(pht('Comment'));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram());
+    $crumbs->setBorder(true);
 
-    return $this->buildStandardPageResponse(
-      array(
-        $crumbs,
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon('fa-flag-checkered');
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $pull_box,
         $timeline,
         $add_comment_form,
-      ),
-      array(
-        'title' => $title,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
 
 }
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
index a754f3c3d..9d987636b 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php
@@ -1,200 +1,200 @@
 <?php
 
 final class PhabricatorRepositoryManagementUpdateWorkflow
   extends PhabricatorRepositoryManagementWorkflow {
 
   private $verbose;
 
   public function setVerbose($verbose) {
     $this->verbose = $verbose;
     return $this;
   }
 
   public function getVerbose() {
     return $this->verbose;
   }
 
   protected function didConstruct() {
     $this
       ->setName('update')
       ->setExamples('**update** [options] __repository__')
       ->setSynopsis(
         pht(
           'Update __repository__. This performs the __pull__, __discover__, '.
-          '__ref__ and __mirror__ operations and is primarily an internal '.
+          '__refs__ and __mirror__ operations and is primarily an internal '.
           'workflow.'))
       ->setArguments(
         array(
           array(
             'name'        => 'verbose',
             'help'        => pht('Show additional debugging information.'),
           ),
           array(
             'name'        => 'no-discovery',
             'help'        => pht('Do not perform discovery.'),
           ),
           array(
             'name'        => 'repos',
             'wildcard'    => true,
           ),
         ));
   }
 
   public function execute(PhutilArgumentParser $args) {
     $this->setVerbose($args->getArg('verbose'));
     $console = PhutilConsole::getConsole();
 
     $repos = $this->loadRepositories($args, 'repos');
     if (count($repos) !== 1) {
       throw new PhutilArgumentUsageException(
         pht('Specify exactly one repository to update.'));
     }
 
     $repository = head($repos);
 
     try {
       $lock_name = 'repository.update:'.$repository->getID();
       $lock = PhabricatorGlobalLock::newLock($lock_name);
 
       try {
         $lock->lock();
       } catch (PhutilLockException $ex) {
         throw new PhutilProxyException(
           pht(
             'Another process is currently holding the update lock for '.
             'repository "%s". Repositories may only be updated by one '.
             'process at a time. This can happen if you are running multiple '.
             'copies of the daemons. This can also happen if you manually '.
             'update a repository while the daemons are also updating it '.
             '(in this case, just try again in a few moments).',
             $repository->getMonogram()),
           $ex);
       }
 
       try {
         $no_discovery = $args->getArg('no-discovery');
 
         id(new PhabricatorRepositoryPullEngine())
           ->setRepository($repository)
           ->setVerbose($this->getVerbose())
           ->pullRepository();
 
         if ($no_discovery) {
           $lock->unlock();
           return;
         }
 
         // TODO: It would be nice to discover only if we pulled something, but
         // this isn't totally trivial. It's slightly more complicated with
         // hosted repositories, too.
 
         $repository->writeStatusMessage(
           PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
           null);
 
         $this->discoverRepository($repository);
 
         $this->checkIfRepositoryIsFullyImported($repository);
 
         $this->updateRepositoryRefs($repository);
 
         $this->mirrorRepository($repository);
 
         $repository->writeStatusMessage(
           PhabricatorRepositoryStatusMessage::TYPE_FETCH,
           PhabricatorRepositoryStatusMessage::CODE_OKAY);
       } catch (Exception $ex) {
         $lock->unlock();
         throw $ex;
       }
     } catch (Exception $ex) {
       $repository->writeStatusMessage(
         PhabricatorRepositoryStatusMessage::TYPE_FETCH,
         PhabricatorRepositoryStatusMessage::CODE_ERROR,
         array(
           'message' => pht(
             'Error updating working copy: %s', $ex->getMessage()),
         ));
       throw $ex;
     }
 
     $lock->unlock();
 
     $console->writeOut(
       pht(
         'Updated repository **%s**.',
         $repository->getMonogram())."\n");
 
     return 0;
   }
 
   private function discoverRepository(PhabricatorRepository $repository) {
     $refs = id(new PhabricatorRepositoryDiscoveryEngine())
       ->setRepository($repository)
       ->setVerbose($this->getVerbose())
       ->discoverCommits();
 
     return (bool)count($refs);
   }
 
   private function mirrorRepository(PhabricatorRepository $repository) {
     try {
       id(new PhabricatorRepositoryMirrorEngine())
         ->setRepository($repository)
         ->pushToMirrors();
     } catch (Exception $ex) {
       // TODO: We should report these into the UI properly, but for now just
       // complain. These errors are much less severe than pull errors.
       $proxy = new PhutilProxyException(
         pht(
           'Error while pushing "%s" repository to mirrors.',
           $repository->getMonogram()),
         $ex);
       phlog($proxy);
     }
   }
 
   private function updateRepositoryRefs(PhabricatorRepository $repository) {
     id(new PhabricatorRepositoryRefEngine())
       ->setRepository($repository)
       ->updateRefs();
   }
 
   private function checkIfRepositoryIsFullyImported(
     PhabricatorRepository $repository) {
 
     // Check if the repository has the "Importing" flag set. We want to clear
     // the flag if we can.
     $importing = $repository->getDetail('importing');
     if (!$importing) {
       // This repository isn't marked as "Importing", so we're done.
       return;
     }
 
     // Look for any commit which hasn't imported.
     $unparsed_commit = queryfx_one(
       $repository->establishConnection('r'),
       'SELECT * FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d
         LIMIT 1',
       id(new PhabricatorRepositoryCommit())->getTableName(),
       $repository->getID(),
       PhabricatorRepositoryCommit::IMPORTED_ALL,
       PhabricatorRepositoryCommit::IMPORTED_ALL);
     if ($unparsed_commit) {
       // We found a commit which still needs to import, so we can't clear the
       // flag.
       return;
     }
 
     // Clear the "importing" flag.
     $repository->openTransaction();
       $repository->beginReadLocking();
         $repository = $repository->reload();
         $repository->setDetail('importing', false);
         $repository->save();
       $repository->endReadLocking();
     $repository->saveTransaction();
   }
 
 
 }
diff --git a/src/applications/search/controller/PhabricatorSearchEditController.php b/src/applications/search/controller/PhabricatorSearchEditController.php
index da8991961..c7f6c860e 100644
--- a/src/applications/search/controller/PhabricatorSearchEditController.php
+++ b/src/applications/search/controller/PhabricatorSearchEditController.php
@@ -1,98 +1,107 @@
 <?php
 
 final class PhabricatorSearchEditController
   extends PhabricatorSearchBaseController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $saved_query = id(new PhabricatorSavedQueryQuery())
       ->setViewer($viewer)
       ->withQueryKeys(array($request->getURIData('queryKey')))
       ->executeOne();
 
     if (!$saved_query) {
       return new Aphront404Response();
     }
 
     $engine = $saved_query->newEngine()->setViewer($viewer);
 
     $complete_uri = $engine->getQueryManagementURI();
     $cancel_uri = $complete_uri;
 
     $named_query = id(new PhabricatorNamedQueryQuery())
       ->setViewer($viewer)
       ->withQueryKeys(array($saved_query->getQueryKey()))
       ->withUserPHIDs(array($viewer->getPHID()))
       ->executeOne();
     if (!$named_query) {
       $named_query = id(new PhabricatorNamedQuery())
         ->setUserPHID($viewer->getPHID())
         ->setQueryKey($saved_query->getQueryKey())
         ->setEngineClassName($saved_query->getEngineClassName());
 
       // If we haven't saved the query yet, this is a "Save..." operation, so
       // take the user back to the query if they cancel instead of back to the
       // management interface.
       $cancel_uri = $engine->getQueryResultsPageURI(
         $saved_query->getQueryKey());
     }
 
     $e_name = true;
     $errors = array();
 
     if ($request->isFormPost()) {
       $named_query->setQueryName($request->getStr('name'));
       if (!strlen($named_query->getQueryName())) {
         $e_name = pht('Required');
         $errors[] = pht('You must name the query.');
       } else {
         $e_name = null;
       }
 
       if (!$errors) {
         $named_query->save();
         return id(new AphrontRedirectResponse())->setURI($complete_uri);
       }
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer);
 
     $form->appendChild(
       id(new AphrontFormTextControl())
         ->setName('name')
         ->setLabel(pht('Query Name'))
         ->setValue($named_query->getQueryName())
         ->setError($e_name));
 
     $form->appendChild(
       id(new AphrontFormSubmitControl())
         ->setValue(pht('Save Query'))
         ->addCancelButton($cancel_uri));
 
     if ($named_query->getID()) {
       $title = pht('Edit Saved Query');
+      $header_icon = 'fa-pencil';
     } else {
       $title = pht('Save Query');
+      $header_icon = 'fa-search';
     }
 
     $form_box = id(new PHUIObjectBoxView())
-      ->setHeaderText($title)
+      ->setHeaderText(pht('Query'))
       ->setFormErrors($errors)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title);
+    $crumbs->setBorder(true);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setHeaderIcon($header_icon);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($form_box);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $form_box,
-      ),
-      array(
-        'title' => $title,
-      ));
   }
 
 }
diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
index f411c208b..b5cd52471 100644
--- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
+++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
@@ -1,595 +1,601 @@
 <?php
 
 abstract class PhabricatorSearchEngineAPIMethod
   extends ConduitAPIMethod {
 
   abstract public function newSearchEngine();
 
   final public function getQueryMaps($query) {
     $maps = $this->getCustomQueryMaps($query);
 
     // Make sure we emit empty maps as objects, not lists.
     foreach ($maps as $key => $map) {
       if (!$map) {
         $maps[$key] = (object)$map;
       }
     }
 
     if (!$maps) {
       $maps = (object)$maps;
     }
 
     return $maps;
   }
 
   protected function getCustomQueryMaps($query) {
     return array();
   }
 
   public function getApplication() {
     $engine = $this->newSearchEngine();
     $class = $engine->getApplicationClassName();
     return PhabricatorApplication::getByClass($class);
   }
 
   public function getMethodStatus() {
     return self::METHOD_STATUS_UNSTABLE;
   }
 
   public function getMethodStatusDescription() {
     return pht('ApplicationSearch methods are highly unstable.');
   }
 
   final protected function defineParamTypes() {
     return array(
       'queryKey' => 'optional string',
       'constraints' => 'optional map<string, wild>',
       'attachments' => 'optional map<string, bool>',
       'order' => 'optional order',
     ) + $this->getPagerParamTypes();
   }
 
   final protected function defineReturnType() {
     return 'map<string, wild>';
   }
 
   final protected function execute(ConduitAPIRequest $request) {
     $engine = $this->newSearchEngine()
       ->setViewer($request->getUser());
 
     return $engine->buildConduitResponse($request, $this);
   }
 
   final public function getMethodDescription() {
     return pht(
       'This is a standard **ApplicationSearch** method which will let you '.
       'list, query, or search for objects. For documentation on these '.
       'endpoints, see **[[ %s | Conduit API: Using Search Endpoints ]]**.',
       PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints'));
   }
 
   final public function getMethodDocumentation() {
     $viewer = $this->getViewer();
 
     $engine = $this->newSearchEngine()
       ->setViewer($viewer);
 
     $query = $engine->newQuery();
 
     $out = array();
 
     $out[] = $this->buildQueriesBox($engine);
     $out[] = $this->buildConstraintsBox($engine);
     $out[] = $this->buildOrderBox($engine, $query);
     $out[] = $this->buildFieldsBox($engine);
     $out[] = $this->buildAttachmentsBox($engine);
     $out[] = $this->buildPagingBox($engine);
 
     return $out;
   }
 
   private function buildQueriesBox(
     PhabricatorApplicationSearchEngine $engine) {
     $viewer = $this->getViewer();
 
     $info = pht(<<<EOTEXT
 You can choose a builtin or saved query as a starting point for filtering
 results by selecting it with `queryKey`. If you don't specify a `queryKey`,
 the query will start with no constraints.
 
 For example, many applications have builtin queries like `"active"` or
 `"open"` to find only active or enabled results. To use a `queryKey`, specify
 it like this:
 
 ```lang=json, name="Selecting a Builtin Query"
 {
   ...
   "queryKey": "active",
   ...
 }
 ```
 
 The table below shows the keys to use to select builtin queries and your
 saved queries, but you can also use **any** query you run via the web UI as a
 starting point. You can find the key for a query by examining the URI after
 running a normal search.
 
 You can use these keys to select builtin queries and your configured saved
 queries:
 EOTEXT
       );
 
     $named_queries = $engine->loadAllNamedQueries();
 
     $rows = array();
     foreach ($named_queries as $named_query) {
       $builtin = $named_query->getIsBuiltin()
         ? pht('Builtin')
         : pht('Custom');
 
       $rows[] = array(
         $named_query->getQueryKey(),
         $named_query->getQueryName(),
         $builtin,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Query Key'),
           pht('Name'),
           pht('Builtin'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri wide',
           null,
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Builtin and Saved Queries'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildConstraintsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 You can apply custom constraints by passing a dictionary in `constraints`.
 This will let you search for specific sets of results (for example, you may
 want show only results with a certain state, status, or owner).
 
 
 If you specify both a `queryKey` and `constraints`, the builtin or saved query
 will be applied first as a starting point, then any additional values in
 `constraints` will be applied, overwriting the defaults from the original query.
 
 Specify constraints like this:
 
 ```lang=json, name="Example Custom Constraints"
 {
   ...
   "constraints": {
     "authors": ["PHID-USER-1111", "PHID-USER-2222"],
     "statuses": ["open", "closed"],
     ...
   },
   ...
 }
 ```
 
 This API endpoint supports these constraints:
 EOTEXT
       );
 
     $fields = $engine->getSearchFieldsForConduit();
 
     // As a convenience, put these fields at the very top, even if the engine
     // specifies and alternate display order for the web UI. These fields are
     // very important in the API and nearly useless in the web UI.
     $fields = array_select_keys(
       $fields,
       array('ids', 'phids')) + $fields;
 
     $rows = array();
     foreach ($fields as $field) {
       $key = $field->getConduitKey();
       $label = $field->getLabel();
 
       $type_object = $field->getConduitParameterType();
       if ($type_object) {
         $type = $type_object->getTypeName();
         $description = $field->getDescription();
       } else {
         $type = null;
         $description = phutil_tag('em', array(), pht('Not supported.'));
       }
 
       $rows[] = array(
         $key,
         $label,
         $type,
         $description,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Label'),
           pht('Type'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri',
           'prewrap',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Custom Query Constraints'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildOrderBox(
     PhabricatorApplicationSearchEngine $engine,
     $query) {
 
     $orders_info = pht(<<<EOTEXT
 Use `order` to choose an ordering for the results.
 
 Either specify a single key from the builtin orders (these are a set of
 meaningful, high-level, human-readable orders) or specify a custom list of
 low-level columns.
 
 To use a high-level order, choose a builtin order from the table below
 and specify it like this:
 
 ```lang=json, name="Choosing a Result Order"
 {
   ...
   "order": "newest",
   ...
 }
 ```
 
 These builtin orders are available:
 EOTEXT
       );
 
     $orders = $query->getBuiltinOrders();
 
     $rows = array();
     foreach ($orders as $key => $order) {
       $rows[] = array(
         $key,
         $order['name'],
         implode(', ', $order['vector']),
       );
     }
 
     $orders_table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Description'),
           pht('Columns'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           '',
           'wide',
         ));
 
     $columns_info = pht(<<<EOTEXT
 You can choose a low-level column order instead. To do this, provide a list
 of columns instead of a single key. This is an advanced feature.
 
 In a custom column order:
 
   - each column may only be specified once;
   - each column may be prefixed with `-` to invert the order;
   - the last column must be a unique column, usually `id`; and
   - no column other than the last may be unique.
 
 To use a low-level order, choose a sequence of columns and specify them like
 this:
 
 ```lang=json, name="Using a Custom Order"
 {
   ...
   "order": ["color", "-name", "id"],
   ...
 }
 ```
 
 These low-level columns are available:
 EOTEXT
       );
 
     $columns = $query->getOrderableColumns();
     $rows = array();
     foreach ($columns as $key => $column) {
       $rows[] = array(
         $key,
         idx($column, 'unique') ? pht('Yes') : pht('No'),
       );
     }
 
     $columns_table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Unique'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           'wide',
         ));
 
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Result Ordering'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($orders_info))
       ->appendChild($orders_table)
       ->appendChild($this->buildRemarkup($columns_info))
       ->appendChild($columns_table);
   }
 
   private function buildFieldsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 Objects matching your query are returned as a list of dictionaries in the
 `data` property of the results. Each dictionary has some metadata and a
 `fields` key, which contains the information abou the object that most callers
 will be interested in.
 
 For example, the results may look something like this:
 
 ```lang=json, name="Example Results"
 {
   ...
   "data": [
     {
       "id": 123,
       "phid": "PHID-WXYZ-1111",
       "fields": {
         "name": "First Example Object",
         "authorPHID": "PHID-USER-2222"
       }
     },
     {
       "id": 124,
       "phid": "PHID-WXYZ-3333",
       "fields": {
         "name": "Second Example Object",
         "authorPHID": "PHID-USER-4444"
       }
     },
     ...
   ]
   ...
 }
 ```
 
 This result structure is standardized across all search methods, but the
 available fields differ from application to application.
 
 These are the fields available on this object type:
 EOTEXT
       );
 
     $specs = $engine->getAllConduitFieldSpecifications();
 
     $rows = array();
     foreach ($specs as $key => $spec) {
       $type = $spec->getType();
       $description = $spec->getDescription();
       $rows[] = array(
         $key,
         $type,
         $description,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Type'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           'mono',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Object Fields'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildAttachmentsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 By default, only basic information about objects is returned. If you want
 more extensive information, you can use available `attachments` to get more
 information in the results (like subscribers and projects).
 
 Generally, requesting more information means the query executes more slowly
 and returns more data (in some cases, much more data). You should normally
 request only the data you need.
 
 To request extra data, specify which attachments you want in the `attachments`
 parameter:
 
 ```lang=json, name="Example Attachments Request"
 {
   ...
   "attachments": {
     "subscribers": true
   },
   ...
 }
 ```
 
 This example specifies that results should include information about
 subscribers. In the return value, each object will now have this information
 filled out in the corresponding `attachments` value:
 
 ```lang=json, name="Example Attachments Result"
 {
   ...
   "data": [
     {
       ...
       "attachments": {
         "subscribers": {
           "subscriberPHIDs": [
             "PHID-WXYZ-2222",
           ],
           "subscriberCount": 1,
           "viewerIsSubscribed": false
         }
       },
       ...
     },
     ...
   ],
   ...
 }
 ```
 
 These attachments are available:
 EOTEXT
       );
 
     $attachments = $engine->getConduitSearchAttachments();
 
     $rows = array();
     foreach ($attachments as $key => $attachment) {
       $rows[] = array(
         $key,
         $attachment->getAttachmentName(),
         $attachment->getAttachmentDescription(),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Name'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Attachments'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildPagingBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 Queries are limited to returning 100 results at a time. If you want fewer
 results than this, you can use `limit` to specify a smaller limit.
 
 If you want more results, you'll need to make additional queries to retrieve
 more pages of results.
 
 The result structure contains a `cursor` key with information you'll need in
 order to fetch the next page of results. After an initial query, it will
 usually look something like this:
 
 ```lang=json, name="Example Cursor Result"
 {
   ...
   "cursor": {
     "limit": 100,
     "after": "1234",
     "before": null,
     "order": null
   }
   ...
 }
 ```
 
 The `limit` and `order` fields are describing the effective limit and order the
 query was executed with, and are usually not of much interest. The `after` and
 `before` fields give you cursors which you can pass when making another API
 call in order to get the next (or previous) page of results.
 
 To get the next page of results, repeat your API call with all the same
 parameters as the original call, but pass the `after` cursor you received from
 the first call in the `after` parameter when making the second call.
 
 If you do things correctly, you should get the second page of results, and
 a cursor structure like this:
 
 ```lang=json, name="Second Result Page"
 {
   ...
   "cursor": {
     "limit": 5,
     "after": "4567",
     "before": "7890",
     "order": null
   }
   ...
 }
 ```
 
 You can now continue to the third page of results by passing the new `after`
 cursor to the `after` parameter in your third call, or return to the previous
 page of results by passing the `before` cursor to the `before` parameter. This
 might be useful if you are rendering a web UI for a user and want to provide
 "Next Page" and "Previous Page" links.
 
 If `after` is `null`, there is no next page of results available. Likewise,
 if `before` is `null`, there are no previous results available.
 EOTEXT
       );
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Paging and Limits'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($info));
   }
 
   private function buildRemarkup($remarkup) {
     $viewer = $this->getViewer();
 
     $view = new PHUIRemarkupView($viewer, $remarkup);
 
     return id(new PHUIBoxView())
       ->appendChild($view)
       ->addPadding(PHUI::PADDING_LARGE);
   }
 }
diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php
index 728e2131a..bf9b17103 100644
--- a/src/applications/settings/controller/PhabricatorSettingsMainController.php
+++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php
@@ -1,147 +1,148 @@
 <?php
 
 final class PhabricatorSettingsMainController
   extends PhabricatorController {
 
   private $user;
 
   private function getUser() {
     return $this->user;
   }
 
   private function isSelf() {
     $viewer_phid = $this->getViewer()->getPHID();
     $user_phid = $this->getUser()->getPHID();
     return ($viewer_phid == $user_phid);
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
     $key = $request->getURIData('key');
 
     if ($id) {
       $user = id(new PhabricatorPeopleQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
 
       if (!$user) {
         return new Aphront404Response();
       }
 
       $this->user = $user;
     } else {
       $this->user = $viewer;
     }
 
     $panels = $this->buildPanels();
     $nav = $this->renderSideNav($panels);
 
     $key = $nav->selectFilter($key, head($panels)->getPanelKey());
 
     $panel = $panels[$key];
     $panel->setUser($this->getUser());
     $panel->setViewer($viewer);
 
     $response = $panel->processRequest($request);
     if ($response instanceof AphrontResponse) {
       return $response;
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     if (!$this->isSelf()) {
       $crumbs->addTextCrumb(
         $this->getUser()->getUsername(),
         '/p/'.$this->getUser()->getUsername().'/');
     }
     $crumbs->addTextCrumb($panel->getPanelName());
-    $nav->appendChild(
-      array(
-        $crumbs,
-        $response,
-      ));
-
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $panel->getPanelName(),
-      ));
+
+    $title = $panel->getPanelName();
+
+    $view = id(new PHUITwoColumnView())
+      ->setNavigation($nav)
+      ->setMainColumn($response);
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($view);
+
   }
 
   private function buildPanels() {
     $panels = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorSettingsPanel')
       ->setExpandMethod('buildPanels')
       ->setUniqueMethod('getPanelKey')
       ->execute();
 
     $result = array();
     foreach ($panels as $key => $panel) {
       $panel->setUser($this->user);
 
       if (!$panel->isEnabled()) {
         continue;
       }
 
       if (!$this->isSelf()) {
         if (!$panel->isEditableByAdministrators()) {
           continue;
         }
       }
 
       if (!empty($result[$key])) {
         throw new Exception(pht(
           "Two settings panels share the same panel key ('%s'): %s, %s.",
           $key,
           get_class($panel),
           get_class($result[$key])));
       }
 
       $result[$key] = $panel;
     }
 
     $result = msort($result, 'getPanelSortKey');
 
     if (!$result) {
       throw new Exception(pht('No settings panels are available.'));
     }
 
     return $result;
   }
 
   private function renderSideNav(array $panels) {
     $nav = new AphrontSideNavFilterView();
 
     if ($this->isSelf()) {
       $base_uri = 'panel/';
     } else {
       $base_uri = $this->getUser()->getID().'/panel/';
     }
 
     $nav->setBaseURI(new PhutilURI($this->getApplicationURI($base_uri)));
 
     $group = null;
     foreach ($panels as $panel) {
       if ($panel->getPanelGroup() != $group) {
         $group = $panel->getPanelGroup();
         $nav->addLabel($group);
       }
 
       $nav->addFilter($panel->getPanelKey(), $panel->getPanelName());
     }
 
     return $nav;
   }
 
   public function buildApplicationMenu() {
     $panels = $this->buildPanels();
     return $this->renderSideNav($panels)->getMenu();
   }
 
 }
diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php
index 8319f19a6..495a0c8de 100644
--- a/src/applications/spaces/controller/PhabricatorSpacesViewController.php
+++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php
@@ -1,144 +1,144 @@
 <?php
 
 final class PhabricatorSpacesViewController
   extends PhabricatorSpacesController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
 
     $space = id(new PhabricatorSpacesNamespaceQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
     if (!$space) {
       return new Aphront404Response();
     }
 
     $curtain = $this->buildCurtain($space);
     $property_list = $this->buildPropertyListView($space);
     $title = array($space->getMonogram(), $space->getNamespaceName());
 
     $xactions = id(new PhabricatorSpacesNamespaceTransactionQuery())
       ->setViewer($viewer)
       ->withObjectPHIDs(array($space->getPHID()))
       ->execute();
 
     $timeline = $this->buildTransactionTimeline(
       $space,
       new PhabricatorSpacesNamespaceTransactionQuery());
     $timeline->setShouldTerminate(true);
 
     $header = id(new PHUIHeaderView())
       ->setUser($viewer)
       ->setHeader($space->getNamespaceName())
       ->setPolicyObject($space)
       ->setHeaderIcon('fa-th-large');
 
     if ($space->getIsArchived()) {
       $header->setStatus('fa-ban', 'red', pht('Archived'));
     } else {
       $header->setStatus('fa-check', 'bluegrey', pht('Active'));
     }
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText(pht('DETAILS'))
+      ->setHeaderText(pht('Details'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->addPropertyList($property_list);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($space->getMonogram());
     $crumbs->setBorder(true);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setMainColumn(array(
           $box,
           $timeline,
         ))
       ->setCurtain($curtain);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
 
   }
 
   private function buildPropertyListView(PhabricatorSpacesNamespace $space) {
     $viewer = $this->getRequest()->getUser();
 
     $list = id(new PHUIPropertyListView())
       ->setUser($viewer);
 
     $list->addProperty(
       pht('Default Space'),
       $space->getIsDefaultNamespace()
         ? pht('Yes')
         : pht('No'));
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $space);
 
     $list->addProperty(
       pht('Editable By'),
       $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
 
     $description = $space->getDescription();
     if (strlen($description)) {
       $description = new PHUIRemarkupView($viewer, $description);
       $list->addSectionHeader(
         pht('Description'),
         PHUIPropertyListView::ICON_SUMMARY);
       $list->addTextContent($description);
     }
 
     return $list;
   }
 
   private function buildCurtain(PhabricatorSpacesNamespace $space) {
     $viewer = $this->getRequest()->getUser();
 
     $curtain = $this->newCurtainView($space);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $space,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Edit Space'))
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI('edit/'.$space->getID().'/'))
         ->setWorkflow(!$can_edit)
         ->setDisabled(!$can_edit));
 
     $id = $space->getID();
 
     if ($space->getIsArchived()) {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Activate Space'))
           ->setIcon('fa-check')
           ->setHref($this->getApplicationURI("activate/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
     } else {
       $curtain->addAction(
         id(new PhabricatorActionView())
           ->setName(pht('Archive Space'))
           ->setIcon('fa-ban')
           ->setHref($this->getApplicationURI("archive/{$id}/"))
           ->setDisabled(!$can_edit)
           ->setWorkflow(true));
     }
 
     return $curtain;
   }
 
 }
diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php
index 19b9281d0..a0a6cbf7b 100644
--- a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php
+++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php
@@ -1,69 +1,70 @@
 <?php
 
 final class PhabricatorSubscriptionsEditEngineExtension
   extends PhabricatorEditEngineExtension {
 
   const EXTENSIONKEY = 'subscriptions.subscribers';
 
   public function getExtensionPriority() {
     return 750;
   }
 
   public function isExtensionEnabled() {
     return true;
   }
 
   public function getExtensionName() {
     return pht('Subscriptions');
   }
 
   public function supportsObject(
     PhabricatorEditEngine $engine,
     PhabricatorApplicationTransactionInterface $object) {
     return ($object instanceof PhabricatorSubscribableInterface);
   }
 
   public function buildCustomEditFields(
     PhabricatorEditEngine $engine,
     PhabricatorApplicationTransactionInterface $object) {
 
     $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS;
 
     $object_phid = $object->getPHID();
     if ($object_phid) {
       $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID(
         $object_phid);
     } else {
       $sub_phids = array();
     }
 
     $subscribers_field = id(new PhabricatorSubscribersEditField())
       ->setKey('subscriberPHIDs')
       ->setLabel(pht('Subscribers'))
       ->setEditTypeKey('subscribers')
       ->setAliases(array('subscriber', 'subscribers'))
       ->setIsCopyable(true)
       ->setUseEdgeTransactions(true)
       ->setCommentActionLabel(pht('Change Subscribers'))
+      ->setCommentActionOrder(9000)
       ->setDescription(pht('Choose subscribers.'))
       ->setTransactionType($subscribers_type)
       ->setValue($sub_phids);
 
     $subscribers_field->setViewer($engine->getViewer());
 
     $edit_add = $subscribers_field->getConduitEditType('subscribers.add')
       ->setConduitDescription(pht('Add subscribers.'));
 
     $edit_set = $subscribers_field->getConduitEditType('subscribers.set')
       ->setConduitDescription(
         pht('Set subscribers, overwriting current value.'));
 
     $edit_rem = $subscribers_field->getConduitEditType('subscribers.remove')
       ->setConduitDescription(pht('Remove subscribers.'));
 
     return array(
       $subscribers_field,
     );
   }
 
 }
diff --git a/src/applications/system/controller/PhabricatorRobotsController.php b/src/applications/system/controller/PhabricatorRobotsController.php
index bfc548fc0..e413984f5 100644
--- a/src/applications/system/controller/PhabricatorRobotsController.php
+++ b/src/applications/system/controller/PhabricatorRobotsController.php
@@ -1,37 +1,38 @@
 <?php
 
 final class PhabricatorRobotsController extends PhabricatorController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function processRequest() {
     $out = array();
 
     // Prevent indexing of '/diffusion/', since the content is not generally
     // useful to index, web spiders get stuck scraping the history of every
     // file, and much of the content is Ajaxed in anyway so spiders won't even
     // see it. These pages are also relatively expensive to generate.
 
     // Note that this still allows commits (at '/rPxxxxx') to be indexed.
     // They're probably not hugely useful, but suffer fewer of the problems
     // Diffusion suffers and are hard to omit with 'robots.txt'.
 
     $out[] = 'User-Agent: *';
     $out[] = 'Disallow: /diffusion/';
 
     // Add a small crawl delay (number of seconds between requests) for spiders
     // which respect it. The intent here is to prevent spiders from affecting
     // performance for users. The possible cost is slower indexing, but that
     // seems like a reasonable tradeoff, since most Phabricator installs are
     // probably not hugely concerned about cutting-edge SEO.
     $out[] = 'Crawl-delay: 1';
 
     $content = implode("\n", $out)."\n";
 
     return id(new AphrontPlainTextResponse())
       ->setContent($content)
-      ->setCacheDurationInSeconds(phutil_units('2 hours in seconds'));
+      ->setCacheDurationInSeconds(phutil_units('2 hours in seconds'))
+      ->setCanCDN(true);
   }
 }
diff --git a/src/applications/tokens/controller/PhabricatorTokenGivenController.php b/src/applications/tokens/controller/PhabricatorTokenGivenController.php
index 86318a885..8352c35d6 100644
--- a/src/applications/tokens/controller/PhabricatorTokenGivenController.php
+++ b/src/applications/tokens/controller/PhabricatorTokenGivenController.php
@@ -1,82 +1,80 @@
 <?php
 
 final class PhabricatorTokenGivenController extends PhabricatorTokenController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
  public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $pager = id(new AphrontCursorPagerView())
       ->readFromRequest($request);
 
     $tokens_given = id(new PhabricatorTokenGivenQuery())
       ->setViewer($viewer)
       ->executeWithCursorPager($pager);
 
     $handles = array();
     if ($tokens_given) {
       $object_phids = mpull($tokens_given, 'getObjectPHID');
       $viewer_phids = mpull($tokens_given, 'getAuthorPHID');
       $handle_phids = array_merge($object_phids, $viewer_phids);
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs($handle_phids)
         ->execute();
     }
 
     $tokens = array();
     if ($tokens_given) {
       $token_phids = mpull($tokens_given, 'getTokenPHID');
       $tokens = id(new PhabricatorTokenQuery())
         ->setViewer($viewer)
         ->withPHIDs($token_phids)
         ->execute();
       $tokens = mpull($tokens, null, 'getPHID');
     }
 
     $list = new PHUIObjectItemListView();
     foreach ($tokens_given as $token_given) {
       $handle = $handles[$token_given->getObjectPHID()];
       $token = idx($tokens, $token_given->getTokenPHID());
 
       $item = id(new PHUIObjectItemView());
       $item->setHeader($handle->getFullName());
       $item->setHref($handle->getURI());
 
       $item->addAttribute($token->renderIcon());
 
       $item->addAttribute(
         pht(
           'Given by %s on %s',
           $handles[$token_given->getAuthorPHID()]->renderLink(),
           phabricator_date($token_given->getDateCreated(), $viewer)));
 
       $list->addItem($item);
     }
     $title = pht('Tokens Given');
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setObjectList($list);
 
     $nav = $this->buildSideNav();
     $nav->setCrumbs(
       $this->buildApplicationCrumbs()
         ->addTextCrumb($title));
     $nav->selectFilter('given/');
 
     $nav->appendChild($box);
     $nav->appendChild($pager);
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
-  }
+    return $this->newPage()
+      ->setTitle($title)
+      ->appendChild($nav);
 
+  }
 
 }
diff --git a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
index 53cda0f48..bfef3cd8c 100644
--- a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
+++ b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
@@ -1,65 +1,64 @@
 <?php
 
 final class PhabricatorTokenLeaderController
   extends PhabricatorTokenController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
  public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $pager = new PHUIPagerView();
     $pager->setURI($request->getRequestURI(), 'page');
     $pager->setOffset($request->getInt('page'));
 
     $query = id(new PhabricatorTokenReceiverQuery());
     $objects = $query->setViewer($viewer)->executeWithOffsetPager($pager);
     $counts = $query->getTokenCounts();
 
     $handles = array();
     $phids = array();
     if ($counts) {
       $phids = mpull($objects, 'getPHID');
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs($phids)
         ->execute();
     }
 
     $list = new PHUIObjectItemListView();
     foreach ($phids as $object) {
       $count = idx($counts, $object, 0);
       $item = id(new PHUIObjectItemView());
       $handle = $handles[$object];
 
       $item->setHeader($handle->getFullName());
       $item->setHref($handle->getURI());
       $item->addAttribute(pht('Tokens: %s', $count));
       $list->addItem($item);
     }
 
     $title = pht('Token Leader Board');
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setObjectList($list);
 
     $nav = $this->buildSideNav();
     $nav->setCrumbs(
       $this->buildApplicationCrumbs()
         ->addTextCrumb($title));
     $nav->selectFilter('leaders/');
 
     $nav->appendChild($box);
     $nav->appendChild($pager);
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->appendChild($nav);
+
   }
 
 }
diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php
new file mode 100644
index 000000000..bc68b7b64
--- /dev/null
+++ b/src/applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php
@@ -0,0 +1,27 @@
+<?php
+
+final class PhabricatorEditEngineColumnsCommentAction
+  extends PhabricatorEditEngineCommentAction {
+
+  private $columnMap;
+
+  public function setColumnMap(array $column_map) {
+    $this->columnMap = $column_map;
+    return $this;
+  }
+
+  public function getColumnMap() {
+    return $this->columnMap;
+  }
+
+  public function getPHUIXControlType() {
+    return 'optgroups';
+  }
+
+  public function getPHUIXControlSpecification() {
+    return array(
+      'groups' => $this->getColumnMap(),
+    );
+  }
+
+}
diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php
index 548039ce1..dc676630b 100644
--- a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php
+++ b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php
@@ -1,49 +1,64 @@
 <?php
 
 abstract class PhabricatorEditEngineCommentAction extends Phobject {
 
   private $key;
   private $label;
   private $value;
   private $initialValue;
+  private $order;
 
   abstract public function getPHUIXControlType();
   abstract public function getPHUIXControlSpecification();
 
   public function setKey($key) {
     $this->key = $key;
     return $this;
   }
 
   public function getKey() {
     return $this->key;
   }
 
   public function setLabel($label) {
     $this->label = $label;
     return $this;
   }
 
   public function getLabel() {
     return $this->label;
   }
 
   public function setValue($value) {
     $this->value = $value;
     return $this;
   }
 
   public function getValue() {
     return $this->value;
   }
 
+  public function setOrder($order) {
+    $this->order = $order;
+    return $this;
+  }
+
+  public function getOrder() {
+    return $this->order;
+  }
+
+  public function getSortVector() {
+    return id(new PhutilSortVector())
+      ->addInt($this->getOrder());
+  }
+
   public function setInitialValue($initial_value) {
     $this->initialValue = $initial_value;
     return $this;
   }
 
   public function getInitialValue() {
     return $this->initialValue;
   }
 
 }
diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php
index 172f80ba3..7d8e8afb1 100644
--- a/src/applications/transactions/constants/PhabricatorTransactions.php
+++ b/src/applications/transactions/constants/PhabricatorTransactions.php
@@ -1,39 +1,40 @@
 <?php
 
 final class PhabricatorTransactions extends Phobject {
 
   const TYPE_COMMENT      = 'core:comment';
   const TYPE_SUBSCRIBERS  = 'core:subscribers';
   const TYPE_VIEW_POLICY  = 'core:view-policy';
   const TYPE_EDIT_POLICY  = 'core:edit-policy';
   const TYPE_JOIN_POLICY  = 'core:join-policy';
   const TYPE_EDGE         = 'core:edge';
   const TYPE_CUSTOMFIELD  = 'core:customfield';
   const TYPE_BUILDABLE    = 'harbormaster:buildable';
   const TYPE_TOKEN        = 'token:give';
   const TYPE_INLINESTATE  = 'core:inlinestate';
   const TYPE_SPACE = 'core:space';
   const TYPE_CREATE = 'core:create';
+  const TYPE_COLUMNS = 'core:columns';
 
   const COLOR_RED         = 'red';
   const COLOR_ORANGE      = 'orange';
   const COLOR_YELLOW      = 'yellow';
   const COLOR_GREEN       = 'green';
   const COLOR_SKY         = 'sky';
   const COLOR_BLUE        = 'blue';
   const COLOR_INDIGO      = 'indigo';
   const COLOR_VIOLET      = 'violet';
   const COLOR_GREY        = 'grey';
   const COLOR_BLACK       = 'black';
 
 
   public static function getInlineStateMap() {
     return array(
       PhabricatorInlineCommentInterface::STATE_DRAFT =>
         PhabricatorInlineCommentInterface::STATE_DONE,
       PhabricatorInlineCommentInterface::STATE_UNDRAFT =>
         PhabricatorInlineCommentInterface::STATE_UNDONE,
     );
   }
 
 }
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
index 9655a83e6..185ac13a5 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -1,1986 +1,1988 @@
 <?php
 
 
 /**
  * @task fields Managing Fields
  * @task text Display Text
  * @task config Edit Engine Configuration
  * @task uri Managing URIs
  * @task load Creating and Loading Objects
  * @task web Responding to Web Requests
  * @task edit Responding to Edit Requests
  * @task http Responding to HTTP Parameter Requests
  * @task conduit Responding to Conduit Requests
  */
 abstract class PhabricatorEditEngine
   extends Phobject
   implements PhabricatorPolicyInterface {
 
   const EDITENGINECONFIG_DEFAULT = 'default';
 
   private $viewer;
   private $controller;
   private $isCreate;
   private $editEngineConfiguration;
   private $contextParameters = array();
   private $targetObject;
 
   final public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
   }
 
   final public function getViewer() {
     return $this->viewer;
   }
 
   final public function setController(PhabricatorController $controller) {
     $this->controller = $controller;
     $this->setViewer($controller->getViewer());
     return $this;
   }
 
   final public function getController() {
     return $this->controller;
   }
 
   final public function getEngineKey() {
     return $this->getPhobjectClassConstant('ENGINECONST', 64);
   }
 
   final public function getApplication() {
     $app_class = $this->getEngineApplicationClass();
     return PhabricatorApplication::getByClass($app_class);
   }
 
   final public function addContextParameter($key) {
     $this->contextParameters[] = $key;
     return $this;
   }
 
   public function isEngineConfigurable() {
     return true;
   }
 
   public function isEngineExtensible() {
     return true;
   }
 
   /**
    * Force the engine to edit a particular object.
    */
   public function setTargetObject($target_object) {
     $this->targetObject = $target_object;
     return $this;
   }
 
   public function getTargetObject() {
     return $this->targetObject;
   }
 
 
 /* -(  Managing Fields  )---------------------------------------------------- */
 
 
   abstract public function getEngineApplicationClass();
   abstract protected function buildCustomEditFields($object);
 
   public function getFieldsForConfig(
     PhabricatorEditEngineConfiguration $config) {
 
     $object = $this->newEditableObject();
 
     $this->editEngineConfiguration = $config;
 
     // This is mostly making sure that we fill in default values.
     $this->setIsCreate(true);
 
     return $this->buildEditFields($object);
   }
 
   final protected function buildEditFields($object) {
     $viewer = $this->getViewer();
 
     $fields = $this->buildCustomEditFields($object);
 
     foreach ($fields as $field) {
       $field
         ->setViewer($viewer)
         ->setObject($object);
     }
 
     $fields = mpull($fields, null, 'getKey');
 
     if ($this->isEngineExtensible()) {
       $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
     } else {
       $extensions = array();
     }
 
     foreach ($extensions as $extension) {
       $extension->setViewer($viewer);
 
       if (!$extension->supportsObject($this, $object)) {
         continue;
       }
 
       $extension_fields = $extension->buildCustomEditFields($this, $object);
 
       // TODO: Validate this in more detail with a more tailored error.
       assert_instances_of($extension_fields, 'PhabricatorEditField');
 
       foreach ($extension_fields as $field) {
         $field
           ->setViewer($viewer)
           ->setObject($object);
       }
 
       $extension_fields = mpull($extension_fields, null, 'getKey');
 
       foreach ($extension_fields as $key => $field) {
         $fields[$key] = $field;
       }
     }
 
     $config = $this->getEditEngineConfiguration();
     $fields = $this->willConfigureFields($object, $fields);
     $fields = $config->applyConfigurationToFields($this, $object, $fields);
 
     return $fields;
   }
 
   protected function willConfigureFields($object, array $fields) {
     return $fields;
   }
 
 
 /* -(  Display Text  )------------------------------------------------------- */
 
 
   /**
    * @task text
    */
   abstract public function getEngineName();
 
 
   /**
    * @task text
    */
   abstract protected function getObjectCreateTitleText($object);
 
   /**
    * @task text
    */
   protected function getFormHeaderText($object) {
     $config = $this->getEditEngineConfiguration();
     return $config->getName();
   }
 
   /**
    * @task text
    */
   abstract protected function getObjectEditTitleText($object);
 
 
   /**
    * @task text
    */
   abstract protected function getObjectCreateShortText();
 
 
   /**
    * @task text
    */
   abstract protected function getObjectName();
 
 
   /**
    * @task text
    */
   abstract protected function getObjectEditShortText($object);
 
 
   /**
    * @task text
    */
   protected function getObjectCreateButtonText($object) {
     return $this->getObjectCreateTitleText($object);
   }
 
 
   /**
    * @task text
    */
   protected function getObjectEditButtonText($object) {
     return pht('Save Changes');
   }
 
 
   /**
    * @task text
    */
   protected function getCommentViewSeriousHeaderText($object) {
     return pht('Take Action');
   }
 
 
   /**
    * @task text
    */
   protected function getCommentViewSeriousButtonText($object) {
     return pht('Submit');
   }
 
 
   /**
    * @task text
    */
   protected function getCommentViewHeaderText($object) {
     return $this->getCommentViewSeriousHeaderText($object);
   }
 
 
   /**
    * @task text
    */
   protected function getCommentViewButtonText($object) {
     return $this->getCommentViewSeriousButtonText($object);
   }
 
 
   /**
    * @task text
    */
   protected function getQuickCreateMenuHeaderText() {
     return $this->getObjectCreateShortText();
   }
 
 
   /**
    * Return a human-readable header describing what this engine is used to do,
    * like "Configure Maniphest Task Forms".
    *
    * @return string Human-readable description of the engine.
    * @task text
    */
   abstract public function getSummaryHeader();
 
 
   /**
    * Return a human-readable summary of what this engine is used to do.
    *
    * @return string Human-readable description of the engine.
    * @task text
    */
   abstract public function getSummaryText();
 
 
 
 
 /* -(  Edit Engine Configuration  )------------------------------------------ */
 
 
   protected function supportsEditEngineConfiguration() {
     return true;
   }
 
   final protected function getEditEngineConfiguration() {
     return $this->editEngineConfiguration;
   }
 
   private function newConfigurationQuery() {
     return id(new PhabricatorEditEngineConfigurationQuery())
       ->setViewer($this->getViewer())
       ->withEngineKeys(array($this->getEngineKey()));
   }
 
   private function loadEditEngineConfigurationWithQuery(
     PhabricatorEditEngineConfigurationQuery $query,
     $sort_method) {
 
     if ($sort_method) {
       $results = $query->execute();
       $results = msort($results, $sort_method);
       $result = head($results);
     } else {
       $result = $query->executeOne();
     }
 
     if (!$result) {
       return null;
     }
 
     $this->editEngineConfiguration = $result;
     return $result;
   }
 
   private function loadEditEngineConfigurationWithIdentifier($identifier) {
     $query = $this->newConfigurationQuery()
       ->withIdentifiers(array($identifier));
 
     return $this->loadEditEngineConfigurationWithQuery($query, null);
   }
 
   private function loadDefaultConfiguration() {
     $query = $this->newConfigurationQuery()
       ->withIdentifiers(
         array(
           self::EDITENGINECONFIG_DEFAULT,
         ))
       ->withIgnoreDatabaseConfigurations(true);
 
     return $this->loadEditEngineConfigurationWithQuery($query, null);
   }
 
   private function loadDefaultCreateConfiguration() {
     $query = $this->newConfigurationQuery()
       ->withIsDefault(true)
       ->withIsDisabled(false);
 
     return $this->loadEditEngineConfigurationWithQuery(
       $query,
       'getCreateSortKey');
   }
 
   public function loadDefaultEditConfiguration() {
     $query = $this->newConfigurationQuery()
       ->withIsEdit(true)
       ->withIsDisabled(false);
 
     return $this->loadEditEngineConfigurationWithQuery(
       $query,
       'getEditSortKey');
   }
 
   final public function getBuiltinEngineConfigurations() {
     $configurations = $this->newBuiltinEngineConfigurations();
 
     if (!$configurations) {
       throw new Exception(
         pht(
           'EditEngine ("%s") returned no builtin engine configurations, but '.
           'an edit engine must have at least one configuration.',
           get_class($this)));
     }
 
     assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration');
 
     $has_default = false;
     foreach ($configurations as $config) {
       if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) {
         $has_default = true;
       }
     }
 
     if (!$has_default) {
       $first = head($configurations);
       if (!$first->getBuiltinKey()) {
         $first
           ->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT)
           ->setIsDefault(true)
           ->setIsEdit(true);
 
         if (!strlen($first->getName())) {
           $first->setName($this->getObjectCreateShortText());
         }
     } else {
         throw new Exception(
           pht(
             'EditEngine ("%s") returned builtin engine configurations, '.
             'but none are marked as default and the first configuration has '.
             'a different builtin key already. Mark a builtin as default or '.
             'omit the key from the first configuration',
             get_class($this)));
       }
     }
 
     $builtins = array();
     foreach ($configurations as $key => $config) {
       $builtin_key = $config->getBuiltinKey();
 
       if ($builtin_key === null) {
         throw new Exception(
           pht(
             'EditEngine ("%s") returned builtin engine configurations, '.
             'but one (with key "%s") is missing a builtin key. Provide a '.
             'builtin key for each configuration (you can omit it from the '.
             'first configuration in the list to automatically assign the '.
             'default key).',
             get_class($this),
             $key));
       }
 
       if (isset($builtins[$builtin_key])) {
         throw new Exception(
           pht(
             'EditEngine ("%s") returned builtin engine configurations, '.
             'but at least two specify the same builtin key ("%s"). Engines '.
             'must have unique builtin keys.',
             get_class($this),
             $builtin_key));
       }
 
       $builtins[$builtin_key] = $config;
     }
 
 
     return $builtins;
   }
 
   protected function newBuiltinEngineConfigurations() {
     return array(
       $this->newConfiguration(),
     );
   }
 
   final protected function newConfiguration() {
     return PhabricatorEditEngineConfiguration::initializeNewConfiguration(
       $this->getViewer(),
       $this);
   }
 
 
 /* -(  Managing URIs  )------------------------------------------------------ */
 
 
   /**
    * @task uri
    */
   abstract protected function getObjectViewURI($object);
 
 
   /**
    * @task uri
    */
   protected function getObjectCreateCancelURI($object) {
     return $this->getApplication()->getApplicationURI();
   }
 
 
   /**
    * @task uri
    */
   protected function getEditorURI() {
     return $this->getApplication()->getApplicationURI('edit/');
   }
 
 
   /**
    * @task uri
    */
   protected function getObjectEditCancelURI($object) {
     return $this->getObjectViewURI($object);
   }
 
 
   /**
    * @task uri
    */
   public function getEditURI($object = null, $path = null) {
     $parts = array();
 
     $parts[] = $this->getEditorURI();
 
     if ($object && $object->getID()) {
       $parts[] = $object->getID().'/';
     }
 
     if ($path !== null) {
       $parts[] = $path;
     }
 
     return implode('', $parts);
   }
 
 
 /* -(  Creating and Loading Objects  )--------------------------------------- */
 
 
   /**
    * Initialize a new object for creation.
    *
    * @return object Newly initialized object.
    * @task load
    */
   abstract protected function newEditableObject();
 
 
   /**
    * Build an empty query for objects.
    *
    * @return PhabricatorPolicyAwareQuery Query.
    * @task load
    */
   abstract protected function newObjectQuery();
 
 
   /**
    * Test if this workflow is creating a new object or editing an existing one.
    *
    * @return bool True if a new object is being created.
    * @task load
    */
   final public function getIsCreate() {
     return $this->isCreate;
   }
 
 
   /**
    * Flag this workflow as a create or edit.
    *
    * @param bool True if this is a create workflow.
    * @return this
    * @task load
    */
   private function setIsCreate($is_create) {
     $this->isCreate = $is_create;
     return $this;
   }
 
 
   /**
    * Try to load an object by ID, PHID, or monogram. This is done primarily
    * to make Conduit a little easier to use.
    *
    * @param wild ID, PHID, or monogram.
    * @param list<const> List of required capability constants, or omit for
    *   defaults.
    * @return object Corresponding editable object.
    * @task load
    */
   private function newObjectFromIdentifier(
     $identifier,
     array $capabilities = array()) {
     if (is_int($identifier) || ctype_digit($identifier)) {
       $object = $this->newObjectFromID($identifier, $capabilities);
 
       if (!$object) {
         throw new Exception(
           pht(
             'No object exists with ID "%s".',
             $identifier));
       }
 
       return $object;
     }
 
     $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN;
     if (phid_get_type($identifier) != $type_unknown) {
       $object = $this->newObjectFromPHID($identifier, $capabilities);
 
       if (!$object) {
         throw new Exception(
           pht(
             'No object exists with PHID "%s".',
             $identifier));
       }
 
       return $object;
     }
 
     $target = id(new PhabricatorObjectQuery())
       ->setViewer($this->getViewer())
       ->withNames(array($identifier))
       ->executeOne();
     if (!$target) {
       throw new Exception(
         pht(
           'Monogram "%s" does not identify a valid object.',
           $identifier));
     }
 
     $expect = $this->newEditableObject();
     $expect_class = get_class($expect);
     $target_class = get_class($target);
     if ($expect_class !== $target_class) {
       throw new Exception(
         pht(
           'Monogram "%s" identifies an object of the wrong type. Loaded '.
           'object has class "%s", but this editor operates on objects of '.
           'type "%s".',
           $identifier,
           $target_class,
           $expect_class));
     }
 
     // Load the object by PHID using this engine's standard query. This makes
     // sure it's really valid, goes through standard policy check logic, and
     // picks up any `need...()` clauses we want it to load with.
 
     $object = $this->newObjectFromPHID($target->getPHID(), $capabilities);
     if (!$object) {
       throw new Exception(
         pht(
           'Failed to reload object identified by monogram "%s" when '.
           'querying by PHID.',
           $identifier));
     }
 
     return $object;
   }
 
   /**
    * Load an object by ID.
    *
    * @param int Object ID.
    * @param list<const> List of required capability constants, or omit for
    *   defaults.
    * @return object|null Object, or null if no such object exists.
    * @task load
    */
   private function newObjectFromID($id, array $capabilities = array()) {
     $query = $this->newObjectQuery()
       ->withIDs(array($id));
 
     return $this->newObjectFromQuery($query, $capabilities);
   }
 
 
   /**
    * Load an object by PHID.
    *
    * @param phid Object PHID.
    * @param list<const> List of required capability constants, or omit for
    *   defaults.
    * @return object|null Object, or null if no such object exists.
    * @task load
    */
   private function newObjectFromPHID($phid, array $capabilities = array()) {
     $query = $this->newObjectQuery()
       ->withPHIDs(array($phid));
 
     return $this->newObjectFromQuery($query, $capabilities);
   }
 
 
   /**
    * Load an object given a configured query.
    *
    * @param PhabricatorPolicyAwareQuery Configured query.
    * @param list<const> List of required capabilitiy constants, or omit for
    *  defaults.
    * @return object|null Object, or null if no such object exists.
    * @task load
    */
   private function newObjectFromQuery(
     PhabricatorPolicyAwareQuery $query,
     array $capabilities = array()) {
 
     $viewer = $this->getViewer();
 
     if (!$capabilities) {
       $capabilities = array(
         PhabricatorPolicyCapability::CAN_VIEW,
         PhabricatorPolicyCapability::CAN_EDIT,
       );
     }
 
     $object = $query
       ->setViewer($viewer)
       ->requireCapabilities($capabilities)
       ->executeOne();
     if (!$object) {
       return null;
     }
 
     return $object;
   }
 
 
   /**
    * Verify that an object is appropriate for editing.
    *
    * @param wild Loaded value.
    * @return void
    * @task load
    */
   private function validateObject($object) {
     if (!$object || !is_object($object)) {
       throw new Exception(
         pht(
           'EditEngine "%s" created or loaded an invalid object: object must '.
           'actually be an object, but is of some other type ("%s").',
           get_class($this),
           gettype($object)));
     }
 
     if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
       throw new Exception(
         pht(
           'EditEngine "%s" created or loaded an invalid object: object (of '.
           'class "%s") must implement "%s", but does not.',
           get_class($this),
           get_class($object),
           'PhabricatorApplicationTransactionInterface'));
     }
   }
 
 
 /* -(  Responding to Web Requests  )----------------------------------------- */
 
 
   final public function buildResponse() {
     $viewer = $this->getViewer();
     $controller = $this->getController();
     $request = $controller->getRequest();
 
     $action = $request->getURIData('editAction');
 
     $capabilities = array();
     $use_default = false;
     $require_create = true;
     switch ($action) {
       case 'comment':
         $capabilities = array(
           PhabricatorPolicyCapability::CAN_VIEW,
         );
         $use_default = true;
         break;
       case 'parameters':
         $use_default = true;
         break;
       case 'nodefault':
       case 'nocreate':
       case 'nomanage':
         $require_create = false;
         break;
       default:
         break;
     }
 
     $object = $this->getTargetObject();
     if (!$object) {
       $id = $request->getURIData('id');
 
       if ($id) {
         $this->setIsCreate(false);
         $object = $this->newObjectFromID($id, $capabilities);
         if (!$object) {
           return new Aphront404Response();
         }
       } else {
         // Make sure the viewer has permission to create new objects of
         // this type if we're going to create a new object.
         if ($require_create) {
           $this->requireCreateCapability();
         }
 
         $this->setIsCreate(true);
         $object = $this->newEditableObject();
       }
     } else {
       $id = $object->getID();
     }
 
     $this->validateObject($object);
 
     if ($use_default) {
       $config = $this->loadDefaultConfiguration();
       if (!$config) {
         return new Aphront404Response();
       }
     } else {
       $form_key = $request->getURIData('formKey');
       if (strlen($form_key)) {
         $config = $this->loadEditEngineConfigurationWithIdentifier($form_key);
 
         if (!$config) {
           return new Aphront404Response();
         }
 
         if ($id && !$config->getIsEdit()) {
           return $this->buildNotEditFormRespose($object, $config);
         }
       } else {
         if ($id) {
           $config = $this->loadDefaultEditConfiguration();
           if (!$config) {
             return $this->buildNoEditResponse($object);
           }
         } else {
           $config = $this->loadDefaultCreateConfiguration();
           if (!$config) {
             return $this->buildNoCreateResponse($object);
           }
         }
       }
     }
 
     if ($config->getIsDisabled()) {
       return $this->buildDisabledFormResponse($object, $config);
     }
 
     switch ($action) {
       case 'parameters':
         return $this->buildParametersResponse($object);
       case 'nodefault':
         return $this->buildNoDefaultResponse($object);
       case 'nocreate':
         return $this->buildNoCreateResponse($object);
       case 'nomanage':
         return $this->buildNoManageResponse($object);
       case 'comment':
         return $this->buildCommentResponse($object);
       default:
         return $this->buildEditResponse($object);
     }
   }
 
   private function buildCrumbs($object, $final = false) {
     $controller = $this->getController();
 
     $crumbs = $controller->buildApplicationCrumbsForEditEngine();
     if ($this->getIsCreate()) {
       $create_text = $this->getObjectCreateShortText();
       if ($final) {
         $crumbs->addTextCrumb($create_text);
       } else {
         $edit_uri = $this->getEditURI($object);
         $crumbs->addTextCrumb($create_text, $edit_uri);
       }
     } else {
       $crumbs->addTextCrumb(
         $this->getObjectEditShortText($object),
         $this->getObjectViewURI($object));
 
       $edit_text = pht('Edit');
       if ($final) {
         $crumbs->addTextCrumb($edit_text);
       } else {
         $edit_uri = $this->getEditURI($object);
         $crumbs->addTextCrumb($edit_text, $edit_uri);
       }
     }
 
     return $crumbs;
   }
 
   private function buildEditResponse($object) {
     $viewer = $this->getViewer();
     $controller = $this->getController();
     $request = $controller->getRequest();
 
     $fields = $this->buildEditFields($object);
     $template = $object->getApplicationTransactionTemplate();
 
     $validation_exception = null;
     if ($request->isFormPost() && $request->getBool('editEngine')) {
       $submit_fields = $fields;
 
       foreach ($submit_fields as $key => $field) {
         if (!$field->shouldGenerateTransactionsFromSubmit()) {
           unset($submit_fields[$key]);
           continue;
         }
       }
 
       // Before we read the submitted values, store a copy of what we would
       // use if the form was empty so we can figure out which transactions are
       // just setting things to their default values for the current form.
       $defaults = array();
       foreach ($submit_fields as $key => $field) {
         $defaults[$key] = $field->getValueForTransaction();
       }
 
       foreach ($submit_fields as $key => $field) {
         $field->setIsSubmittedForm(true);
 
         if (!$field->shouldReadValueFromSubmit()) {
           continue;
         }
 
         $field->readValueFromSubmit($request);
       }
 
       $xactions = array();
 
       if ($this->getIsCreate()) {
         $xactions[] = id(clone $template)
           ->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
       }
 
       foreach ($submit_fields as $key => $field) {
         $field_value = $field->getValueForTransaction();
 
         $type_xactions = $field->generateTransactions(
           clone $template,
           array(
             'value' => $field_value,
           ));
 
         foreach ($type_xactions as $type_xaction) {
           $default = $defaults[$key];
 
           if ($default === $field->getValueForTransaction()) {
             $type_xaction->setIsDefaultTransaction(true);
           }
 
           $xactions[] = $type_xaction;
         }
       }
 
       $editor = $object->getApplicationTransactionEditor()
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
 
         $editor->applyTransactions($object, $xactions);
 
         return $this->newEditResponse($request, $object, $xactions);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
         foreach ($fields as $field) {
           $xaction_type = $field->getTransactionType();
           if ($xaction_type === null) {
             continue;
           }
 
           $message = $ex->getShortMessage($xaction_type);
           if ($message === null) {
             continue;
           }
 
           $field->setControlError($message);
         }
       }
     } else {
       if ($this->getIsCreate()) {
         $template = $request->getStr('template');
 
         if (strlen($template)) {
           $template_object = $this->newObjectFromIdentifier(
             $template,
             array(
               PhabricatorPolicyCapability::CAN_VIEW,
             ));
           if (!$template_object) {
             return new Aphront404Response();
           }
         } else {
           $template_object = null;
         }
 
         if ($template_object) {
           $copy_fields = $this->buildEditFields($template_object);
           $copy_fields = mpull($copy_fields, null, 'getKey');
           foreach ($copy_fields as $copy_key => $copy_field) {
             if (!$copy_field->getIsCopyable()) {
               unset($copy_fields[$copy_key]);
             }
           }
         } else {
           $copy_fields = array();
         }
 
         foreach ($fields as $field) {
           if (!$field->shouldReadValueFromRequest()) {
             continue;
           }
 
           $field_key = $field->getKey();
           if (isset($copy_fields[$field_key])) {
             $field->readValueFromField($copy_fields[$field_key]);
           }
 
           $field->readValueFromRequest($request);
         }
       }
     }
 
     $action_button = $this->buildEditFormActionButton($object);
 
     if ($this->getIsCreate()) {
       $header_text = $this->getFormHeaderText($object);
       $header_icon = 'fa-plus-square';
     } else {
       $header_text = $this->getObjectEditTitleText($object);
       $header_icon = 'fa-pencil';
     }
 
     $show_preview = !$request->isAjax();
 
     if ($show_preview) {
       $previews = array();
       foreach ($fields as $field) {
         $preview = $field->getPreviewPanel();
         if (!$preview) {
           continue;
         }
 
         $control_id = $field->getControlID();
 
         $preview
           ->setControlID($control_id)
           ->setPreviewURI('/transactions/remarkuppreview/');
 
         $previews[] = $preview;
       }
     } else {
       $previews = array();
     }
 
     $form = $this->buildEditForm($object, $fields);
 
     if ($request->isAjax()) {
       if ($this->getIsCreate()) {
         $cancel_uri = $this->getObjectCreateCancelURI($object);
         $submit_button = $this->getObjectCreateButtonText($object);
       } else {
         $cancel_uri = $this->getObjectEditCancelURI($object);
         $submit_button = $this->getObjectEditButtonText($object);
       }
 
       return $this->getController()
         ->newDialog()
         ->setWidth(AphrontDialogView::WIDTH_FULL)
         ->setTitle($header_text)
         ->setValidationException($validation_exception)
         ->appendForm($form)
         ->addCancelButton($cancel_uri)
         ->addSubmitButton($submit_button);
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader($header_text)
       ->setHeaderIcon($header_icon);
 
     if ($action_button) {
       $header->addActionLink($action_button);
     }
 
     $crumbs = $this->buildCrumbs($object, $final = true);
     $crumbs->setBorder(true);
 
     $box = id(new PHUIObjectBoxView())
       ->setUser($viewer)
       ->setHeaderText($this->getObjectName())
       ->setValidationException($validation_exception)
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($form);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setFooter(array(
         $box,
         $previews,
       ));
 
     return $controller->newPage()
       ->setTitle($header_text)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   protected function newEditResponse(
     AphrontRequest $request,
     $object,
     array $xactions) {
     return id(new AphrontRedirectResponse())
       ->setURI($this->getObjectViewURI($object));
   }
 
   private function buildEditForm($object, array $fields) {
     $viewer = $this->getViewer();
     $controller = $this->getController();
     $request = $controller->getRequest();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('editEngine', 'true');
 
     foreach ($this->contextParameters as $param) {
       $form->addHiddenInput($param, $request->getStr($param));
     }
 
     foreach ($fields as $field) {
       $field->appendToForm($form);
     }
 
     if ($this->getIsCreate()) {
       $cancel_uri = $this->getObjectCreateCancelURI($object);
       $submit_button = $this->getObjectCreateButtonText($object);
     } else {
       $cancel_uri = $this->getObjectEditCancelURI($object);
       $submit_button = $this->getObjectEditButtonText($object);
     }
 
     if (!$request->isAjax()) {
       $form->appendControl(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($cancel_uri)
           ->setValue($submit_button));
     }
 
     return $form;
   }
 
   private function buildEditFormActionButton($object) {
     if (!$this->isEngineConfigurable()) {
       return null;
     }
 
     $viewer = $this->getViewer();
 
     $action_view = id(new PhabricatorActionListView())
       ->setUser($viewer);
 
     foreach ($this->buildEditFormActions($object) as $action) {
       $action_view->addAction($action);
     }
 
     $action_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Configure Form'))
       ->setHref('#')
       ->setIcon('fa-gear')
       ->setDropdownMenu($action_view);
 
     return $action_button;
   }
 
   private function buildEditFormActions($object) {
     $actions = array();
 
     if ($this->supportsEditEngineConfiguration()) {
       $engine_key = $this->getEngineKey();
       $config = $this->getEditEngineConfiguration();
 
       $can_manage = PhabricatorPolicyFilter::hasCapability(
         $this->getViewer(),
         $config,
         PhabricatorPolicyCapability::CAN_EDIT);
 
       if ($can_manage) {
         $manage_uri = $config->getURI();
       } else {
         $manage_uri = $this->getEditURI(null, 'nomanage/');
       }
 
       $view_uri = "/transactions/editengine/{$engine_key}/";
 
       $actions[] = id(new PhabricatorActionView())
         ->setLabel(true)
         ->setName(pht('Configuration'));
 
       $actions[] = id(new PhabricatorActionView())
         ->setName(pht('View Form Configurations'))
         ->setIcon('fa-list-ul')
         ->setHref($view_uri);
 
       $actions[] = id(new PhabricatorActionView())
         ->setName(pht('Edit Form Configuration'))
         ->setIcon('fa-pencil')
         ->setHref($manage_uri)
         ->setDisabled(!$can_manage)
         ->setWorkflow(!$can_manage);
     }
 
     $actions[] = id(new PhabricatorActionView())
       ->setLabel(true)
       ->setName(pht('Documentation'));
 
     $actions[] = id(new PhabricatorActionView())
       ->setName(pht('Using HTTP Parameters'))
       ->setIcon('fa-book')
       ->setHref($this->getEditURI($object, 'parameters/'));
 
     $doc_href = PhabricatorEnv::getDoclink('User Guide: Customizing Forms');
     $actions[] = id(new PhabricatorActionView())
       ->setName(pht('User Guide: Customizing Forms'))
       ->setIcon('fa-book')
       ->setHref($doc_href);
 
     return $actions;
   }
 
 
   /**
    * Test if the viewer could apply a certain type of change by using the
    * normal "Edit" form.
    *
    * This method returns `true` if the user has access to an edit form and
    * that edit form has a field which applied the specified transaction type,
    * and that field is visible and editable for the user.
    *
    * For example, you can use it to test if a user is able to reassign tasks
    * or not, prior to rendering dedicated UI for task reassingment.
    *
    * Note that this method does NOT test if the user can actually edit the
    * current object, just if they have access to the related field.
    *
    * @param const Transaction type to test for.
    * @return bool True if the user could "Edit" to apply the transaction type.
    */
   final public function hasEditAccessToTransaction($xaction_type) {
     $viewer = $this->getViewer();
 
     $config = $this->loadDefaultEditConfiguration();
     if (!$config) {
       return false;
     }
 
     $object = $this->getTargetObject();
     if (!$object) {
       $object = $this->newEditableObject();
     }
 
     $fields = $this->buildEditFields($object);
 
     $field = null;
     foreach ($fields as $form_field) {
       $field_xaction_type = $form_field->getTransactionType();
       if ($field_xaction_type === $xaction_type) {
         $field = $form_field;
         break;
       }
     }
 
     if (!$field) {
       return false;
     }
 
     if (!$field->shouldReadValueFromSubmit()) {
       return false;
     }
 
     return true;
   }
 
 
   final public function addActionToCrumbs(PHUICrumbsView $crumbs) {
     $viewer = $this->getViewer();
 
     $can_create = $this->hasCreateCapability();
     if ($can_create) {
       $configs = $this->loadUsableConfigurationsForCreate();
     } else {
       $configs = array();
     }
 
     $dropdown = null;
     $disabled = false;
     $workflow = false;
 
     $menu_icon = 'fa-plus-square';
 
     if (!$configs) {
       if ($viewer->isLoggedIn()) {
         $disabled = true;
       } else {
         // If the viewer isn't logged in, assume they'll get hit with a login
         // dialog and are likely able to create objects after they log in.
         $disabled = false;
       }
       $workflow = true;
 
       if ($can_create) {
         $create_uri = $this->getEditURI(null, 'nodefault/');
       } else {
         $create_uri = $this->getEditURI(null, 'nocreate/');
       }
     } else {
       $config = head($configs);
       $form_key = $config->getIdentifier();
       $create_uri = $this->getEditURI(null, "form/{$form_key}/");
 
       if (count($configs) > 1) {
         $menu_icon = 'fa-caret-square-o-down';
 
         $dropdown = id(new PhabricatorActionListView())
           ->setUser($viewer);
 
         foreach ($configs as $config) {
           $form_key = $config->getIdentifier();
           $config_uri = $this->getEditURI(null, "form/{$form_key}/");
 
           $item_icon = 'fa-plus';
 
           $dropdown->addAction(
             id(new PhabricatorActionView())
               ->setName($config->getDisplayName())
               ->setIcon($item_icon)
               ->setHref($config_uri));
         }
       }
     }
 
     $action = id(new PHUIListItemView())
       ->setName($this->getObjectCreateShortText())
       ->setHref($create_uri)
       ->setIcon($menu_icon)
       ->setWorkflow($workflow)
       ->setDisabled($disabled);
 
     if ($dropdown) {
       $action->setDropdownMenu($dropdown);
     }
 
     $crumbs->addAction($action);
   }
 
   final public function buildEditEngineCommentView($object) {
     $config = $this->loadDefaultEditConfiguration();
 
     if (!$config) {
       // TODO: This just nukes the entire comment form if you don't have access
       // to any edit forms. We might want to tailor this UX a bit.
       return id(new PhabricatorApplicationTransactionCommentView())
         ->setNoPermission(true);
     }
 
     $viewer = $this->getViewer();
     $object_phid = $object->getPHID();
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     if ($is_serious) {
       $header_text = $this->getCommentViewSeriousHeaderText($object);
       $button_text = $this->getCommentViewSeriousButtonText($object);
     } else {
       $header_text = $this->getCommentViewHeaderText($object);
       $button_text = $this->getCommentViewButtonText($object);
     }
 
     $comment_uri = $this->getEditURI($object, 'comment/');
 
     $view = id(new PhabricatorApplicationTransactionCommentView())
       ->setUser($viewer)
       ->setObjectPHID($object_phid)
       ->setHeaderText($header_text)
       ->setAction($comment_uri)
       ->setSubmitButtonName($button_text);
 
     $draft = PhabricatorVersionedDraft::loadDraft(
       $object_phid,
       $viewer->getPHID());
     if ($draft) {
       $view->setVersionedDraft($draft);
     }
 
     $view->setCurrentVersion($this->loadDraftVersion($object));
 
     $fields = $this->buildEditFields($object);
 
     $comment_actions = array();
     foreach ($fields as $field) {
       if (!$field->shouldGenerateTransactionsFromComment()) {
         continue;
       }
 
       $comment_action = $field->getCommentAction();
       if (!$comment_action) {
         continue;
       }
 
       $key = $comment_action->getKey();
 
       // TODO: Validate these better.
 
       $comment_actions[$key] = $comment_action;
     }
 
+    $comment_actions = msortv($comment_actions, 'getSortVector');
+
     $view->setCommentActions($comment_actions);
 
     return $view;
   }
 
   protected function loadDraftVersion($object) {
     $viewer = $this->getViewer();
 
     if (!$viewer->isLoggedIn()) {
       return null;
     }
 
     $template = $object->getApplicationTransactionTemplate();
     $conn_r = $template->establishConnection('r');
 
     // Find the most recent transaction the user has written. We'll use this
     // as a version number to make sure that out-of-date drafts get discarded.
     $result = queryfx_one(
       $conn_r,
       'SELECT id AS version FROM %T
         WHERE objectPHID = %s AND authorPHID = %s
         ORDER BY id DESC LIMIT 1',
       $template->getTableName(),
       $object->getPHID(),
       $viewer->getPHID());
 
     if ($result) {
       return (int)$result['version'];
     } else {
       return null;
     }
   }
 
 
 /* -(  Responding to HTTP Parameter Requests  )------------------------------ */
 
 
   /**
    * Respond to a request for documentation on HTTP parameters.
    *
    * @param object Editable object.
    * @return AphrontResponse Response object.
    * @task http
    */
   private function buildParametersResponse($object) {
     $controller = $this->getController();
     $viewer = $this->getViewer();
     $request = $controller->getRequest();
     $fields = $this->buildEditFields($object);
 
     $crumbs = $this->buildCrumbs($object);
     $crumbs->addTextCrumb(pht('HTTP Parameters'));
     $crumbs->setBorder(true);
 
     $header_text = pht(
       'HTTP Parameters: %s',
       $this->getObjectCreateShortText());
 
     $header = id(new PHUIHeaderView())
       ->setHeader($header_text);
 
     $help_view = id(new PhabricatorApplicationEditHTTPParameterHelpView())
       ->setUser($viewer)
       ->setFields($fields);
 
     $document = id(new PHUIDocumentViewPro())
       ->setUser($viewer)
       ->setHeader($header)
       ->appendChild($help_view);
 
     return $controller->newPage()
       ->setTitle(pht('HTTP Parameters'))
       ->setCrumbs($crumbs)
       ->appendChild($document);
   }
 
 
   private function buildError($object, $title, $body) {
     $cancel_uri = $this->getObjectCreateCancelURI($object);
 
     return $this->getController()
       ->newDialog()
       ->setTitle($title)
       ->appendParagraph($body)
       ->addCancelButton($cancel_uri);
   }
 
 
   private function buildNoDefaultResponse($object) {
     return $this->buildError(
       $object,
       pht('No Default Create Forms'),
       pht(
         'This application is not configured with any forms for creating '.
         'objects that are visible to you and enabled.'));
   }
 
   private function buildNoCreateResponse($object) {
     return $this->buildError(
       $object,
       pht('No Create Permission'),
       pht('You do not have permission to create these objects.'));
   }
 
   private function buildNoManageResponse($object) {
     return $this->buildError(
       $object,
       pht('No Manage Permission'),
       pht(
         'You do not have permission to configure forms for this '.
         'application.'));
   }
 
   private function buildNoEditResponse($object) {
     return $this->buildError(
       $object,
       pht('No Edit Forms'),
       pht(
         'You do not have access to any forms which are enabled and marked '.
         'as edit forms.'));
   }
 
   private function buildNotEditFormRespose($object, $config) {
     return $this->buildError(
       $object,
       pht('Not an Edit Form'),
       pht(
         'This form ("%s") is not marked as an edit form, so '.
         'it can not be used to edit objects.',
         $config->getName()));
   }
 
   private function buildDisabledFormResponse($object, $config) {
     return $this->buildError(
       $object,
       pht('Form Disabled'),
       pht(
         'This form ("%s") has been disabled, so it can not be used.',
         $config->getName()));
   }
 
   private function buildCommentResponse($object) {
     $viewer = $this->getViewer();
 
     if ($this->getIsCreate()) {
       return new Aphront404Response();
     }
 
     $controller = $this->getController();
     $request = $controller->getRequest();
 
     if (!$request->isFormPost()) {
       return new Aphront400Response();
     }
 
     $config = $this->loadDefaultEditConfiguration();
     if (!$config) {
       return new Aphront404Response();
     }
 
     $fields = $this->buildEditFields($object);
 
     $is_preview = $request->isPreviewRequest();
     $view_uri = $this->getObjectViewURI($object);
 
     $template = $object->getApplicationTransactionTemplate();
     $comment_template = $template->getApplicationTransactionCommentObject();
 
     $comment_text = $request->getStr('comment');
 
     $actions = $request->getStr('editengine.actions');
     if ($actions) {
       $actions = phutil_json_decode($actions);
     }
 
     if ($is_preview) {
       $version_key = PhabricatorVersionedDraft::KEY_VERSION;
       $request_version = $request->getInt($version_key);
       $current_version = $this->loadDraftVersion($object);
       if ($request_version >= $current_version) {
         $draft = PhabricatorVersionedDraft::loadOrCreateDraft(
           $object->getPHID(),
           $viewer->getPHID(),
           $current_version);
 
         $draft
           ->setProperty('comment', $comment_text)
           ->setProperty('actions', $actions)
           ->save();
       }
     }
 
     $xactions = array();
 
     if ($actions) {
       $action_map = array();
       foreach ($actions as $action) {
         $type = idx($action, 'type');
         if (!$type) {
           continue;
         }
 
         if (empty($fields[$type])) {
           continue;
         }
 
         $action_map[$type] = $action;
       }
 
       foreach ($action_map as $type => $action) {
         $field = $fields[$type];
 
         if (!$field->shouldGenerateTransactionsFromComment()) {
           continue;
         }
 
         if (array_key_exists('initialValue', $action)) {
           $field->setInitialValue($action['initialValue']);
         }
 
         $field->readValueFromComment(idx($action, 'value'));
 
         $type_xactions = $field->generateTransactions(
           clone $template,
           array(
             'value' => $field->getValueForTransaction(),
           ));
         foreach ($type_xactions as $type_xaction) {
           $xactions[] = $type_xaction;
         }
       }
     }
 
     if (strlen($comment_text) || !$xactions) {
       $xactions[] = id(clone $template)
         ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
         ->attachComment(
           id(clone $comment_template)
             ->setContent($comment_text));
     }
 
     $editor = $object->getApplicationTransactionEditor()
       ->setActor($viewer)
       ->setContinueOnNoEffect($request->isContinueRequest())
       ->setContinueOnMissingFields(true)
       ->setContentSourceFromRequest($request)
       ->setIsPreview($is_preview);
 
     try {
       $xactions = $editor->applyTransactions($object, $xactions);
     } catch (PhabricatorApplicationTransactionNoEffectException $ex) {
       return id(new PhabricatorApplicationTransactionNoEffectResponse())
         ->setCancelURI($view_uri)
         ->setException($ex);
     }
 
     if (!$is_preview) {
       PhabricatorVersionedDraft::purgeDrafts(
         $object->getPHID(),
         $viewer->getPHID(),
         $this->loadDraftVersion($object));
     }
 
     if ($request->isAjax() && $is_preview) {
       return id(new PhabricatorApplicationTransactionResponse())
         ->setViewer($viewer)
         ->setTransactions($xactions)
         ->setIsPreview($is_preview);
     } else {
       return id(new AphrontRedirectResponse())
         ->setURI($view_uri);
     }
   }
 
 
 /* -(  Conduit  )------------------------------------------------------------ */
 
 
   /**
    * Respond to a Conduit edit request.
    *
    * This method accepts a list of transactions to apply to an object, and
    * either edits an existing object or creates a new one.
    *
    * @task conduit
    */
   final public function buildConduitResponse(ConduitAPIRequest $request) {
     $viewer = $this->getViewer();
 
     $config = $this->loadDefaultConfiguration();
     if (!$config) {
       throw new Exception(
         pht(
           'Unable to load configuration for this EditEngine ("%s").',
           get_class($this)));
     }
 
     $identifier = $request->getValue('objectIdentifier');
     if ($identifier) {
       $this->setIsCreate(false);
       $object = $this->newObjectFromIdentifier($identifier);
     } else {
       $this->requireCreateCapability();
 
       $this->setIsCreate(true);
       $object = $this->newEditableObject();
     }
 
     $this->validateObject($object);
 
     $fields = $this->buildEditFields($object);
 
     $types = $this->getConduitEditTypesFromFields($fields);
     $template = $object->getApplicationTransactionTemplate();
 
     $xactions = $this->getConduitTransactions($request, $types, $template);
 
     $editor = $object->getApplicationTransactionEditor()
       ->setActor($viewer)
       ->setContentSource($request->newContentSource())
       ->setContinueOnNoEffect(true);
 
     if (!$this->getIsCreate()) {
       $editor->setContinueOnMissingFields(true);
     }
 
     $xactions = $editor->applyTransactions($object, $xactions);
 
     $xactions_struct = array();
     foreach ($xactions as $xaction) {
       $xactions_struct[] = array(
         'phid' => $xaction->getPHID(),
       );
     }
 
     return array(
       'object' => array(
         'id' => $object->getID(),
         'phid' => $object->getPHID(),
       ),
       'transactions' => $xactions_struct,
     );
   }
 
 
   /**
    * Generate transactions which can be applied from edit actions in a Conduit
    * request.
    *
    * @param ConduitAPIRequest The request.
    * @param list<PhabricatorEditType> Supported edit types.
    * @param PhabricatorApplicationTransaction Template transaction.
    * @return list<PhabricatorApplicationTransaction> Generated transactions.
    * @task conduit
    */
   private function getConduitTransactions(
     ConduitAPIRequest $request,
     array $types,
     PhabricatorApplicationTransaction $template) {
 
     $viewer = $request->getUser();
     $transactions_key = 'transactions';
 
     $xactions = $request->getValue($transactions_key);
     if (!is_array($xactions)) {
       throw new Exception(
         pht(
           'Parameter "%s" is not a list of transactions.',
           $transactions_key));
     }
 
     foreach ($xactions as $key => $xaction) {
       if (!is_array($xaction)) {
         throw new Exception(
           pht(
             'Parameter "%s" must contain a list of transaction descriptions, '.
             'but item with key "%s" is not a dictionary.',
             $transactions_key,
             $key));
       }
 
       if (!array_key_exists('type', $xaction)) {
         throw new Exception(
           pht(
             'Parameter "%s" must contain a list of transaction descriptions, '.
             'but item with key "%s" is missing a "type" field. Each '.
             'transaction must have a type field.',
             $transactions_key,
             $key));
       }
 
       $type = $xaction['type'];
       if (empty($types[$type])) {
         throw new Exception(
           pht(
             'Transaction with key "%s" has invalid type "%s". This type is '.
             'not recognized. Valid types are: %s.',
             $key,
             $type,
             implode(', ', array_keys($types))));
       }
     }
 
     $results = array();
 
     if ($this->getIsCreate()) {
       $results[] = id(clone $template)
         ->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
     }
 
     foreach ($xactions as $xaction) {
       $type = $types[$xaction['type']];
 
       // Let the parameter type interpret the value. This allows you to
       // use usernames in list<user> fields, for example.
       $parameter_type = $type->getConduitParameterType();
 
       $parameter_type->setViewer($viewer);
 
       try {
         $xaction['value'] = $parameter_type->getValue($xaction, 'value');
       } catch (Exception $ex) {
         throw new PhutilProxyException(
           pht(
             'Exception when processing transaction of type "%s".',
             $xaction['type']),
           $ex);
       }
 
       $type_xactions = $type->generateTransactions(
         clone $template,
         $xaction);
 
       foreach ($type_xactions as $type_xaction) {
         $results[] = $type_xaction;
       }
     }
 
     return $results;
   }
 
 
   /**
    * @return map<string, PhabricatorEditType>
    * @task conduit
    */
   private function getConduitEditTypesFromFields(array $fields) {
     $types = array();
     foreach ($fields as $field) {
       $field_types = $field->getConduitEditTypes();
 
       if ($field_types === null) {
         continue;
       }
 
       foreach ($field_types as $field_type) {
         $field_type->setField($field);
         $types[$field_type->getEditType()] = $field_type;
       }
     }
     return $types;
   }
 
   public function getConduitEditTypes() {
     $config = $this->loadDefaultConfiguration();
     if (!$config) {
       return array();
     }
 
     $object = $this->newEditableObject();
     $fields = $this->buildEditFields($object);
     return $this->getConduitEditTypesFromFields($fields);
   }
 
   final public static function getAllEditEngines() {
     return id(new PhutilClassMapQuery())
       ->setAncestorClass(__CLASS__)
       ->setUniqueMethod('getEngineKey')
       ->execute();
   }
 
   final public static function getByKey(PhabricatorUser $viewer, $key) {
     return id(new PhabricatorEditEngineQuery())
       ->setViewer($viewer)
       ->withEngineKeys(array($key))
       ->executeOne();
   }
 
   public function getIcon() {
     $application = $this->getApplication();
     return $application->getIcon();
   }
 
   public function loadQuickCreateItems() {
     $items = array();
 
     if (!$this->hasCreateCapability()) {
       return $items;
     }
 
     $configs = $this->loadUsableConfigurationsForCreate();
 
     if (!$configs) {
       // No items to add.
     } else if (count($configs) == 1) {
       $config = head($configs);
       $items[] = $this->newQuickCreateItem($config);
     } else {
       $group_name = $this->getQuickCreateMenuHeaderText();
 
       $items[] = id(new PHUIListItemView())
         ->setType(PHUIListItemView::TYPE_LABEL)
         ->setName($group_name);
 
       foreach ($configs as $config) {
         $items[] = $this->newQuickCreateItem($config)
           ->setIndented(true);
       }
     }
 
     return $items;
   }
 
   private function loadUsableConfigurationsForCreate() {
     $viewer = $this->getViewer();
 
     $configs = id(new PhabricatorEditEngineConfigurationQuery())
       ->setViewer($viewer)
       ->withEngineKeys(array($this->getEngineKey()))
       ->withIsDefault(true)
       ->withIsDisabled(false)
       ->execute();
 
     $configs = msort($configs, 'getCreateSortKey');
 
     return $configs;
   }
 
   private function newQuickCreateItem(
     PhabricatorEditEngineConfiguration $config) {
 
     $item_name = $config->getName();
     $item_icon = $config->getIcon();
     $form_key = $config->getIdentifier();
     $item_uri = $this->getEditURI(null, "form/{$form_key}/");
 
     return id(new PHUIListItemView())
       ->setName($item_name)
       ->setIcon($item_icon)
       ->setHref($item_uri);
   }
 
   protected function getCreateNewObjectPolicy() {
     return PhabricatorPolicies::POLICY_USER;
   }
 
   private function requireCreateCapability() {
     PhabricatorPolicyFilter::requireCapability(
       $this->getViewer(),
       $this,
       PhabricatorPolicyCapability::CAN_EDIT);
   }
 
   private function hasCreateCapability() {
     return PhabricatorPolicyFilter::hasCapability(
       $this->getViewer(),
       $this,
       PhabricatorPolicyCapability::CAN_EDIT);
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getPHID() {
     return get_class($this);
   }
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::getMostOpenPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getCreateNewObjectPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 }
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
index 3cf04aeca..b6f95ecd1 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
@@ -1,161 +1,163 @@
 <?php
 
 abstract class PhabricatorEditEngineAPIMethod
   extends ConduitAPIMethod {
 
   abstract public function newEditEngine();
 
   public function getApplication() {
     $engine = $this->newEditEngine();
     $class = $engine->getEngineApplicationClass();
     return PhabricatorApplication::getByClass($class);
   }
 
   public function getMethodStatus() {
     return self::METHOD_STATUS_UNSTABLE;
   }
 
   public function getMethodStatusDescription() {
     return pht('ApplicationEditor methods are highly unstable.');
   }
 
   final protected function defineParamTypes() {
     return array(
       'transactions' => 'list<map<string, wild>>',
       'objectIdentifier' => 'optional id|phid|string',
     );
   }
 
   final protected function defineReturnType() {
     return 'map<string, wild>';
   }
 
   final protected function execute(ConduitAPIRequest $request) {
     $engine = $this->newEditEngine()
       ->setViewer($request->getUser());
 
     return $engine->buildConduitResponse($request);
   }
 
   final public function getMethodDescription() {
     return pht(
       'This is a standard **ApplicationEditor** method which allows you to '.
       'create and modify objects by applying transactions. For documentation '.
       'on these endpoints, see '.
       '**[[ %s | Conduit API: Using Edit Endpoints ]]**.',
       PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints'));
   }
 
   final public function getMethodDocumentation() {
     $viewer = $this->getViewer();
 
     $engine = $this->newEditEngine()
       ->setViewer($viewer);
 
     $types = $engine->getConduitEditTypes();
 
     $out = array();
 
     $out[] = $this->buildEditTypesBoxes($engine, $types);
 
     return $out;
   }
 
   private function buildEditTypesBoxes(
     PhabricatorEditEngine $engine,
     array $types) {
 
     $boxes = array();
 
     $summary_info = pht(
       'This endpoint supports these types of transactions. See below for '.
       'detailed information about each transaction type.');
 
     $rows = array();
     foreach ($types as $type) {
       $rows[] = array(
         $type->getEditType(),
         $type->getConduitDescription(),
       );
     }
 
     $summary_table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'wide',
         ));
 
     $boxes[] = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Transaction Types'))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($summary_info))
       ->appendChild($summary_table);
 
     foreach ($types as $type) {
       $section = array();
 
       $section[] = $type->getConduitDescription();
 
       $type_documentation = $type->getConduitDocumentation();
       if (strlen($type_documentation)) {
         $section[] = $type_documentation;
       }
 
       $section = implode("\n\n", $section);
 
       $rows = array();
 
       $rows[] = array(
         'type',
         'const',
         $type->getEditType(),
       );
 
       $rows[] = array(
         'value',
         $type->getConduitType(),
         $type->getConduitTypeDescription(),
       );
 
       $type_table = id(new AphrontTableView($rows))
         ->setHeaders(
           array(
             pht('Key'),
             pht('Type'),
             pht('Description'),
           ))
         ->setColumnClasses(
           array(
             'prewrap',
             'prewrap',
             'wide',
           ));
 
       $boxes[] = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Transaction Type: %s', $type->getEditType()))
       ->setCollapsed(true)
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($this->buildRemarkup($section))
       ->appendChild($type_table);
     }
 
     return $boxes;
   }
 
 
   private function buildRemarkup($remarkup) {
     $viewer = $this->getViewer();
 
     $view = new PHUIRemarkupView($viewer, $remarkup);
 
     return id(new PHUIBoxView())
       ->appendChild($view)
       ->addPadding(PHUI::PADDING_LARGE);
   }
 
 }
diff --git a/src/applications/transactions/editfield/PhabricatorColumnsEditField.php b/src/applications/transactions/editfield/PhabricatorColumnsEditField.php
new file mode 100644
index 000000000..fef51e286
--- /dev/null
+++ b/src/applications/transactions/editfield/PhabricatorColumnsEditField.php
@@ -0,0 +1,42 @@
+<?php
+
+final class PhabricatorColumnsEditField
+  extends PhabricatorPHIDListEditField {
+
+  private $columnMap;
+
+  public function setColumnMap(array $column_map) {
+    $this->columnMap = $column_map;
+    return $this;
+  }
+
+  public function getColumnMap() {
+    return $this->columnMap;
+  }
+
+  protected function newControl() {
+    $control = id(new AphrontFormHandlesControl());
+    $control->setIsInvisible(true);
+
+    return $control;
+  }
+
+  protected function newHTTPParameterType() {
+    return new AphrontPHIDListHTTPParameterType();
+  }
+
+  protected function newConduitParameterType() {
+    return new ConduitColumnsParameterType();
+  }
+
+  protected function newCommentAction() {
+    $column_map = $this->getColumnMap();
+    if (!$column_map) {
+      return null;
+    }
+
+    return id(new PhabricatorEditEngineColumnsCommentAction())
+      ->setColumnMap($this->getColumnMap());
+  }
+
+}
diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php
index 121637081..3b4c4e264 100644
--- a/src/applications/transactions/editfield/PhabricatorEditField.php
+++ b/src/applications/transactions/editfield/PhabricatorEditField.php
@@ -1,780 +1,791 @@
 <?php
 
 abstract class PhabricatorEditField extends Phobject {
 
   private $key;
   private $viewer;
   private $label;
   private $aliases = array();
   private $value;
   private $initialValue;
   private $hasValue = false;
   private $object;
   private $transactionType;
   private $metadata = array();
   private $editTypeKey;
   private $isRequired;
   private $previewPanel;
   private $controlID;
 
   private $description;
   private $conduitDescription;
   private $conduitDocumentation;
   private $conduitTypeDescription;
 
   private $commentActionLabel;
   private $commentActionValue;
+  private $commentActionOrder = 1000;
   private $hasCommentActionValue;
 
   private $isLocked;
   private $isHidden;
 
   private $isPreview;
   private $isEditDefaults;
   private $isSubmittedForm;
   private $controlError;
 
   private $isReorderable = true;
   private $isDefaultable = true;
   private $isLockable = true;
   private $isCopyable = false;
   private $isConduitOnly = false;
 
   private $conduitEditTypes;
 
   public function setKey($key) {
     $this->key = $key;
     return $this;
   }
 
   public function getKey() {
     return $this->key;
   }
 
   public function setLabel($label) {
     $this->label = $label;
     return $this;
   }
 
   public function getLabel() {
     return $this->label;
   }
 
   public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
   }
 
   public function getViewer() {
     return $this->viewer;
   }
 
   public function setAliases(array $aliases) {
     $this->aliases = $aliases;
     return $this;
   }
 
   public function getAliases() {
     return $this->aliases;
   }
 
   public function setObject($object) {
     $this->object = $object;
     return $this;
   }
 
   public function getObject() {
     return $this->object;
   }
 
   public function setIsLocked($is_locked) {
     $this->isLocked = $is_locked;
     return $this;
   }
 
   public function getIsLocked() {
     return $this->isLocked;
   }
 
   public function setIsPreview($preview) {
     $this->isPreview = $preview;
     return $this;
   }
 
   public function getIsPreview() {
     return $this->isPreview;
   }
 
   public function setIsReorderable($is_reorderable) {
     $this->isReorderable = $is_reorderable;
     return $this;
   }
 
   public function getIsReorderable() {
     return $this->isReorderable;
   }
 
   public function setIsConduitOnly($is_conduit_only) {
     $this->isConduitOnly = $is_conduit_only;
     return $this;
   }
 
   public function getIsConduitOnly() {
     return $this->isConduitOnly;
   }
 
   public function setDescription($description) {
     $this->description = $description;
     return $this;
   }
 
   public function getDescription() {
     return $this->description;
   }
 
   public function setConduitDescription($conduit_description) {
     $this->conduitDescription = $conduit_description;
     return $this;
   }
 
   public function getConduitDescription() {
     if ($this->conduitDescription === null) {
       return $this->getDescription();
     }
     return $this->conduitDescription;
   }
 
   public function setConduitDocumentation($conduit_documentation) {
     $this->conduitDocumentation = $conduit_documentation;
     return $this;
   }
 
   public function getConduitDocumentation() {
     return $this->conduitDocumentation;
   }
 
   public function setConduitTypeDescription($conduit_type_description) {
     $this->conduitTypeDescription = $conduit_type_description;
     return $this;
   }
 
   public function getConduitTypeDescription() {
     return $this->conduitTypeDescription;
   }
 
   public function setIsEditDefaults($is_edit_defaults) {
     $this->isEditDefaults = $is_edit_defaults;
     return $this;
   }
 
   public function getIsEditDefaults() {
     return $this->isEditDefaults;
   }
 
   public function setIsDefaultable($is_defaultable) {
     $this->isDefaultable = $is_defaultable;
     return $this;
   }
 
   public function getIsDefaultable() {
     return $this->isDefaultable;
   }
 
   public function setIsLockable($is_lockable) {
     $this->isLockable = $is_lockable;
     return $this;
   }
 
   public function getIsLockable() {
     return $this->isLockable;
   }
 
   public function setIsHidden($is_hidden) {
     $this->isHidden = $is_hidden;
     return $this;
   }
 
   public function getIsHidden() {
     return $this->isHidden;
   }
 
   public function setIsCopyable($is_copyable) {
     $this->isCopyable = $is_copyable;
     return $this;
   }
 
   public function getIsCopyable() {
     return $this->isCopyable;
   }
 
   public function setIsSubmittedForm($is_submitted) {
     $this->isSubmittedForm = $is_submitted;
     return $this;
   }
 
   public function getIsSubmittedForm() {
     return $this->isSubmittedForm;
   }
 
   public function setIsRequired($is_required) {
     $this->isRequired = $is_required;
     return $this;
   }
 
   public function getIsRequired() {
     return $this->isRequired;
   }
 
   public function setControlError($control_error) {
     $this->controlError = $control_error;
     return $this;
   }
 
   public function getControlError() {
     return $this->controlError;
   }
 
   public function setCommentActionLabel($label) {
     $this->commentActionLabel = $label;
     return $this;
   }
 
   public function getCommentActionLabel() {
     return $this->commentActionLabel;
   }
 
+  public function setCommentActionOrder($order) {
+    $this->commentActionOrder = $order;
+    return $this;
+  }
+
+  public function getCommentActionOrder() {
+    return $this->commentActionOrder;
+  }
+
   public function setCommentActionValue($comment_action_value) {
     $this->hasCommentActionValue = true;
     $this->commentActionValue = $comment_action_value;
     return $this;
   }
 
   public function getCommentActionValue() {
     return $this->commentActionValue;
   }
 
   public function setPreviewPanel(PHUIRemarkupPreviewPanel $preview_panel) {
     $this->previewPanel = $preview_panel;
     return $this;
   }
 
   public function getPreviewPanel() {
     return $this->previewPanel;
   }
 
   protected function newControl() {
     throw new PhutilMethodNotImplementedException();
   }
 
   protected function buildControl() {
     if ($this->getIsConduitOnly()) {
       return null;
     }
 
     $control = $this->newControl();
     if ($control === null) {
       return null;
     }
 
     $control
       ->setValue($this->getValueForControl())
       ->setName($this->getKey());
 
     if (!$control->getLabel()) {
       $control->setLabel($this->getLabel());
     }
 
     if ($this->getIsSubmittedForm()) {
       $error = $this->getControlError();
       if ($error !== null) {
         $control->setError($error);
       }
     } else if ($this->getIsRequired()) {
       $control->setError(true);
     }
 
     return $control;
   }
 
   public function getControlID() {
     if (!$this->controlID) {
       $this->controlID = celerity_generate_unique_node_id();
     }
     return $this->controlID;
   }
 
   protected function renderControl() {
     $control = $this->buildControl();
     if ($control === null) {
       return null;
     }
 
     if ($this->getIsPreview()) {
       $disabled = true;
       $hidden = false;
     } else if ($this->getIsEditDefaults()) {
       $disabled = false;
       $hidden = false;
     } else {
       $disabled = $this->getIsLocked();
       $hidden = $this->getIsHidden();
     }
 
     if ($hidden) {
       return null;
     }
 
     $control->setDisabled($disabled);
 
     if ($this->controlID) {
       $control->setID($this->controlID);
     }
 
     return $control;
   }
 
   public function appendToForm(AphrontFormView $form) {
     $control = $this->renderControl();
     if ($control !== null) {
 
       if ($this->getIsPreview()) {
         if ($this->getIsHidden()) {
           $control
             ->addClass('aphront-form-preview-hidden')
             ->setError(pht('Hidden'));
         } else if ($this->getIsLocked()) {
           $control
             ->setError(pht('Locked'));
         }
       }
 
       $form->appendControl($control);
     }
     return $this;
   }
 
   protected function getValueForControl() {
     return $this->getValue();
   }
 
   public function getValueForDefaults() {
     $value = $this->getValue();
 
     // By default, just treat the empty string like `null` since they're
     // equivalent for almost all fields and this reduces the number of
     // meaningless transactions we generate when adjusting defaults.
     if ($value === '') {
       return null;
     }
 
     return $value;
   }
 
   protected function getValue() {
     return $this->value;
   }
 
   public function setValue($value) {
     $this->hasValue = true;
     $this->initialValue = $value;
     $this->value = $value;
     return $this;
   }
 
   public function setMetadataValue($key, $value) {
     $this->metadata[$key] = $value;
     return $this;
   }
 
   public function getMetadata() {
     return $this->metadata;
   }
 
   public function getValueForTransaction() {
     return $this->getValue();
   }
 
   public function getTransactionType() {
     return $this->transactionType;
   }
 
   public function setTransactionType($type) {
     $this->transactionType = $type;
     return $this;
   }
 
   public function readValueFromRequest(AphrontRequest $request) {
     $check = $this->getAllReadValueFromRequestKeys();
     foreach ($check as $key) {
       if (!$this->getValueExistsInRequest($request, $key)) {
         continue;
       }
 
       $this->value = $this->getValueFromRequest($request, $key);
       break;
     }
     return $this;
   }
 
   public function readValueFromComment($value) {
     $this->value = $this->getValueFromComment($value);
     return $this;
   }
 
   protected function getValueFromComment($value) {
     return $value;
   }
 
   public function getAllReadValueFromRequestKeys() {
     $keys = array();
 
     $keys[] = $this->getKey();
     foreach ($this->getAliases() as $alias) {
       $keys[] = $alias;
     }
 
     return $keys;
   }
 
   public function readDefaultValueFromConfiguration($value) {
     $this->value = $this->getDefaultValueFromConfiguration($value);
     return $this;
   }
 
   protected function getDefaultValueFromConfiguration($value) {
     return $value;
   }
 
   protected function getValueFromObject($object) {
     if ($this->hasValue) {
       return $this->value;
     } else {
       return $this->getDefaultValue();
     }
   }
 
   protected function getValueExistsInRequest(AphrontRequest $request, $key) {
     return $this->getHTTPParameterValueExists($request, $key);
   }
 
   protected function getValueFromRequest(AphrontRequest $request, $key) {
     return $this->getHTTPParameterValue($request, $key);
   }
 
   public function readValueFromField(PhabricatorEditField $other) {
     $this->value = $this->getValueFromField($other);
     return $this;
   }
 
   protected function getValueFromField(PhabricatorEditField $other) {
     return $other->getValue();
   }
 
 
   /**
    * Read and return the value the object had when the user first loaded the
    * form.
    *
    * This is the initial value from the user's point of view when they started
    * the edit process, and used primarily to prevent race conditions for fields
    * like "Projects" and "Subscribers" that use tokenizers and support edge
    * transactions.
    *
    * Most fields do not need to store these values or deal with initial value
    * handling.
    *
    * @param AphrontRequest Request to read from.
    * @param string Key to read.
    * @return wild Value read from request.
    */
   protected function getInitialValueFromSubmit(AphrontRequest $request, $key) {
     return null;
   }
 
   public function getInitialValue() {
     return $this->initialValue;
   }
 
   public function setInitialValue($initial_value) {
     $this->initialValue = $initial_value;
     return $this;
   }
 
   public function readValueFromSubmit(AphrontRequest $request) {
     $key = $this->getKey();
     if ($this->getValueExistsInSubmit($request, $key)) {
       $value = $this->getValueFromSubmit($request, $key);
     } else {
       $value = $this->getDefaultValue();
     }
     $this->value = $value;
 
     $initial_value = $this->getInitialValueFromSubmit($request, $key);
     $this->initialValue = $initial_value;
 
     return $this;
   }
 
   protected function getValueExistsInSubmit(AphrontRequest $request, $key) {
     return $this->getHTTPParameterValueExists($request, $key);
   }
 
   protected function getValueFromSubmit(AphrontRequest $request, $key) {
     return $this->getHTTPParameterValue($request, $key);
   }
 
   protected function getHTTPParameterValueExists(
     AphrontRequest $request,
     $key) {
     $type = $this->getHTTPParameterType();
 
     if ($type) {
       return $type->getExists($request, $key);
     }
 
     return false;
   }
 
   protected function getHTTPParameterValue($request, $key) {
     $type = $this->getHTTPParameterType();
 
     if ($type) {
       return $type->getValue($request, $key);
     }
 
     return null;
   }
 
   protected function getDefaultValue() {
     $type = $this->getHTTPParameterType();
 
     if ($type) {
       return $type->getDefaultValue();
     }
 
     return null;
   }
 
   final public function getHTTPParameterType() {
     if ($this->getIsConduitOnly()) {
       return null;
     }
 
     $type = $this->newHTTPParameterType();
 
     if ($type) {
       $type->setViewer($this->getViewer());
     }
 
     return $type;
   }
 
   protected function newHTTPParameterType() {
     return new AphrontStringHTTPParameterType();
   }
 
   public function getConduitParameterType() {
     $type = $this->newConduitParameterType();
 
     if (!$type) {
       return null;
     }
 
     $type->setViewer($this->getViewer());
 
     return $type;
   }
 
   abstract protected function newConduitParameterType();
 
   public function setEditTypeKey($edit_type_key) {
     $this->editTypeKey = $edit_type_key;
     return $this;
   }
 
   public function getEditTypeKey() {
     if ($this->editTypeKey === null) {
       return $this->getKey();
     }
     return $this->editTypeKey;
   }
 
   protected function newEditType() {
     $parameter_type = $this->getConduitParameterType();
     if (!$parameter_type) {
       return null;
     }
 
     return id(new PhabricatorSimpleEditType())
       ->setConduitParameterType($parameter_type);
   }
 
   protected function getEditType() {
     $transaction_type = $this->getTransactionType();
 
     if ($transaction_type === null) {
       return null;
     }
 
     $type_key = $this->getEditTypeKey();
     $edit_type = $this->newEditType();
     if (!$edit_type) {
       return null;
     }
 
     return $edit_type
       ->setEditType($type_key)
       ->setTransactionType($transaction_type)
       ->setMetadata($this->getMetadata());
   }
 
   final public function getConduitEditTypes() {
     if ($this->conduitEditTypes === null) {
       $edit_types = $this->newConduitEditTypes();
       $edit_types = mpull($edit_types, null, 'getEditType');
 
       foreach ($edit_types as $edit_type) {
         $edit_type->setEditField($this);
       }
 
       $this->conduitEditTypes = $edit_types;
     }
 
     return $this->conduitEditTypes;
   }
 
   final public function getConduitEditType($key) {
     $edit_types = $this->getConduitEditTypes();
 
     if (empty($edit_types[$key])) {
       throw new Exception(
         pht(
           'This EditField does not provide a Conduit EditType with key "%s".',
           $key));
     }
 
     return $edit_types[$key];
   }
 
   protected function newConduitEditTypes() {
     $edit_type = $this->getEditType();
 
     if (!$edit_type) {
       return array();
     }
 
     return array($edit_type);
   }
 
   public function getCommentAction() {
     $label = $this->getCommentActionLabel();
     if ($label === null) {
       return null;
     }
 
     $action = $this->newCommentAction();
     if ($action === null) {
       return null;
     }
 
     if ($this->hasCommentActionValue) {
       $value = $this->getCommentActionValue();
     } else {
       $value = $this->getValue();
     }
 
     $action
       ->setKey($this->getKey())
       ->setLabel($label)
-      ->setValue($this->getValueForCommentAction($value));
+      ->setValue($this->getValueForCommentAction($value))
+      ->setOrder($this->getCommentActionOrder());
 
     return $action;
   }
 
   protected function newCommentAction() {
     return null;
   }
 
   protected function getValueForCommentAction($value) {
     return $value;
   }
 
   public function shouldGenerateTransactionsFromSubmit() {
     if ($this->getIsConduitOnly()) {
       return false;
     }
 
     $edit_type = $this->getEditType();
     if (!$edit_type) {
       return false;
     }
 
     return true;
   }
 
   public function shouldReadValueFromRequest() {
     if ($this->getIsConduitOnly()) {
       return false;
     }
 
     if ($this->getIsLocked()) {
       return false;
     }
 
     if ($this->getIsHidden()) {
       return false;
     }
 
     return true;
   }
 
   public function shouldReadValueFromSubmit() {
     if ($this->getIsConduitOnly()) {
       return false;
     }
 
     if ($this->getIsLocked()) {
       return false;
     }
 
     if ($this->getIsHidden()) {
       return false;
     }
 
     return true;
   }
 
   public function shouldGenerateTransactionsFromComment() {
     if ($this->getIsConduitOnly()) {
       return false;
     }
 
     if ($this->getIsLocked()) {
       return false;
     }
 
     if ($this->getIsHidden()) {
       return false;
     }
 
     return true;
   }
 
   public function generateTransactions(
     PhabricatorApplicationTransaction $template,
     array $spec) {
 
     $edit_type = $this->getEditType();
     if (!$edit_type) {
       throw new Exception(
         pht(
           'EditField (with key "%s", of class "%s") is generating '.
           'transactions, but has no EditType.',
           $this->getKey(),
           get_class($this)));
     }
 
     return $edit_type->generateTransactions($template, $spec);
   }
 
 }
diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php
new file mode 100644
index 000000000..c5dabe617
--- /dev/null
+++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php
@@ -0,0 +1,21 @@
+<?php
+
+final class PhabricatorEpochEditField
+  extends PhabricatorEditField {
+
+  protected function newControl() {
+    return id(new AphrontFormDateControl())
+      ->setViewer($this->getViewer());
+  }
+
+  protected function newHTTPParameterType() {
+    return new AphrontEpochHTTPParameterType();
+  }
+
+  protected function newConduitParameterType() {
+    // TODO: This isn't correct, but we don't have any methods which use this
+    // yet.
+    return new ConduitIntParameterType();
+  }
+
+}
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
index d7c0bbf5f..3f51ca729 100644
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -1,3474 +1,3537 @@
 <?php
 
 /**
  *
  * Publishing and Managing State
  * ======
  *
  * After applying changes, the Editor queues a worker to publish mail, feed,
  * and notifications, and to perform other background work like updating search
  * indexes. This allows it to do this work without impacting performance for
  * users.
  *
  * When work is moved to the daemons, the Editor state is serialized by
  * @{method:getWorkerState}, then reloaded in a daemon process by
  * @{method:loadWorkerState}. **This is fragile.**
  *
  * State is not persisted into the daemons by default, because we can not send
  * arbitrary objects into the queue. This means the default behavior of any
  * state properties is to reset to their defaults without warning prior to
  * publishing.
  *
  * The easiest way to avoid this is to keep Editors stateless: the overwhelming
  * majority of Editors can be written statelessly. If you need to maintain
  * state, you can either:
  *
  *   - not require state to exist during publishing; or
  *   - pass state to the daemons by implementing @{method:getCustomWorkerState}
  *     and @{method:loadCustomWorkerState}.
  *
  * This architecture isn't ideal, and we may eventually split this class into
  * "Editor" and "Publisher" parts to make it more robust. See T6367 for some
  * discussion and context.
  *
  * @task mail Sending Mail
  * @task feed Publishing Feed Stories
  * @task search Search Index
  * @task files Integration with Files
  * @task workers Managing Workers
  */
 abstract class PhabricatorApplicationTransactionEditor
   extends PhabricatorEditor {
 
   private $contentSource;
   private $object;
   private $xactions;
 
   private $isNewObject;
   private $mentionedPHIDs;
   private $continueOnNoEffect;
   private $continueOnMissingFields;
   private $parentMessageID;
   private $heraldAdapter;
   private $heraldTranscript;
   private $subscribers;
   private $unmentionablePHIDMap = array();
   private $applicationEmail;
 
   private $isPreview;
   private $isHeraldEditor;
   private $isInverseEdgeEditor;
   private $actingAsPHID;
   private $disableEmail;
 
   private $heraldEmailPHIDs = array();
   private $heraldForcedEmailPHIDs = array();
   private $heraldHeader;
   private $mailToPHIDs = array();
   private $mailCCPHIDs = array();
   private $feedNotifyPHIDs = array();
   private $feedRelatedPHIDs = array();
 
   const STORAGE_ENCODING_BINARY = 'binary';
 
   /**
    * Get the class name for the application this editor is a part of.
    *
    * Uninstalling the application will disable the editor.
    *
    * @return string Editor's application class name.
    */
   abstract public function getEditorApplicationClass();
 
 
   /**
    * Get a description of the objects this editor edits, like "Differential
    * Revisions".
    *
    * @return string Human readable description of edited objects.
    */
   abstract public function getEditorObjectsDescription();
 
 
   public function setActingAsPHID($acting_as_phid) {
     $this->actingAsPHID = $acting_as_phid;
     return $this;
   }
 
   public function getActingAsPHID() {
     if ($this->actingAsPHID) {
       return $this->actingAsPHID;
     }
     return $this->getActor()->getPHID();
   }
 
 
   /**
    * When the editor tries to apply transactions that have no effect, should
    * it raise an exception (default) or drop them and continue?
    *
    * Generally, you will set this flag for edits coming from "Edit" interfaces,
    * and leave it cleared for edits coming from "Comment" interfaces, so the
    * user will get a useful error if they try to submit a comment that does
    * nothing (e.g., empty comment with a status change that has already been
    * performed by another user).
    *
    * @param bool  True to drop transactions without effect and continue.
    * @return this
    */
   public function setContinueOnNoEffect($continue) {
     $this->continueOnNoEffect = $continue;
     return $this;
   }
 
   public function getContinueOnNoEffect() {
     return $this->continueOnNoEffect;
   }
 
 
   /**
    * When the editor tries to apply transactions which don't populate all of
    * an object's required fields, should it raise an exception (default) or
    * drop them and continue?
    *
    * For example, if a user adds a new required custom field (like "Severity")
    * to a task, all existing tasks won't have it populated. When users
    * manually edit existing tasks, it's usually desirable to have them provide
    * a severity. However, other operations (like batch editing just the
    * owner of a task) will fail by default.
    *
    * By setting this flag for edit operations which apply to specific fields
    * (like the priority, batch, and merge editors in Maniphest), these
    * operations can continue to function even if an object is outdated.
    *
    * @param bool  True to continue when transactions don't completely satisfy
    *              all required fields.
    * @return this
    */
   public function setContinueOnMissingFields($continue_on_missing_fields) {
     $this->continueOnMissingFields = $continue_on_missing_fields;
     return $this;
   }
 
   public function getContinueOnMissingFields() {
     return $this->continueOnMissingFields;
   }
 
 
   /**
    * Not strictly necessary, but reply handlers ideally set this value to
    * make email threading work better.
    */
   public function setParentMessageID($parent_message_id) {
     $this->parentMessageID = $parent_message_id;
     return $this;
   }
   public function getParentMessageID() {
     return $this->parentMessageID;
   }
 
   public function getIsNewObject() {
     return $this->isNewObject;
   }
 
   protected function getMentionedPHIDs() {
     return $this->mentionedPHIDs;
   }
 
   public function setIsPreview($is_preview) {
     $this->isPreview = $is_preview;
     return $this;
   }
 
   public function getIsPreview() {
     return $this->isPreview;
   }
 
   public function setIsInverseEdgeEditor($is_inverse_edge_editor) {
     $this->isInverseEdgeEditor = $is_inverse_edge_editor;
     return $this;
   }
 
   public function getIsInverseEdgeEditor() {
     return $this->isInverseEdgeEditor;
   }
 
   public function setIsHeraldEditor($is_herald_editor) {
     $this->isHeraldEditor = $is_herald_editor;
     return $this;
   }
 
   public function getIsHeraldEditor() {
     return $this->isHeraldEditor;
   }
 
   /**
    * Prevent this editor from generating email when applying transactions.
    *
    * @param bool  True to disable email.
    * @return this
    */
   public function setDisableEmail($disable_email) {
     $this->disableEmail = $disable_email;
     return $this;
   }
 
   public function getDisableEmail() {
     return $this->disableEmail;
   }
 
   public function setUnmentionablePHIDMap(array $map) {
     $this->unmentionablePHIDMap = $map;
     return $this;
   }
 
   public function getUnmentionablePHIDMap() {
     return $this->unmentionablePHIDMap;
   }
 
   protected function shouldEnableMentions(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   public function setApplicationEmail(
     PhabricatorMetaMTAApplicationEmail $email) {
     $this->applicationEmail = $email;
     return $this;
   }
 
   public function getApplicationEmail() {
     return $this->applicationEmail;
   }
 
   public function getTransactionTypesForObject($object) {
     $old = $this->object;
     try {
       $this->object = $object;
       $result = $this->getTransactionTypes();
       $this->object = $old;
     } catch (Exception $ex) {
       $this->object = $old;
       throw $ex;
     }
     return $result;
   }
 
   public function getTransactionTypes() {
     $types = array();
 
     $types[] = PhabricatorTransactions::TYPE_CREATE;
 
     if ($this->object instanceof PhabricatorSubscribableInterface) {
       $types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS;
     }
 
     if ($this->object instanceof PhabricatorCustomFieldInterface) {
       $types[] = PhabricatorTransactions::TYPE_CUSTOMFIELD;
     }
 
     if ($this->object instanceof HarbormasterBuildableInterface) {
       $types[] = PhabricatorTransactions::TYPE_BUILDABLE;
     }
 
     if ($this->object instanceof PhabricatorTokenReceiverInterface) {
       $types[] = PhabricatorTransactions::TYPE_TOKEN;
     }
 
     if ($this->object instanceof PhabricatorProjectInterface ||
         $this->object instanceof PhabricatorMentionableInterface) {
       $types[] = PhabricatorTransactions::TYPE_EDGE;
     }
 
     if ($this->object instanceof PhabricatorSpacesInterface) {
       $types[] = PhabricatorTransactions::TYPE_SPACE;
     }
 
     return $types;
   }
 
   private function adjustTransactionValues(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     if ($xaction->shouldGenerateOldValue()) {
       $old = $this->getTransactionOldValue($object, $xaction);
       $xaction->setOldValue($old);
     }
 
     $new = $this->getTransactionNewValue($object, $xaction);
     $xaction->setNewValue($new);
   }
 
   private function getTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return null;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return array_values($this->subscribers);
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         if ($this->getIsNewObject()) {
           return null;
         }
         return $object->getViewPolicy();
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         if ($this->getIsNewObject()) {
           return null;
         }
         return $object->getEditPolicy();
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         if ($this->getIsNewObject()) {
           return null;
         }
         return $object->getJoinPolicy();
       case PhabricatorTransactions::TYPE_SPACE:
         if ($this->getIsNewObject()) {
           return null;
         }
 
         $space_phid = $object->getSpacePHID();
         if ($space_phid === null) {
           $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
           if ($default_space) {
             $space_phid = $default_space->getPHID();
           }
         }
 
         return $space_phid;
       case PhabricatorTransactions::TYPE_EDGE:
         $edge_type = $xaction->getMetadataValue('edge:type');
         if (!$edge_type) {
           throw new Exception(
             pht(
               "Edge transaction has no '%s'!",
               'edge:type'));
         }
 
         $old_edges = array();
         if ($object->getPHID()) {
           $edge_src = $object->getPHID();
 
           $old_edges = id(new PhabricatorEdgeQuery())
             ->withSourcePHIDs(array($edge_src))
             ->withEdgeTypes(array($edge_type))
             ->needEdgeData(true)
             ->execute();
 
           $old_edges = $old_edges[$edge_src][$edge_type];
         }
         return $old_edges;
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         // NOTE: Custom fields have their old value pre-populated when they are
         // built by PhabricatorCustomFieldList.
         return $xaction->getOldValue();
       case PhabricatorTransactions::TYPE_COMMENT:
         return null;
       default:
         return $this->getCustomTransactionOldValue($object, $xaction);
     }
   }
 
   private function getTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return null;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return $this->getPHIDTransactionNewValue($xaction);
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
       case PhabricatorTransactions::TYPE_BUILDABLE:
       case PhabricatorTransactions::TYPE_TOKEN:
       case PhabricatorTransactions::TYPE_INLINESTATE:
         return $xaction->getNewValue();
       case PhabricatorTransactions::TYPE_SPACE:
         $space_phid = $xaction->getNewValue();
         if (!strlen($space_phid)) {
           // If an install has no Spaces or the Spaces controls are not visible
           // to the viewer, we might end up with the empty string here instead
           // of a strict `null`, because some controller just used `getStr()`
           // to read the space PHID from the request.
           // Just make this work like callers might reasonably expect so we
           // don't need to handle this specially in every EditController.
           return $this->getActor()->getDefaultSpacePHID();
         } else {
           return $space_phid;
         }
       case PhabricatorTransactions::TYPE_EDGE:
         $new_value = $this->getEdgeTransactionNewValue($xaction);
 
         $edge_type = $xaction->getMetadataValue('edge:type');
         $type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
         if ($edge_type == $type_project) {
           $new_value = $this->applyProjectConflictRules($new_value);
         }
 
         return $new_value;
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getCustomFieldForTransaction($object, $xaction);
         return $field->getNewValueFromApplicationTransactions($xaction);
       case PhabricatorTransactions::TYPE_COMMENT:
         return null;
       default:
         return $this->getCustomTransactionNewValue($object, $xaction);
     }
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     throw new Exception(pht('Capability not supported!'));
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     throw new Exception(pht('Capability not supported!'));
   }
 
   protected function transactionHasEffect(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return true;
       case PhabricatorTransactions::TYPE_COMMENT:
         return $xaction->hasComment();
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getCustomFieldForTransaction($object, $xaction);
         return $field->getApplicationTransactionHasEffect($xaction);
       case PhabricatorTransactions::TYPE_EDGE:
         // A straight value comparison here doesn't always get the right
         // result, because newly added edges aren't fully populated. Instead,
         // compare the changes in a more granular way.
         $old = $xaction->getOldValue();
         $new = $xaction->getNewValue();
 
         $old_dst = array_keys($old);
         $new_dst = array_keys($new);
 
         // NOTE: For now, we don't consider edge reordering to be a change.
         // We have very few order-dependent edges and effectively no order
         // oriented UI. This might change in the future.
         sort($old_dst);
         sort($new_dst);
 
         if ($old_dst !== $new_dst) {
           // We've added or removed edges, so this transaction definitely
           // has an effect.
           return true;
         }
 
         // We haven't added or removed edges, but we might have changed
         // edge data.
         foreach ($old as $key => $old_value) {
           $new_value = $new[$key];
           if ($old_value['data'] !== $new_value['data']) {
             return true;
           }
         }
 
         return false;
     }
 
     return ($xaction->getOldValue() !== $xaction->getNewValue());
   }
 
   protected function shouldApplyInitialEffects(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return false;
   }
 
   protected function applyInitialEffects(
     PhabricatorLiskDAO $object,
     array $xactions) {
     throw new PhutilMethodNotImplementedException();
   }
 
   private function applyInternalEffects(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getCustomFieldForTransaction($object, $xaction);
         return $field->applyApplicationTransactionInternalEffects($xaction);
       case PhabricatorTransactions::TYPE_CREATE:
       case PhabricatorTransactions::TYPE_BUILDABLE:
       case PhabricatorTransactions::TYPE_TOKEN:
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
       case PhabricatorTransactions::TYPE_INLINESTATE:
       case PhabricatorTransactions::TYPE_EDGE:
       case PhabricatorTransactions::TYPE_SPACE:
       case PhabricatorTransactions::TYPE_COMMENT:
         return $this->applyBuiltinInternalTransaction($object, $xaction);
     }
 
     return $this->applyCustomInternalTransaction($object, $xaction);
   }
 
   private function applyExternalEffects(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         $subeditor = id(new PhabricatorSubscriptionsEditor())
           ->setObject($object)
           ->setActor($this->requireActor());
 
         $old_map = array_fuse($xaction->getOldValue());
         $new_map = array_fuse($xaction->getNewValue());
 
         $subeditor->unsubscribe(
           array_keys(
             array_diff_key($old_map, $new_map)));
 
         $subeditor->subscribeExplicit(
           array_keys(
             array_diff_key($new_map, $old_map)));
 
         $subeditor->save();
 
         // for the rest of these edits, subscribers should include those just
         // added as well as those just removed.
         $subscribers = array_unique(array_merge(
           $this->subscribers,
           $xaction->getOldValue(),
           $xaction->getNewValue()));
         $this->subscribers = $subscribers;
         return $this->applyBuiltinExternalTransaction($object, $xaction);
 
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getCustomFieldForTransaction($object, $xaction);
         return $field->applyApplicationTransactionExternalEffects($xaction);
       case PhabricatorTransactions::TYPE_CREATE:
       case PhabricatorTransactions::TYPE_EDGE:
       case PhabricatorTransactions::TYPE_BUILDABLE:
       case PhabricatorTransactions::TYPE_TOKEN:
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
       case PhabricatorTransactions::TYPE_INLINESTATE:
       case PhabricatorTransactions::TYPE_SPACE:
       case PhabricatorTransactions::TYPE_COMMENT:
         return $this->applyBuiltinExternalTransaction($object, $xaction);
     }
 
     return $this->applyCustomExternalTransaction($object, $xaction);
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     $type = $xaction->getTransactionType();
     throw new Exception(
       pht(
         "Transaction type '%s' is missing an internal apply implementation!",
         $type));
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     $type = $xaction->getTransactionType();
     throw new Exception(
       pht(
         "Transaction type '%s' is missing an external apply implementation!",
         $type));
   }
 
   /**
    * @{class:PhabricatorTransactions} provides many built-in transactions
    * which should not require much - if any - code in specific applications.
    *
    * This method is a hook for the exceedingly-rare cases where you may need
    * to do **additional** work for built-in transactions. Developers should
    * extend this method, making sure to return the parent implementation
    * regardless of handling any transactions.
    *
    * See also @{method:applyBuiltinExternalTransaction}.
    */
   protected function applyBuiltinInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         $object->setViewPolicy($xaction->getNewValue());
         break;
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         $object->setEditPolicy($xaction->getNewValue());
         break;
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         $object->setJoinPolicy($xaction->getNewValue());
         break;
       case PhabricatorTransactions::TYPE_SPACE:
         $object->setSpacePHID($xaction->getNewValue());
         break;
     }
   }
 
   /**
    * See @{method::applyBuiltinInternalTransaction}.
    */
   protected function applyBuiltinExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_EDGE:
         if ($this->getIsInverseEdgeEditor()) {
           // If we're writing an inverse edge transaction, don't actually
           // do anything. The initiating editor on the other side of the
           // transaction will take care of the edge writes.
           break;
         }
 
         $old = $xaction->getOldValue();
         $new = $xaction->getNewValue();
         $src = $object->getPHID();
         $const = $xaction->getMetadataValue('edge:type');
 
         $type = PhabricatorEdgeType::getByConstant($const);
         if ($type->shouldWriteInverseTransactions()) {
           $this->applyInverseEdgeTransactions(
             $object,
             $xaction,
             $type->getInverseEdgeConstant());
         }
 
         foreach ($new as $dst_phid => $edge) {
           $new[$dst_phid]['src'] = $src;
         }
 
         $editor = new PhabricatorEdgeEditor();
 
         foreach ($old as $dst_phid => $edge) {
           if (!empty($new[$dst_phid])) {
             if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
               continue;
             }
           }
           $editor->removeEdge($src, $const, $dst_phid);
         }
 
         foreach ($new as $dst_phid => $edge) {
           if (!empty($old[$dst_phid])) {
             if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
               continue;
             }
           }
 
           $data = array(
             'data' => $edge['data'],
           );
 
           $editor->addEdge($src, $const, $dst_phid, $data);
         }
 
         $editor->save();
         break;
+      case PhabricatorTransactions::TYPE_VIEW_POLICY:
+      case PhabricatorTransactions::TYPE_SPACE:
+        $this->scrambleFileSecrets($object);
+        break;
     }
   }
 
   /**
    * Fill in a transaction's common values, like author and content source.
    */
   protected function populateTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $actor = $this->getActor();
 
     // TODO: This needs to be more sophisticated once we have meta-policies.
     $xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC);
 
     if ($actor->isOmnipotent()) {
       $xaction->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     } else {
       $xaction->setEditPolicy($this->getActingAsPHID());
     }
 
     $xaction->setAuthorPHID($this->getActingAsPHID());
     $xaction->setContentSource($this->getContentSource());
     $xaction->attachViewer($actor);
     $xaction->attachObject($object);
 
     if ($object->getPHID()) {
       $xaction->setObjectPHID($object->getPHID());
     }
 
     return $xaction;
   }
 
   protected function didApplyInternalEffects(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return $xactions;
   }
 
   protected function applyFinalEffects(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return $xactions;
   }
 
   public function setContentSource(PhabricatorContentSource $content_source) {
     $this->contentSource = $content_source;
     return $this;
   }
 
   public function setContentSourceFromRequest(AphrontRequest $request) {
     return $this->setContentSource(
       PhabricatorContentSource::newFromRequest($request));
   }
 
   public function getContentSource() {
     return $this->contentSource;
   }
 
   final public function applyTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $this->object = $object;
     $this->xactions = $xactions;
     $this->isNewObject = ($object->getPHID() === null);
 
     $this->validateEditParameters($object, $xactions);
 
     $actor = $this->requireActor();
 
     // NOTE: Some transaction expansion requires that the edited object be
     // attached.
     foreach ($xactions as $xaction) {
       $xaction->attachObject($object);
       $xaction->attachViewer($actor);
     }
 
     $xactions = $this->expandTransactions($object, $xactions);
     $xactions = $this->expandSupportTransactions($object, $xactions);
     $xactions = $this->combineTransactions($xactions);
 
     foreach ($xactions as $xaction) {
       $xaction = $this->populateTransaction($object, $xaction);
     }
 
     $is_preview = $this->getIsPreview();
     $read_locking = false;
     $transaction_open = false;
 
     if (!$is_preview) {
       $errors = array();
       $type_map = mgroup($xactions, 'getTransactionType');
       foreach ($this->getTransactionTypes() as $type) {
         $type_xactions = idx($type_map, $type, array());
         $errors[] = $this->validateTransaction($object, $type, $type_xactions);
       }
 
       $errors[] = $this->validateAllTransactions($object, $xactions);
       $errors = array_mergev($errors);
 
       $continue_on_missing = $this->getContinueOnMissingFields();
       foreach ($errors as $key => $error) {
         if ($continue_on_missing && $error->getIsMissingFieldError()) {
           unset($errors[$key]);
         }
       }
 
       if ($errors) {
         throw new PhabricatorApplicationTransactionValidationException($errors);
       }
 
       if ($object->getID()) {
         foreach ($xactions as $xaction) {
 
           // If any of the transactions require a read lock, hold one and
           // reload the object. We need to do this fairly early so that the
           // call to `adjustTransactionValues()` (which populates old values)
           // is based on the synchronized state of the object, which may differ
           // from the state when it was originally loaded.
 
           if ($this->shouldReadLock($object, $xaction)) {
             $object->openTransaction();
             $object->beginReadLocking();
             $transaction_open = true;
             $read_locking = true;
             $object->reload();
             break;
           }
         }
       }
 
       if ($this->shouldApplyInitialEffects($object, $xactions)) {
         if (!$transaction_open) {
           $object->openTransaction();
           $transaction_open = true;
         }
       }
     }
 
     if ($this->shouldApplyInitialEffects($object, $xactions)) {
       $this->applyInitialEffects($object, $xactions);
     }
 
     foreach ($xactions as $xaction) {
       $this->adjustTransactionValues($object, $xaction);
     }
 
     try {
       $xactions = $this->filterTransactions($object, $xactions);
     } catch (Exception $ex) {
       if ($read_locking) {
         $object->endReadLocking();
       }
       if ($transaction_open) {
         $object->killTransaction();
       }
       throw $ex;
     }
 
     // TODO: Once everything is on EditEngine, just use getIsNewObject() to
     // figure this out instead.
     $mark_as_create = false;
     $create_type = PhabricatorTransactions::TYPE_CREATE;
     foreach ($xactions as $xaction) {
       if ($xaction->getTransactionType() == $create_type) {
         $mark_as_create = true;
       }
     }
 
     if ($mark_as_create) {
       foreach ($xactions as $xaction) {
         $xaction->setIsCreateTransaction(true);
       }
     }
 
     // Now that we've merged, filtered, and combined transactions, check for
     // required capabilities.
     foreach ($xactions as $xaction) {
       $this->requireCapabilities($object, $xaction);
     }
 
     $xactions = $this->sortTransactions($xactions);
     $file_phids = $this->extractFilePHIDs($object, $xactions);
 
     if ($is_preview) {
       $this->loadHandles($xactions);
       return $xactions;
     }
 
     $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor())
       ->setActor($actor)
       ->setActingAsPHID($this->getActingAsPHID())
       ->setContentSource($this->getContentSource());
 
     if (!$transaction_open) {
       $object->openTransaction();
     }
 
       foreach ($xactions as $xaction) {
         $this->applyInternalEffects($object, $xaction);
       }
 
       $xactions = $this->didApplyInternalEffects($object, $xactions);
 
       try {
         $object->save();
       } catch (AphrontDuplicateKeyQueryException $ex) {
         $object->killTransaction();
 
         // This callback has an opportunity to throw a better exception,
         // so execution may end here.
         $this->didCatchDuplicateKeyException($object, $xactions, $ex);
 
         throw $ex;
       }
 
       foreach ($xactions as $xaction) {
         $xaction->setObjectPHID($object->getPHID());
         if ($xaction->getComment()) {
           $xaction->setPHID($xaction->generatePHID());
           $comment_editor->applyEdit($xaction, $xaction->getComment());
         } else {
           $xaction->save();
         }
       }
 
       if ($file_phids) {
         $this->attachFiles($object, $file_phids);
       }
 
       foreach ($xactions as $xaction) {
         $this->applyExternalEffects($object, $xaction);
       }
 
       $xactions = $this->applyFinalEffects($object, $xactions);
-
       if ($read_locking) {
         $object->endReadLocking();
         $read_locking = false;
       }
 
     $object->saveTransaction();
 
     // Now that we've completely applied the core transaction set, try to apply
     // Herald rules. Herald rules are allowed to either take direct actions on
     // the database (like writing flags), or take indirect actions (like saving
     // some targets for CC when we generate mail a little later), or return
     // transactions which we'll apply normally using another Editor.
 
     // First, check if *this* is a sub-editor which is itself applying Herald
     // rules: if it is, stop working and return so we don't descend into
     // madness.
 
     // Otherwise, we're not a Herald editor, so process Herald rules (possibly
     // using a Herald editor to apply resulting transactions) and then send out
     // mail, notifications, and feed updates about everything.
 
     if ($this->getIsHeraldEditor()) {
       // We are the Herald editor, so stop work here and return the updated
       // transactions.
       return $xactions;
     } else if ($this->getIsInverseEdgeEditor()) {
       // If we're applying inverse edge transactions, don't trigger Herald.
       // From a product perspective, the current set of inverse edges (most
       // often, mentions) aren't things users would expect to trigger Herald.
       // From a technical perspective, objects loaded by the inverse editor may
       // not have enough data to execute rules. At least for now, just stop
       // Herald from executing when applying inverse edges.
     } else if ($this->shouldApplyHeraldRules($object, $xactions)) {
       // We are not the Herald editor, so try to apply Herald rules.
       $herald_xactions = $this->applyHeraldRules($object, $xactions);
 
       if ($herald_xactions) {
         $xscript_id = $this->getHeraldTranscript()->getID();
         foreach ($herald_xactions as $herald_xaction) {
           $herald_xaction->setMetadataValue('herald:transcriptID', $xscript_id);
         }
 
         // NOTE: We're acting as the omnipotent user because rules deal with
         // their own policy issues. We use a synthetic author PHID (the
         // Herald application) as the author of record, so that transactions
         // will render in a reasonable way ("Herald assigned this task ...").
         $herald_actor = PhabricatorUser::getOmnipotentUser();
         $herald_phid = id(new PhabricatorHeraldApplication())->getPHID();
 
         // TODO: It would be nice to give transactions a more specific source
         // which points at the rule which generated them. You can figure this
         // out from transcripts, but it would be cleaner if you didn't have to.
 
         $herald_source = PhabricatorContentSource::newForSource(
           PhabricatorHeraldContentSource::SOURCECONST);
 
         $herald_editor = newv(get_class($this), array())
           ->setContinueOnNoEffect(true)
           ->setContinueOnMissingFields(true)
           ->setParentMessageID($this->getParentMessageID())
           ->setIsHeraldEditor(true)
           ->setActor($herald_actor)
           ->setActingAsPHID($herald_phid)
           ->setContentSource($herald_source);
 
         $herald_xactions = $herald_editor->applyTransactions(
           $object,
           $herald_xactions);
 
         // Merge the new transactions into the transaction list: we want to
         // send email and publish feed stories about them, too.
         $xactions = array_merge($xactions, $herald_xactions);
       }
 
       // If Herald did not generate transactions, we may still need to handle
       // "Send an Email" rules.
       $adapter = $this->getHeraldAdapter();
       $this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
       $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs();
     }
 
     $this->didApplyTransactions($xactions);
 
     if ($object instanceof PhabricatorCustomFieldInterface) {
       // Maybe this makes more sense to move into the search index itself? For
       // now I'm putting it here since I think we might end up with things that
       // need it to be up to date once the next page loads, but if we don't go
       // there we could move it into search once search moves to the daemons.
 
       // It now happens in the search indexer as well, but the search indexer is
       // always daemonized, so the logic above still potentially holds. We could
       // possibly get rid of this. The major motivation for putting it in the
       // indexer was to enable reindexing to work.
 
       $fields = PhabricatorCustomField::getObjectFields(
         $object,
         PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
       $fields->readFieldsFromStorage($object);
       $fields->rebuildIndexes($object);
     }
 
     $herald_xscript = $this->getHeraldTranscript();
     if ($herald_xscript) {
       $herald_header = $herald_xscript->getXHeraldRulesHeader();
       $herald_header = HeraldTranscript::saveXHeraldRulesHeader(
         $object->getPHID(),
         $herald_header);
     } else {
       $herald_header = HeraldTranscript::loadXHeraldRulesHeader(
         $object->getPHID());
     }
     $this->heraldHeader = $herald_header;
 
     // We're going to compute some of the data we'll use to publish these
     // transactions here, before queueing a worker.
     //
     // Primarily, this is more correct: we want to publish the object as it
     // exists right now. The worker may not execute for some time, and we want
     // to use the current To/CC list, not respect any changes which may occur
     // between now and when the worker executes.
     //
     // As a secondary benefit, this tends to reduce the amount of state that
     // Editors need to pass into workers.
     $object = $this->willPublish($object, $xactions);
 
     if (!$this->getDisableEmail()) {
       if ($this->shouldSendMail($object, $xactions)) {
         $this->mailToPHIDs = $this->getMailTo($object);
         $this->mailCCPHIDs = $this->getMailCC($object);
       }
     }
 
     if ($this->shouldPublishFeedStory($object, $xactions)) {
       $this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs($object, $xactions);
       $this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs($object, $xactions);
     }
 
     PhabricatorWorker::scheduleTask(
       'PhabricatorApplicationTransactionPublishWorker',
       array(
         'objectPHID' => $object->getPHID(),
         'actorPHID' => $this->getActingAsPHID(),
         'xactionPHIDs' => mpull($xactions, 'getPHID'),
         'state' => $this->getWorkerState(),
       ),
       array(
         'objectPHID' => $object->getPHID(),
         'priority' => PhabricatorWorker::PRIORITY_ALERTS,
       ));
 
     return $xactions;
   }
 
   protected function didCatchDuplicateKeyException(
     PhabricatorLiskDAO $object,
     array $xactions,
     Exception $ex) {
     return;
   }
 
   public function publishTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     // Hook for edges or other properties that may need (re-)loading
     $object = $this->willPublish($object, $xactions);
 
     $messages = array();
     if (!$this->getDisableEmail()) {
       if ($this->shouldSendMail($object, $xactions)) {
         $messages = $this->buildMail($object, $xactions);
       }
     }
 
     if ($this->supportsSearch()) {
       PhabricatorSearchWorker::queueDocumentForIndexing(
         $object->getPHID(),
         array(
           'transactionPHIDs' => mpull($xactions, 'getPHID'),
         ));
     }
 
     if ($this->shouldPublishFeedStory($object, $xactions)) {
 
       $mailed = array();
       foreach ($messages as $mail) {
         foreach ($mail->buildRecipientList() as $phid) {
           $mailed[$phid] = $phid;
         }
       }
 
       $this->publishFeedStory($object, $xactions, $mailed);
     }
 
     // NOTE: This actually sends the mail. We do this last to reduce the chance
     // that we send some mail, hit an exception, then send the mail again when
     // retrying.
     foreach ($messages as $mail) {
       $mail->save();
     }
 
     return $xactions;
   }
 
   protected function didApplyTransactions(array $xactions) {
     // Hook for subclasses.
     return;
   }
 
 
   /**
    * Determine if the editor should hold a read lock on the object while
    * applying a transaction.
    *
    * If the editor does not hold a lock, two editors may read an object at the
    * same time, then apply their changes without any synchronization. For most
    * transactions, this does not matter much. However, it is important for some
    * transactions. For example, if an object has a transaction count on it, both
    * editors may read the object with `count = 23`, then independently update it
    * and save the object with `count = 24` twice. This will produce the wrong
    * state: the object really has 25 transactions, but the count is only 24.
    *
    * Generally, transactions fall into one of four buckets:
    *
    *   - Append operations: Actions like adding a comment to an object purely
    *     add information to its state, and do not depend on the current object
    *     state in any way. These transactions never need to hold locks.
    *   - Overwrite operations: Actions like changing the title or description
    *     of an object replace the current value with a new value, so the end
    *     state is consistent without a lock. We currently do not lock these
    *     transactions, although we may in the future.
    *   - Edge operations: Edge and subscription operations have internal
    *     synchronization which limits the damage race conditions can cause.
    *     We do not currently lock these transactions, although we may in the
    *     future.
    *   - Update operations: Actions like incrementing a count on an object.
    *     These operations generally should use locks, unless it is not
    *     important that the state remain consistent in the presence of races.
    *
    * @param   PhabricatorLiskDAO  Object being updated.
    * @param   PhabricatorApplicationTransaction Transaction being applied.
    * @return  bool                True to synchronize the edit with a lock.
    */
   protected function shouldReadLock(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     return false;
   }
 
   private function loadHandles(array $xactions) {
     $phids = array();
     foreach ($xactions as $key => $xaction) {
       $phids[$key] = $xaction->getRequiredHandlePHIDs();
     }
     $handles = array();
     $merged = array_mergev($phids);
     if ($merged) {
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($this->requireActor())
         ->withPHIDs($merged)
         ->execute();
     }
     foreach ($xactions as $key => $xaction) {
       $xaction->setHandles(array_select_keys($handles, $phids[$key]));
     }
   }
 
   private function loadSubscribers(PhabricatorLiskDAO $object) {
     if ($object->getPHID() &&
         ($object instanceof PhabricatorSubscribableInterface)) {
       $subs = PhabricatorSubscribersQuery::loadSubscribersForPHID(
         $object->getPHID());
       $this->subscribers = array_fuse($subs);
     } else {
       $this->subscribers = array();
     }
   }
 
   private function validateEditParameters(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     if (!$this->getContentSource()) {
       throw new PhutilInvalidStateException('setContentSource');
     }
 
     // Do a bunch of sanity checks that the incoming transactions are fresh.
     // They should be unsaved and have only "transactionType" and "newValue"
     // set.
 
     $types = array_fill_keys($this->getTransactionTypes(), true);
 
     assert_instances_of($xactions, 'PhabricatorApplicationTransaction');
     foreach ($xactions as $xaction) {
       if ($xaction->getPHID() || $xaction->getID()) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht('You can not apply transactions which already have IDs/PHIDs!'));
       }
       if ($xaction->getObjectPHID()) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'You can not apply transactions which already have %s!',
             'objectPHIDs'));
       }
       if ($xaction->getAuthorPHID()) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'You can not apply transactions which already have %s!',
             'authorPHIDs'));
       }
       if ($xaction->getCommentPHID()) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'You can not apply transactions which already have %s!',
             'commentPHIDs'));
       }
       if ($xaction->getCommentVersion() !== 0) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'You can not apply transactions which already have '.
             'commentVersions!'));
       }
 
       $expect_value = !$xaction->shouldGenerateOldValue();
       $has_value = $xaction->hasOldValue();
 
       if ($expect_value && !$has_value) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'This transaction is supposed to have an %s set, but it does not!',
             'oldValue'));
       }
 
       if ($has_value && !$expect_value) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'This transaction should generate its %s automatically, '.
             'but has already had one set!',
             'oldValue'));
       }
 
       $type = $xaction->getTransactionType();
       if (empty($types[$type])) {
         throw new PhabricatorApplicationTransactionStructureException(
           $xaction,
           pht(
             'Transaction has type "%s", but that transaction type is not '.
             'supported by this editor (%s).',
             $type,
             get_class($this)));
       }
     }
   }
 
   protected function requireCapabilities(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     if ($this->getIsNewObject()) {
       return;
     }
 
     $actor = $this->requireActor();
     switch ($xaction->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         PhabricatorPolicyFilter::requireCapability(
           $actor,
           $object,
           PhabricatorPolicyCapability::CAN_VIEW);
         break;
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
       case PhabricatorTransactions::TYPE_SPACE:
         PhabricatorPolicyFilter::requireCapability(
           $actor,
           $object,
           PhabricatorPolicyCapability::CAN_EDIT);
         break;
     }
   }
 
   private function buildSubscribeTransaction(
     PhabricatorLiskDAO $object,
     array $xactions,
     array $blocks) {
 
     if (!($object instanceof PhabricatorSubscribableInterface)) {
       return null;
     }
 
     if ($this->shouldEnableMentions($object, $xactions)) {
       $texts = array_mergev($blocks);
       $phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
         $this->getActor(),
         $texts);
     } else {
       $phids = array();
     }
 
     $this->mentionedPHIDs = $phids;
 
     if ($object->getPHID()) {
       // Don't try to subscribe already-subscribed mentions: we want to generate
       // a dialog about an action having no effect if the user explicitly adds
       // existing CCs, but not if they merely mention existing subscribers.
       $phids = array_diff($phids, $this->subscribers);
     }
 
     if ($phids) {
       $users = id(new PhabricatorPeopleQuery())
         ->setViewer($this->getActor())
         ->withPHIDs($phids)
         ->execute();
       $users = mpull($users, null, 'getPHID');
 
       foreach ($phids as $key => $phid) {
         // Do not subscribe mentioned users
         // who do not have VIEW Permissions
         if ($object instanceof PhabricatorPolicyInterface
           && !PhabricatorPolicyFilter::hasCapability(
           $users[$phid],
           $object,
           PhabricatorPolicyCapability::CAN_VIEW)
         ) {
           unset($phids[$key]);
         } else {
           if ($object->isAutomaticallySubscribed($phid)) {
             unset($phids[$key]);
           }
         }
       }
       $phids = array_values($phids);
     }
     // No else here to properly return null should we unset all subscriber
     if (!$phids) {
       return null;
     }
 
     $xaction = newv(get_class(head($xactions)), array());
     $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
     $xaction->setNewValue(array('+' => $phids));
 
     return $xaction;
   }
 
   protected function getRemarkupBlocksFromTransaction(
     PhabricatorApplicationTransaction $transaction) {
     return $transaction->getRemarkupBlocks();
   }
 
   protected function mergeTransactions(
     PhabricatorApplicationTransaction $u,
     PhabricatorApplicationTransaction $v) {
 
     $type = $u->getTransactionType();
 
     switch ($type) {
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return $this->mergePHIDOrEdgeTransactions($u, $v);
       case PhabricatorTransactions::TYPE_EDGE:
         $u_type = $u->getMetadataValue('edge:type');
         $v_type = $v->getMetadataValue('edge:type');
         if ($u_type == $v_type) {
           return $this->mergePHIDOrEdgeTransactions($u, $v);
         }
         return null;
     }
 
     // By default, do not merge the transactions.
     return null;
   }
 
   /**
    * Optionally expand transactions which imply other effects. For example,
    * resigning from a revision in Differential implies removing yourself as
    * a reviewer.
    */
   protected function expandTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $results = array();
     foreach ($xactions as $xaction) {
       foreach ($this->expandTransaction($object, $xaction) as $expanded) {
         $results[] = $expanded;
       }
     }
 
     return $results;
   }
 
   protected function expandTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     return array($xaction);
   }
 
 
   public function getExpandedSupportTransactions(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $xactions = array($xaction);
     $xactions = $this->expandSupportTransactions(
       $object,
       $xactions);
 
     if (count($xactions) == 1) {
       return array();
     }
 
     foreach ($xactions as $index => $cxaction) {
       if ($cxaction === $xaction) {
         unset($xactions[$index]);
         break;
       }
     }
 
     return $xactions;
   }
 
   private function expandSupportTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
     $this->loadSubscribers($object);
 
     $xactions = $this->applyImplicitCC($object, $xactions);
 
     $blocks = array();
     foreach ($xactions as $key => $xaction) {
       $blocks[$key] = $this->getRemarkupBlocksFromTransaction($xaction);
     }
 
     $subscribe_xaction = $this->buildSubscribeTransaction(
       $object,
       $xactions,
       $blocks);
     if ($subscribe_xaction) {
       $xactions[] = $subscribe_xaction;
     }
 
     // TODO: For now, this is just a placeholder.
     $engine = PhabricatorMarkupEngine::getEngine('extract');
     $engine->setConfig('viewer', $this->requireActor());
 
     $block_xactions = $this->expandRemarkupBlockTransactions(
       $object,
       $xactions,
       $blocks,
       $engine);
 
     foreach ($block_xactions as $xaction) {
       $xactions[] = $xaction;
     }
 
     return $xactions;
   }
 
   private function expandRemarkupBlockTransactions(
     PhabricatorLiskDAO $object,
     array $xactions,
     $blocks,
     PhutilMarkupEngine $engine) {
 
     $block_xactions = $this->expandCustomRemarkupBlockTransactions(
       $object,
       $xactions,
       $blocks,
       $engine);
 
     $mentioned_phids = array();
     if ($this->shouldEnableMentions($object, $xactions)) {
       foreach ($blocks as $key => $xaction_blocks) {
         foreach ($xaction_blocks as $block) {
           $engine->markupText($block);
           $mentioned_phids += $engine->getTextMetadata(
             PhabricatorObjectRemarkupRule::KEY_MENTIONED_OBJECTS,
             array());
         }
       }
     }
 
     if (!$mentioned_phids) {
       return $block_xactions;
     }
 
     $mentioned_objects = id(new PhabricatorObjectQuery())
       ->setViewer($this->getActor())
       ->withPHIDs($mentioned_phids)
       ->execute();
 
     $mentionable_phids = array();
     if ($this->shouldEnableMentions($object, $xactions)) {
       foreach ($mentioned_objects as $mentioned_object) {
         if ($mentioned_object instanceof PhabricatorMentionableInterface) {
           $mentioned_phid = $mentioned_object->getPHID();
           if (idx($this->getUnmentionablePHIDMap(), $mentioned_phid)) {
             continue;
           }
           // don't let objects mention themselves
           if ($object->getPHID() && $mentioned_phid == $object->getPHID()) {
             continue;
           }
           $mentionable_phids[$mentioned_phid] = $mentioned_phid;
         }
       }
     }
 
     if ($mentionable_phids) {
       $edge_type = PhabricatorObjectMentionsObjectEdgeType::EDGECONST;
       $block_xactions[] = newv(get_class(head($xactions)), array())
         ->setIgnoreOnNoEffect(true)
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $edge_type)
         ->setNewValue(array('+' => $mentionable_phids));
     }
 
     return $block_xactions;
   }
 
   protected function expandCustomRemarkupBlockTransactions(
     PhabricatorLiskDAO $object,
     array $xactions,
     $blocks,
     PhutilMarkupEngine $engine) {
     return array();
   }
 
 
   /**
    * Attempt to combine similar transactions into a smaller number of total
    * transactions. For example, two transactions which edit the title of an
    * object can be merged into a single edit.
    */
   private function combineTransactions(array $xactions) {
     $stray_comments = array();
 
     $result = array();
     $types = array();
     foreach ($xactions as $key => $xaction) {
       $type = $xaction->getTransactionType();
       if (isset($types[$type])) {
         foreach ($types[$type] as $other_key) {
           $merged = $this->mergeTransactions($result[$other_key], $xaction);
           if ($merged) {
             $result[$other_key] = $merged;
 
             if ($xaction->getComment() &&
                 ($xaction->getComment() !== $merged->getComment())) {
               $stray_comments[] = $xaction->getComment();
             }
 
             if ($result[$other_key]->getComment() &&
                 ($result[$other_key]->getComment() !== $merged->getComment())) {
               $stray_comments[] = $result[$other_key]->getComment();
             }
 
             // Move on to the next transaction.
             continue 2;
           }
         }
       }
       $result[$key] = $xaction;
       $types[$type][] = $key;
     }
 
     // If we merged any comments away, restore them.
     foreach ($stray_comments as $comment) {
       $xaction = newv(get_class(head($result)), array());
       $xaction->setTransactionType(PhabricatorTransactions::TYPE_COMMENT);
       $xaction->setComment($comment);
       $result[] = $xaction;
     }
 
     return array_values($result);
   }
 
   protected function mergePHIDOrEdgeTransactions(
     PhabricatorApplicationTransaction $u,
     PhabricatorApplicationTransaction $v) {
 
     $result = $u->getNewValue();
     foreach ($v->getNewValue() as $key => $value) {
       if ($u->getTransactionType() == PhabricatorTransactions::TYPE_EDGE) {
         if (empty($result[$key])) {
           $result[$key] = $value;
         } else {
           // We're merging two lists of edge adds, sets, or removes. Merge
           // them by merging individual PHIDs within them.
           $merged = $result[$key];
 
           foreach ($value as $dst => $v_spec) {
             if (empty($merged[$dst])) {
               $merged[$dst] = $v_spec;
             } else {
               // Two transactions are trying to perform the same operation on
               // the same edge. Normalize the edge data and then merge it. This
               // allows transactions to specify how data merges execute in a
               // precise way.
 
               $u_spec = $merged[$dst];
 
               if (!is_array($u_spec)) {
                 $u_spec = array('dst' => $u_spec);
               }
               if (!is_array($v_spec)) {
                 $v_spec = array('dst' => $v_spec);
               }
 
               $ux_data = idx($u_spec, 'data', array());
               $vx_data = idx($v_spec, 'data', array());
 
               $merged_data = $this->mergeEdgeData(
                 $u->getMetadataValue('edge:type'),
                 $ux_data,
                 $vx_data);
 
               $u_spec['data'] = $merged_data;
               $merged[$dst] = $u_spec;
             }
           }
 
           $result[$key] = $merged;
         }
       } else {
         $result[$key] = array_merge($value, idx($result, $key, array()));
       }
     }
     $u->setNewValue($result);
 
     // When combining an "ignore" transaction with a normal transaction, make
     // sure we don't propagate the "ignore" flag.
     if (!$v->getIgnoreOnNoEffect()) {
       $u->setIgnoreOnNoEffect(false);
     }
 
     return $u;
   }
 
   protected function mergeEdgeData($type, array $u, array $v) {
     return $v + $u;
   }
 
   protected function getPHIDTransactionNewValue(
     PhabricatorApplicationTransaction $xaction,
     $old = null) {
 
     if ($old !== null) {
       $old = array_fuse($old);
     } else {
       $old = array_fuse($xaction->getOldValue());
     }
 
     $new = $xaction->getNewValue();
     $new_add = idx($new, '+', array());
     unset($new['+']);
     $new_rem = idx($new, '-', array());
     unset($new['-']);
     $new_set = idx($new, '=', null);
     if ($new_set !== null) {
       $new_set = array_fuse($new_set);
     }
     unset($new['=']);
 
     if ($new) {
       throw new Exception(
         pht(
           "Invalid '%s' value for PHID transaction. Value should contain only ".
           "keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).",
           'new',
           '+',
           '-',
           '='));
     }
 
     $result = array();
 
     foreach ($old as $phid) {
       if ($new_set !== null && empty($new_set[$phid])) {
         continue;
       }
       $result[$phid] = $phid;
     }
 
     if ($new_set !== null) {
       foreach ($new_set as $phid) {
         $result[$phid] = $phid;
       }
     }
 
     foreach ($new_add as $phid) {
       $result[$phid] = $phid;
     }
 
     foreach ($new_rem as $phid) {
       unset($result[$phid]);
     }
 
     return array_values($result);
   }
 
   protected function getEdgeTransactionNewValue(
     PhabricatorApplicationTransaction $xaction) {
 
     $new = $xaction->getNewValue();
     $new_add = idx($new, '+', array());
     unset($new['+']);
     $new_rem = idx($new, '-', array());
     unset($new['-']);
     $new_set = idx($new, '=', null);
     unset($new['=']);
 
     if ($new) {
       throw new Exception(
         pht(
           "Invalid '%s' value for Edge transaction. Value should contain only ".
           "keys '%s' (add edges), '%s' (remove edges) and '%s' (set edges).",
           'new',
           '+',
           '-',
           '='));
     }
 
     $old = $xaction->getOldValue();
 
     $lists = array($new_set, $new_add, $new_rem);
     foreach ($lists as $list) {
       $this->checkEdgeList($list, $xaction->getMetadataValue('edge:type'));
     }
 
     $result = array();
     foreach ($old as $dst_phid => $edge) {
       if ($new_set !== null && empty($new_set[$dst_phid])) {
         continue;
       }
       $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
         $xaction,
         $edge,
         $dst_phid);
     }
 
     if ($new_set !== null) {
       foreach ($new_set as $dst_phid => $edge) {
         $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
           $xaction,
           $edge,
           $dst_phid);
       }
     }
 
     foreach ($new_add as $dst_phid => $edge) {
       $result[$dst_phid] = $this->normalizeEdgeTransactionValue(
         $xaction,
         $edge,
         $dst_phid);
     }
 
     foreach ($new_rem as $dst_phid => $edge) {
       unset($result[$dst_phid]);
     }
 
     return $result;
   }
 
   private function checkEdgeList($list, $edge_type) {
     if (!$list) {
       return;
     }
     foreach ($list as $key => $item) {
       if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
         throw new Exception(
           pht(
             'Edge transactions must have destination PHIDs as in edge '.
             'lists (found key "%s" on transaction of type "%s").',
             $key,
             $edge_type));
       }
       if (!is_array($item) && $item !== $key) {
         throw new Exception(
           pht(
             'Edge transactions must have PHIDs or edge specs as values '.
             '(found value "%s" on transaction of type "%s").',
             $item,
             $edge_type));
       }
     }
   }
 
   private function normalizeEdgeTransactionValue(
     PhabricatorApplicationTransaction $xaction,
     $edge,
     $dst_phid) {
 
     if (!is_array($edge)) {
       if ($edge != $dst_phid) {
         throw new Exception(
           pht(
             'Transaction edge data must either be the edge PHID or an edge '.
             'specification dictionary.'));
       }
       $edge = array();
     } else {
       foreach ($edge as $key => $value) {
         switch ($key) {
           case 'src':
           case 'dst':
           case 'type':
           case 'data':
           case 'dateCreated':
           case 'dateModified':
           case 'seq':
           case 'dataID':
             break;
           default:
             throw new Exception(
               pht(
                 'Transaction edge specification contains unexpected key "%s".',
                 $key));
         }
       }
     }
 
     $edge['dst'] = $dst_phid;
 
     $edge_type = $xaction->getMetadataValue('edge:type');
     if (empty($edge['type'])) {
       $edge['type'] = $edge_type;
     } else {
       if ($edge['type'] != $edge_type) {
         $this_type = $edge['type'];
         throw new Exception(
           pht(
             "Edge transaction includes edge of type '%s', but ".
             "transaction is of type '%s'. Each edge transaction ".
             "must alter edges of only one type.",
             $this_type,
             $edge_type));
       }
     }
 
     if (!isset($edge['data'])) {
       $edge['data'] = array();
     }
 
     return $edge;
   }
 
   protected function sortTransactions(array $xactions) {
     $head = array();
     $tail = array();
 
     // Move bare comments to the end, so the actions precede them.
     foreach ($xactions as $xaction) {
       $type = $xaction->getTransactionType();
       if ($type == PhabricatorTransactions::TYPE_COMMENT) {
         $tail[] = $xaction;
       } else {
         $head[] = $xaction;
       }
     }
 
     return array_values(array_merge($head, $tail));
   }
 
 
   protected function filterTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $type_comment = PhabricatorTransactions::TYPE_COMMENT;
 
     $no_effect = array();
     $has_comment = false;
     $any_effect = false;
     foreach ($xactions as $key => $xaction) {
       if ($this->transactionHasEffect($object, $xaction)) {
         if ($xaction->getTransactionType() != $type_comment) {
           $any_effect = true;
         }
       } else if ($xaction->getIgnoreOnNoEffect()) {
         unset($xactions[$key]);
       } else {
         $no_effect[$key] = $xaction;
       }
       if ($xaction->hasComment()) {
         $has_comment = true;
       }
     }
 
     if (!$no_effect) {
       return $xactions;
     }
 
     if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) {
       throw new PhabricatorApplicationTransactionNoEffectException(
         $no_effect,
         $any_effect,
         $has_comment);
     }
 
     if (!$any_effect && !$has_comment) {
       // If we only have empty comment transactions, just drop them all.
       return array();
     }
 
     foreach ($no_effect as $key => $xaction) {
       if ($xaction->hasComment()) {
         $xaction->setTransactionType($type_comment);
         $xaction->setOldValue(null);
         $xaction->setNewValue(null);
       } else {
         unset($xactions[$key]);
       }
     }
 
     return $xactions;
   }
 
 
   /**
    * Hook for validating transactions. This callback will be invoked for each
    * available transaction type, even if an edit does not apply any transactions
    * of that type. This allows you to raise exceptions when required fields are
    * missing, by detecting that the object has no field value and there is no
    * transaction which sets one.
    *
    * @param PhabricatorLiskDAO Object being edited.
    * @param string Transaction type to validate.
    * @param list<PhabricatorApplicationTransaction> Transactions of given type,
    *   which may be empty if the edit does not apply any transactions of the
    *   given type.
    * @return list<PhabricatorApplicationTransactionValidationError> List of
    *   validation errors.
    */
   protected function validateTransaction(
     PhabricatorLiskDAO $object,
     $type,
     array $xactions) {
 
     $errors = array();
     switch ($type) {
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         $errors[] = $this->validatePolicyTransaction(
           $object,
           $xactions,
           $type,
           PhabricatorPolicyCapability::CAN_VIEW);
         break;
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         $errors[] = $this->validatePolicyTransaction(
           $object,
           $xactions,
           $type,
           PhabricatorPolicyCapability::CAN_EDIT);
         break;
       case PhabricatorTransactions::TYPE_SPACE:
         $errors[] = $this->validateSpaceTransactions(
           $object,
           $xactions,
           $type);
         break;
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $groups = array();
         foreach ($xactions as $xaction) {
           $groups[$xaction->getMetadataValue('customfield:key')][] = $xaction;
         }
 
         $field_list = PhabricatorCustomField::getObjectFields(
           $object,
           PhabricatorCustomField::ROLE_EDIT);
         $field_list->setViewer($this->getActor());
 
         $role_xactions = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
         foreach ($field_list->getFields() as $field) {
           if (!$field->shouldEnableForRole($role_xactions)) {
             continue;
           }
           $errors[] = $field->validateApplicationTransactions(
             $this,
             $type,
             idx($groups, $field->getFieldKey(), array()));
         }
         break;
     }
 
     return array_mergev($errors);
   }
 
   private function validatePolicyTransaction(
     PhabricatorLiskDAO $object,
     array $xactions,
     $transaction_type,
     $capability) {
 
     $actor = $this->requireActor();
     $errors = array();
     // Note $this->xactions is necessary; $xactions is $this->xactions of
     // $transaction_type
     $policy_object = $this->adjustObjectForPolicyChecks(
       $object,
       $this->xactions);
 
     // Make sure the user isn't editing away their ability to $capability this
     // object.
     foreach ($xactions as $xaction) {
       try {
         PhabricatorPolicyFilter::requireCapabilityWithForcedPolicy(
           $actor,
           $policy_object,
           $capability,
           $xaction->getNewValue());
       } catch (PhabricatorPolicyException $ex) {
         $errors[] = new PhabricatorApplicationTransactionValidationError(
           $transaction_type,
           pht('Invalid'),
           pht(
             'You can not select this %s policy, because you would no longer '.
             'be able to %s the object.',
             $capability,
             $capability),
           $xaction);
       }
     }
 
     if ($this->getIsNewObject()) {
       if (!$xactions) {
         $has_capability = PhabricatorPolicyFilter::hasCapability(
           $actor,
           $policy_object,
           $capability);
         if (!$has_capability) {
           $errors[] = new PhabricatorApplicationTransactionValidationError(
             $transaction_type,
             pht('Invalid'),
             pht(
               'The selected %s policy excludes you. Choose a %s policy '.
               'which allows you to %s the object.',
               $capability,
               $capability,
               $capability));
         }
       }
     }
 
     return $errors;
   }
 
 
   private function validateSpaceTransactions(
     PhabricatorLiskDAO $object,
     array $xactions,
     $transaction_type) {
     $errors = array();
 
     $actor = $this->getActor();
 
     $has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
     $actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
     $active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces(
       $actor);
     foreach ($xactions as $xaction) {
       $space_phid = $xaction->getNewValue();
 
       if ($space_phid === null) {
         if (!$has_spaces) {
           // The install doesn't have any spaces, so this is fine.
           continue;
         }
 
         // The install has some spaces, so every object needs to be put
         // in a valid space.
         $errors[] = new PhabricatorApplicationTransactionValidationError(
           $transaction_type,
           pht('Invalid'),
           pht('You must choose a space for this object.'),
           $xaction);
         continue;
       }
 
       // If the PHID isn't `null`, it needs to be a valid space that the
       // viewer can see.
       if (empty($actor_spaces[$space_phid])) {
         $errors[] = new PhabricatorApplicationTransactionValidationError(
           $transaction_type,
           pht('Invalid'),
           pht(
             'You can not shift this object in the selected space, because '.
             'the space does not exist or you do not have access to it.'),
           $xaction);
       } else if (empty($active_spaces[$space_phid])) {
 
         // It's OK to edit objects in an archived space, so just move on if
         // we aren't adjusting the value.
         $old_space_phid = $this->getTransactionOldValue($object, $xaction);
         if ($space_phid == $old_space_phid) {
           continue;
         }
 
         $errors[] = new PhabricatorApplicationTransactionValidationError(
           $transaction_type,
           pht('Archived'),
           pht(
             'You can not shift this object into the selected space, because '.
             'the space is archived. Objects can not be created inside (or '.
             'moved into) archived spaces.'),
           $xaction);
       }
     }
 
     return $errors;
   }
 
 
   protected function adjustObjectForPolicyChecks(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $copy = clone $object;
 
     foreach ($xactions as $xaction) {
       switch ($xaction->getTransactionType()) {
         case PhabricatorTransactions::TYPE_SUBSCRIBERS:
           $clone_xaction = clone $xaction;
           $clone_xaction->setOldValue(array_values($this->subscribers));
           $clone_xaction->setNewValue(
             $this->getPHIDTransactionNewValue(
               $clone_xaction));
 
           PhabricatorPolicyRule::passTransactionHintToRule(
             $copy,
             new PhabricatorSubscriptionsSubscribersPolicyRule(),
             array_fuse($clone_xaction->getNewValue()));
 
           break;
         case PhabricatorTransactions::TYPE_SPACE:
           $space_phid = $this->getTransactionNewValue($object, $xaction);
           $copy->setSpacePHID($space_phid);
           break;
       }
     }
 
     return $copy;
   }
 
   protected function validateAllTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return array();
   }
 
   /**
    * Check for a missing text field.
    *
    * A text field is missing if the object has no value and there are no
    * transactions which set a value, or if the transactions remove the value.
    * This method is intended to make implementing @{method:validateTransaction}
    * more convenient:
    *
    *   $missing = $this->validateIsEmptyTextField(
    *     $object->getName(),
    *     $xactions);
    *
    * This will return `true` if the net effect of the object and transactions
    * is an empty field.
    *
    * @param wild Current field value.
    * @param list<PhabricatorApplicationTransaction> Transactions editing the
    *          field.
    * @return bool True if the field will be an empty text field after edits.
    */
   protected function validateIsEmptyTextField($field_value, array $xactions) {
     if (strlen($field_value) && empty($xactions)) {
       return false;
     }
 
     if ($xactions && strlen(last($xactions)->getNewValue())) {
       return false;
     }
 
     return true;
   }
 
   /**
    * Check that text field input isn't longer than a specified length.
    *
    * A text field input is invalid if the length of the input is longer than a
    * specified length. This length can be determined by the space allotted in
    * the database, or given arbitrarily.
    * This method is intended to make implementing @{method:validateTransaction}
    * more convenient:
    *
    *   $overdrawn = $this->validateIsTextFieldTooLong(
    *     $object->getName(),
    *     $xactions,
    *     $field_length);
    *
    * This will return `true` if the net effect of the object and transactions
    * is a field that is too long.
    *
    * @param wild Current field value.
    * @param list<PhabricatorApplicationTransaction> Transactions editing the
    *          field.
    * @param integer for maximum field length.
    * @return bool True if the field will be too long after edits.
    */
   protected function validateIsTextFieldTooLong(
     $field_value,
     array $xactions,
     $length) {
 
     if ($xactions) {
       $new_value_length = phutil_utf8_strlen(last($xactions)->getNewValue());
       if ($new_value_length <= $length) {
         return false;
       } else {
         return true;
       }
     }
 
     $old_value_length = phutil_utf8_strlen($field_value);
     if ($old_value_length <= $length) {
       return false;
     }
 
     return true;
   }
 
 
 /* -(  Implicit CCs  )------------------------------------------------------- */
 
 
   /**
    * When a user interacts with an object, we might want to add them to CC.
    */
   final public function applyImplicitCC(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     if (!($object instanceof PhabricatorSubscribableInterface)) {
       // If the object isn't subscribable, we can't CC them.
       return $xactions;
     }
 
     $actor_phid = $this->getActingAsPHID();
 
     $type_user = PhabricatorPeopleUserPHIDType::TYPECONST;
     if (phid_get_type($actor_phid) != $type_user) {
       // Transactions by application actors like Herald, Harbormaster and
       // Diffusion should not CC the applications.
       return $xactions;
     }
 
     if ($object->isAutomaticallySubscribed($actor_phid)) {
       // If they're auto-subscribed, don't CC them.
       return $xactions;
     }
 
     $should_cc = false;
     foreach ($xactions as $xaction) {
       if ($this->shouldImplyCC($object, $xaction)) {
         $should_cc = true;
         break;
       }
     }
 
     if (!$should_cc) {
       // Only some types of actions imply a CC (like adding a comment).
       return $xactions;
     }
 
     if ($object->getPHID()) {
       if (isset($this->subscribers[$actor_phid])) {
         // If the user is already subscribed, don't implicitly CC them.
         return $xactions;
       }
 
       $unsub = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $object->getPHID(),
         PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST);
       $unsub = array_fuse($unsub);
       if (isset($unsub[$actor_phid])) {
         // If the user has previously unsubscribed from this object explicitly,
         // don't implicitly CC them.
         return $xactions;
       }
     }
 
     $xaction = newv(get_class(head($xactions)), array());
     $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
     $xaction->setNewValue(array('+' => array($actor_phid)));
 
     array_unshift($xactions, $xaction);
 
     return $xactions;
   }
 
   protected function shouldImplyCC(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     return $xaction->isCommentTransaction();
   }
 
 
 /* -(  Sending Mail  )------------------------------------------------------- */
 
 
   /**
    * @task mail
    */
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return false;
   }
 
 
   /**
    * @task mail
    */
   private function buildMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $email_to = $this->mailToPHIDs;
     $email_cc = $this->mailCCPHIDs;
     $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs);
 
     $targets = $this->buildReplyHandler($object)
       ->getMailTargets($email_to, $email_cc);
 
     // Set this explicitly before we start swapping out the effective actor.
     $this->setActingAsPHID($this->getActingAsPHID());
 
     $messages = array();
     foreach ($targets as $target) {
       $original_actor = $this->getActor();
 
       $viewer = $target->getViewer();
       $this->setActor($viewer);
       $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation());
 
       $caught = null;
       $mail = null;
       try {
         // Reload handles for the new viewer.
         $this->loadHandles($xactions);
 
         $mail = $this->buildMailForTarget($object, $xactions, $target);
       } catch (Exception $ex) {
         $caught = $ex;
       }
 
       $this->setActor($original_actor);
       unset($locale);
 
       if ($caught) {
         throw $ex;
       }
 
       if ($mail) {
         $messages[] = $mail;
       }
     }
 
     $this->runHeraldMailRules($messages);
 
     return $messages;
   }
 
   private function buildMailForTarget(
     PhabricatorLiskDAO $object,
     array $xactions,
     PhabricatorMailTarget $target) {
 
     // Check if any of the transactions are visible for this viewer. If we
     // don't have any visible transactions, don't send the mail.
 
     $any_visible = false;
     foreach ($xactions as $xaction) {
       if (!$xaction->shouldHideForMail($xactions)) {
         $any_visible = true;
         break;
       }
     }
 
     if (!$any_visible) {
       return null;
     }
 
     $mail = $this->buildMailTemplate($object);
     $body = $this->buildMailBody($object, $xactions);
 
     $mail_tags = $this->getMailTags($object, $xactions);
     $action = $this->getMailAction($object, $xactions);
 
     if (PhabricatorEnv::getEnvConfig('metamta.email-preferences')) {
       $this->addEmailPreferenceSectionToMailBody(
         $body,
         $object,
         $xactions);
     }
 
     $mail
       ->setSensitiveContent(false)
       ->setFrom($this->getActingAsPHID())
       ->setSubjectPrefix($this->getMailSubjectPrefix())
       ->setVarySubjectPrefix('['.$action.']')
       ->setThreadID($this->getMailThreadID($object), $this->getIsNewObject())
       ->setRelatedPHID($object->getPHID())
       ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs())
       ->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs)
       ->setMailTags($mail_tags)
       ->setIsBulk(true)
       ->setBody($body->render())
       ->setHTMLBody($body->renderHTML());
 
     foreach ($body->getAttachments() as $attachment) {
       $mail->addAttachment($attachment);
     }
 
     if ($this->heraldHeader) {
       $mail->addHeader('X-Herald-Rules', $this->heraldHeader);
     }
 
     if ($object instanceof PhabricatorProjectInterface) {
       $this->addMailProjectMetadata($object, $mail);
     }
 
     if ($this->getParentMessageID()) {
       $mail->setParentMessageID($this->getParentMessageID());
     }
 
     return $target->willSendMail($mail);
   }
 
   private function addMailProjectMetadata(
     PhabricatorLiskDAO $object,
     PhabricatorMetaMTAMail $template) {
 
     $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
       $object->getPHID(),
       PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
 
     if (!$project_phids) {
       return;
     }
 
     // TODO: This viewer isn't quite right. It would be slightly better to use
     // the mail recipient, but that's not very easy given the way rendering
     // works today.
 
     $handles = id(new PhabricatorHandleQuery())
       ->setViewer($this->requireActor())
       ->withPHIDs($project_phids)
       ->execute();
 
     $project_tags = array();
     foreach ($handles as $handle) {
       if (!$handle->isComplete()) {
         continue;
       }
       $project_tags[] = '<'.$handle->getObjectName().'>';
     }
 
     if (!$project_tags) {
       return;
     }
 
     $project_tags = implode(', ', $project_tags);
     $template->addHeader('X-Phabricator-Projects', $project_tags);
   }
 
 
   protected function getMailThreadID(PhabricatorLiskDAO $object) {
     return $object->getPHID();
   }
 
 
   /**
    * @task mail
    */
   protected function getStrongestAction(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return last(msort($xactions, 'getActionStrength'));
   }
 
 
   /**
    * @task mail
    */
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     throw new Exception(pht('Capability not supported.'));
   }
 
   /**
    * @task mail
    */
   protected function getMailSubjectPrefix() {
     throw new Exception(pht('Capability not supported.'));
   }
 
 
   /**
    * @task mail
    */
   protected function getMailTags(
     PhabricatorLiskDAO $object,
     array $xactions) {
     $tags = array();
 
     foreach ($xactions as $xaction) {
       $tags[] = $xaction->getMailTags();
     }
 
     return array_mergev($tags);
   }
 
   /**
    * @task mail
    */
   public function getMailTagsMap() {
     // TODO: We should move shared mail tags, like "comment", here.
     return array();
   }
 
 
   /**
    * @task mail
    */
   protected function getMailAction(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return $this->getStrongestAction($object, $xactions)->getActionName();
   }
 
 
   /**
    * @task mail
    */
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     throw new Exception(pht('Capability not supported.'));
   }
 
 
   /**
    * @task mail
    */
   protected function getMailTo(PhabricatorLiskDAO $object) {
     throw new Exception(pht('Capability not supported.'));
   }
 
 
   /**
    * @task mail
    */
   protected function getMailCC(PhabricatorLiskDAO $object) {
     $phids = array();
     $has_support = false;
 
     if ($object instanceof PhabricatorSubscribableInterface) {
       $phid = $object->getPHID();
       $phids[] = PhabricatorSubscribersQuery::loadSubscribersForPHID($phid);
       $has_support = true;
     }
 
     if ($object instanceof PhabricatorProjectInterface) {
       $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $object->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
 
       if ($project_phids) {
         $projects = id(new PhabricatorProjectQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withPHIDs($project_phids)
           ->needWatchers(true)
           ->execute();
 
         $watcher_phids = array();
         foreach ($projects as $project) {
           foreach ($project->getAllAncestorWatcherPHIDs() as $phid) {
             $watcher_phids[$phid] = $phid;
           }
         }
 
         if ($watcher_phids) {
           // We need to do a visibility check for all the watchers, as
           // watching a project is not a guarantee that you can see objects
           // associated with it.
           $users = id(new PhabricatorPeopleQuery())
             ->setViewer($this->requireActor())
             ->withPHIDs($watcher_phids)
             ->execute();
 
           $watchers = array();
           foreach ($users as $user) {
             $can_see = PhabricatorPolicyFilter::hasCapability(
               $user,
               $object,
               PhabricatorPolicyCapability::CAN_VIEW);
             if ($can_see) {
               $watchers[] = $user->getPHID();
             }
           }
           $phids[] = $watchers;
         }
       }
 
       $has_support = true;
     }
 
     if (!$has_support) {
       throw new Exception(pht('Capability not supported.'));
     }
 
     return array_mergev($phids);
   }
 
 
   /**
    * @task mail
    */
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $body = new PhabricatorMetaMTAMailBody();
     $body->setViewer($this->requireActor());
 
     $this->addHeadersAndCommentsToMailBody($body, $xactions);
     $this->addCustomFieldsToMailBody($body, $object, $xactions);
     return $body;
   }
 
 
   /**
    * @task mail
    */
   protected function addEmailPreferenceSectionToMailBody(
     PhabricatorMetaMTAMailBody $body,
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $href = PhabricatorEnv::getProductionURI(
       '/settings/panel/emailpreferences/');
     $body->addLinkSection(pht('EMAIL PREFERENCES'), $href);
   }
 
 
   /**
    * @task mail
    */
   protected function addHeadersAndCommentsToMailBody(
     PhabricatorMetaMTAMailBody $body,
     array $xactions) {
 
     $headers = array();
     $comments = array();
 
     foreach ($xactions as $xaction) {
       if ($xaction->shouldHideForMail($xactions)) {
         continue;
       }
 
       $header = $xaction->getTitleForMail();
       if ($header !== null) {
         $headers[] = $header;
       }
 
       $comment = $xaction->getBodyForMail();
       if ($comment !== null) {
         $comments[] = $comment;
       }
     }
     $body->addRawSection(implode("\n", $headers));
 
     foreach ($comments as $comment) {
       $body->addRemarkupSection(null, $comment);
     }
   }
 
   /**
    * @task mail
    */
   protected function addCustomFieldsToMailBody(
     PhabricatorMetaMTAMailBody $body,
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     if ($object instanceof PhabricatorCustomFieldInterface) {
       $field_list = PhabricatorCustomField::getObjectFields(
         $object,
         PhabricatorCustomField::ROLE_TRANSACTIONMAIL);
       $field_list->setViewer($this->getActor());
       $field_list->readFieldsFromStorage($object);
 
       foreach ($field_list->getFields() as $field) {
         $field->updateTransactionMailBody(
           $body,
           $this,
           $xactions);
       }
     }
   }
 
 
   /**
    * @task mail
    */
   private function runHeraldMailRules(array $messages) {
     foreach ($messages as $message) {
       $engine = new HeraldEngine();
       $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())
         ->setObject($message);
 
       $rules = $engine->loadRulesForAdapter($adapter);
       $effects = $engine->applyRules($rules, $adapter);
       $engine->applyEffects($effects, $adapter, $rules);
     }
   }
 
 
 /* -(  Publishing Feed Stories  )-------------------------------------------- */
 
 
   /**
    * @task feed
    */
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return false;
   }
 
 
   /**
    * @task feed
    */
   protected function getFeedStoryType() {
     return 'PhabricatorApplicationTransactionFeedStory';
   }
 
 
   /**
    * @task feed
    */
   protected function getFeedRelatedPHIDs(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $phids = array(
       $object->getPHID(),
       $this->getActingAsPHID(),
     );
 
     if ($object instanceof PhabricatorProjectInterface) {
       $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $object->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       foreach ($project_phids as $project_phid) {
         $phids[] = $project_phid;
       }
     }
 
     return $phids;
   }
 
 
   /**
    * @task feed
    */
   protected function getFeedNotifyPHIDs(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     return array_unique(array_merge(
       $this->getMailTo($object),
       $this->getMailCC($object)));
   }
 
 
   /**
    * @task feed
    */
   protected function getFeedStoryData(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $xactions = msort($xactions, 'getActionStrength');
     $xactions = array_reverse($xactions);
 
     return array(
       'objectPHID'        => $object->getPHID(),
       'transactionPHIDs'  => mpull($xactions, 'getPHID'),
     );
   }
 
 
   /**
    * @task feed
    */
   protected function publishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions,
     array $mailed_phids) {
 
     $xactions = mfilter($xactions, 'shouldHideForFeed', true);
 
     if (!$xactions) {
       return;
     }
 
     $related_phids = $this->feedRelatedPHIDs;
     $subscribed_phids = $this->feedNotifyPHIDs;
 
     $story_type = $this->getFeedStoryType();
     $story_data = $this->getFeedStoryData($object, $xactions);
 
     id(new PhabricatorFeedStoryPublisher())
       ->setStoryType($story_type)
       ->setStoryData($story_data)
       ->setStoryTime(time())
       ->setStoryAuthorPHID($this->getActingAsPHID())
       ->setRelatedPHIDs($related_phids)
       ->setPrimaryObjectPHID($object->getPHID())
       ->setSubscribedPHIDs($subscribed_phids)
       ->setMailRecipientPHIDs($mailed_phids)
       ->setMailTags($this->getMailTags($object, $xactions))
       ->publish();
   }
 
 
 /* -(  Search Index  )------------------------------------------------------- */
 
 
   /**
    * @task search
    */
   protected function supportsSearch() {
     return false;
   }
 
 
 /* -(  Herald Integration )-------------------------------------------------- */
 
 
   protected function shouldApplyHeraldRules(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return false;
   }
 
   protected function buildHeraldAdapter(
     PhabricatorLiskDAO $object,
     array $xactions) {
     throw new Exception(pht('No herald adapter specified.'));
   }
 
   private function setHeraldAdapter(HeraldAdapter $adapter) {
     $this->heraldAdapter = $adapter;
     return $this;
   }
 
   protected function getHeraldAdapter() {
     return $this->heraldAdapter;
   }
 
   private function setHeraldTranscript(HeraldTranscript $transcript) {
     $this->heraldTranscript = $transcript;
     return $this;
   }
 
   protected function getHeraldTranscript() {
     return $this->heraldTranscript;
   }
 
   private function applyHeraldRules(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $adapter = $this->buildHeraldAdapter($object, $xactions)
       ->setContentSource($this->getContentSource())
       ->setIsNewObject($this->getIsNewObject())
       ->setAppliedTransactions($xactions);
 
     if ($this->getApplicationEmail()) {
       $adapter->setApplicationEmail($this->getApplicationEmail());
     }
 
     $xscript = HeraldEngine::loadAndApplyRules($adapter);
 
     $this->setHeraldAdapter($adapter);
     $this->setHeraldTranscript($xscript);
 
     if ($adapter instanceof HarbormasterBuildableAdapterInterface) {
       HarbormasterBuildable::applyBuildPlans(
         $adapter->getHarbormasterBuildablePHID(),
         $adapter->getHarbormasterContainerPHID(),
         $adapter->getQueuedHarbormasterBuildRequests());
     }
 
     return array_merge(
       $this->didApplyHeraldRules($object, $adapter, $xscript),
       $adapter->getQueuedTransactions());
   }
 
   protected function didApplyHeraldRules(
     PhabricatorLiskDAO $object,
     HeraldAdapter $adapter,
     HeraldTranscript $transcript) {
     return array();
   }
 
 
 /* -(  Custom Fields  )------------------------------------------------------ */
 
 
   /**
    * @task customfield
    */
   private function getCustomFieldForTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     $field_key = $xaction->getMetadataValue('customfield:key');
     if (!$field_key) {
       throw new Exception(
         pht(
         "Custom field transaction has no '%s'!",
         'customfield:key'));
     }
 
     $field = PhabricatorCustomField::getObjectField(
       $object,
       PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
       $field_key);
 
     if (!$field) {
       throw new Exception(
         pht(
           "Custom field transaction has invalid '%s'; field '%s' ".
           "is disabled or does not exist.",
           'customfield:key',
           $field_key));
     }
 
     if (!$field->shouldAppearInApplicationTransactions()) {
       throw new Exception(
         pht(
           "Custom field transaction '%s' does not implement ".
           "integration for %s.",
           $field_key,
           'ApplicationTransactions'));
     }
 
     $field->setViewer($this->getActor());
 
     return $field;
   }
 
 
 /* -(  Files  )-------------------------------------------------------------- */
 
 
   /**
    * Extract the PHIDs of any files which these transactions attach.
    *
    * @task files
    */
   private function extractFilePHIDs(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $blocks = array();
     foreach ($xactions as $xaction) {
       $blocks[] = $this->getRemarkupBlocksFromTransaction($xaction);
     }
     $blocks = array_mergev($blocks);
 
     $phids = array();
     if ($blocks) {
       $phids[] = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
         $this->getActor(),
         $blocks);
     }
 
     foreach ($xactions as $xaction) {
       $phids[] = $this->extractFilePHIDsFromCustomTransaction(
         $object,
         $xaction);
     }
 
     $phids = array_unique(array_filter(array_mergev($phids)));
     if (!$phids) {
       return array();
     }
 
     // Only let a user attach files they can actually see, since this would
     // otherwise let you access any file by attaching it to an object you have
     // view permission on.
 
     $files = id(new PhabricatorFileQuery())
       ->setViewer($this->getActor())
       ->withPHIDs($phids)
       ->execute();
 
     return mpull($files, 'getPHID');
   }
 
   /**
    * @task files
    */
   protected function extractFilePHIDsFromCustomTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
     return array();
   }
 
 
   /**
    * @task files
    */
   private function attachFiles(
     PhabricatorLiskDAO $object,
     array $file_phids) {
 
     if (!$file_phids) {
       return;
     }
 
     $editor = new PhabricatorEdgeEditor();
 
     $src = $object->getPHID();
     $type = PhabricatorObjectHasFileEdgeType::EDGECONST;
     foreach ($file_phids as $dst) {
       $editor->addEdge($src, $type, $dst);
     }
 
     $editor->save();
   }
 
   private function applyInverseEdgeTransactions(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction,
     $inverse_type) {
 
     $old = $xaction->getOldValue();
     $new = $xaction->getNewValue();
 
     $add = array_keys(array_diff_key($new, $old));
     $rem = array_keys(array_diff_key($old, $new));
 
     $add = array_fuse($add);
     $rem = array_fuse($rem);
     $all = $add + $rem;
 
     $nodes = id(new PhabricatorObjectQuery())
       ->setViewer($this->requireActor())
       ->withPHIDs($all)
       ->execute();
 
     foreach ($nodes as $node) {
       if (!($node instanceof PhabricatorApplicationTransactionInterface)) {
         continue;
       }
 
       if ($node instanceof PhabricatorUser) {
         // TODO: At least for now, don't record inverse edge transactions
         // for users (for example, "alincoln joined project X"): Feed fills
         // this role instead.
         continue;
       }
 
       $editor = $node->getApplicationTransactionEditor();
       $template = $node->getApplicationTransactionTemplate();
       $target = $node->getApplicationTransactionObject();
 
       if (isset($add[$node->getPHID()])) {
         $edge_edit_type = '+';
       } else {
         $edge_edit_type = '-';
       }
 
       $template
         ->setTransactionType($xaction->getTransactionType())
         ->setMetadataValue('edge:type', $inverse_type)
         ->setNewValue(
           array(
             $edge_edit_type => array($object->getPHID() => $object->getPHID()),
           ));
 
       $editor
         ->setContinueOnNoEffect(true)
         ->setContinueOnMissingFields(true)
         ->setParentMessageID($this->getParentMessageID())
         ->setIsInverseEdgeEditor(true)
         ->setActor($this->requireActor())
         ->setActingAsPHID($this->getActingAsPHID())
         ->setContentSource($this->getContentSource());
 
       $editor->applyTransactions($target, array($template));
     }
   }
 
 
 /* -(  Workers  )------------------------------------------------------------ */
 
 
   /**
    * Load any object state which is required to publish transactions.
    *
    * This hook is invoked in the main process before we compute data related
    * to publishing transactions (like email "To" and "CC" lists), and again in
    * the worker before publishing occurs.
    *
    * @return object Publishable object.
    * @task workers
    */
   protected function willPublish(PhabricatorLiskDAO $object, array $xactions) {
     return $object;
   }
 
 
   /**
    * Convert the editor state to a serializable dictionary which can be passed
    * to a worker.
    *
    * This data will be loaded with @{method:loadWorkerState} in the worker.
    *
    * @return dict<string, wild> Serializable editor state.
    * @task workers
    */
   final private function getWorkerState() {
     $state = array();
     foreach ($this->getAutomaticStateProperties() as $property) {
       $state[$property] = $this->$property;
     }
 
     $custom_state = $this->getCustomWorkerState();
     $custom_encoding = $this->getCustomWorkerStateEncoding();
 
     $state += array(
       'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(),
       'custom' => $this->encodeStateForStorage($custom_state, $custom_encoding),
       'custom.encoding' => $custom_encoding,
     );
 
     return $state;
   }
 
 
   /**
    * Hook; return custom properties which need to be passed to workers.
    *
    * @return dict<string, wild> Custom properties.
    * @task workers
    */
   protected function getCustomWorkerState() {
     return array();
   }
 
 
   /**
    * Hook; return storage encoding for custom properties which need to be
    * passed to workers.
    *
    * This primarily allows binary data to be passed to workers and survive
    * JSON encoding.
    *
    * @return dict<string, string> Property encodings.
    * @task workers
    */
   protected function getCustomWorkerStateEncoding() {
     return array();
   }
 
 
   /**
    * Load editor state using a dictionary emitted by @{method:getWorkerState}.
    *
    * This method is used to load state when running worker operations.
    *
    * @param dict<string, wild> Editor state, from @{method:getWorkerState}.
    * @return this
    * @task workers
    */
   final public function loadWorkerState(array $state) {
     foreach ($this->getAutomaticStateProperties() as $property) {
       $this->$property = idx($state, $property);
     }
 
     $exclude = idx($state, 'excludeMailRecipientPHIDs', array());
     $this->setExcludeMailRecipientPHIDs($exclude);
 
     $custom_state = idx($state, 'custom', array());
     $custom_encodings = idx($state, 'custom.encoding', array());
     $custom = $this->decodeStateFromStorage($custom_state, $custom_encodings);
 
     $this->loadCustomWorkerState($custom);
 
     return $this;
   }
 
 
   /**
    * Hook; set custom properties on the editor from data emitted by
    * @{method:getCustomWorkerState}.
    *
    * @param dict<string, wild> Custom state,
    *   from @{method:getCustomWorkerState}.
    * @return this
    * @task workers
    */
   protected function loadCustomWorkerState(array $state) {
     return $this;
   }
 
 
   /**
    * Get a list of object properties which should be automatically sent to
    * workers in the state data.
    *
    * These properties will be automatically stored and loaded by the editor in
    * the worker.
    *
    * @return list<string> List of properties.
    * @task workers
    */
   private function getAutomaticStateProperties() {
     return array(
       'parentMessageID',
       'disableEmail',
       'isNewObject',
       'heraldEmailPHIDs',
       'heraldForcedEmailPHIDs',
       'heraldHeader',
       'mailToPHIDs',
       'mailCCPHIDs',
       'feedNotifyPHIDs',
       'feedRelatedPHIDs',
     );
   }
 
   /**
    * Apply encodings prior to storage.
    *
    * See @{method:getCustomWorkerStateEncoding}.
    *
    * @param map<string, wild> Map of values to encode.
    * @param map<string, string> Map of encodings to apply.
    * @return map<string, wild> Map of encoded values.
    * @task workers
    */
   final private function encodeStateForStorage(
     array $state,
     array $encodings) {
 
     foreach ($state as $key => $value) {
       $encoding = idx($encodings, $key);
       switch ($encoding) {
         case self::STORAGE_ENCODING_BINARY:
           // The mechanics of this encoding (serialize + base64) are a little
           // awkward, but it allows us encode arrays and still be JSON-safe
           // with binary data.
 
           $value = @serialize($value);
           if ($value === false) {
             throw new Exception(
               pht(
                 'Failed to serialize() value for key "%s".',
                 $key));
           }
 
           $value = base64_encode($value);
           if ($value === false) {
             throw new Exception(
               pht(
                 'Failed to base64 encode value for key "%s".',
                 $key));
           }
           break;
       }
       $state[$key] = $value;
     }
 
     return $state;
   }
 
 
   /**
    * Undo storage encoding applied when storing state.
    *
    * See @{method:getCustomWorkerStateEncoding}.
    *
    * @param map<string, wild> Map of encoded values.
    * @param map<string, string> Map of encodings.
    * @return map<string, wild> Map of decoded values.
    * @task workers
    */
   final private function decodeStateFromStorage(
     array $state,
     array $encodings) {
 
     foreach ($state as $key => $value) {
       $encoding = idx($encodings, $key);
       switch ($encoding) {
         case self::STORAGE_ENCODING_BINARY:
           $value = base64_decode($value);
           if ($value === false) {
             throw new Exception(
               pht(
                 'Failed to base64_decode() value for key "%s".',
                 $key));
           }
 
           $value = unserialize($value);
           break;
       }
       $state[$key] = $value;
     }
 
     return $state;
   }
 
 
   /**
    * Remove conflicts from a list of projects.
    *
    * Objects aren't allowed to be tagged with multiple milestones in the same
    * group, nor projects such that one tag is the ancestor of any other tag.
    * If the list of PHIDs include mutually exclusive projects, remove the
    * conflicting projects.
    *
    * @param list<phid> List of project PHIDs.
    * @return list<phid> List with conflicts removed.
    */
   private function applyProjectConflictRules(array $phids) {
     if (!$phids) {
       return array();
     }
 
     // Overall, the last project in the list wins in cases of conflict (so when
     // you add something, the thing you just added sticks and removes older
     // values).
 
     // Beyond that, there are two basic cases:
 
     // Milestones: An object can't be in "A > Sprint 3" and "A > Sprint 4".
     // If multiple projects are milestones of the same parent, we only keep the
     // last one.
 
     // Ancestor: You can't be in "A" and "A > B". If "A > B" comes later
     // in the list, we remove "A" and keep "A > B". If "A" comes later, we
     // remove "A > B" and keep "A".
 
     // Note that it's OK to be in "A > B" and "A > C". There's only a conflict
     // if one project is an ancestor of another. It's OK to have something
     // tagged with multiple projects which share a common ancestor, so long as
     // they are not mutual ancestors.
 
     $viewer = PhabricatorUser::getOmnipotentUser();
 
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->withPHIDs(array_keys($phids))
       ->execute();
     $projects = mpull($projects, null, 'getPHID');
 
     // We're going to build a map from each project with milestones to the last
     // milestone in the list. This last milestone is the milestone we'll keep.
     $milestone_map = array();
 
     // We're going to build a set of the projects which have no descendants
     // later in the list. This allows us to apply both ancestor rules.
     $ancestor_map = array();
 
     foreach ($phids as $phid => $ignored) {
       $project = idx($projects, $phid);
       if (!$project) {
         continue;
       }
 
       // This is the last milestone we've seen, so set it as the selection for
       // the project's parent. This might be setting a new value or overwriting
       // an earlier value.
       if ($project->isMilestone()) {
         $parent_phid = $project->getParentProjectPHID();
         $milestone_map[$parent_phid] = $phid;
       }
 
       // Since this is the last item in the list we've examined so far, add it
       // to the set of projects with no later descendants.
       $ancestor_map[$phid] = $phid;
 
       // Remove any ancestors from the set, since this is a later descendant.
       foreach ($project->getAncestorProjects() as $ancestor) {
         $ancestor_phid = $ancestor->getPHID();
         unset($ancestor_map[$ancestor_phid]);
       }
     }
 
     // Now that we've built the maps, we can throw away all the projects which
     // have conflicts.
     foreach ($phids as $phid => $ignored) {
       $project = idx($projects, $phid);
 
       if (!$project) {
         // If a PHID is invalid, we just leave it as-is. We could clean it up,
         // but leaving it untouched is less likely to cause collateral damage.
         continue;
       }
 
       // If this was a milestone, check if it was the last milestone from its
       // group in the list. If not, remove it from the list.
       if ($project->isMilestone()) {
         $parent_phid = $project->getParentProjectPHID();
         if ($milestone_map[$parent_phid] !== $phid) {
           unset($phids[$phid]);
           continue;
         }
       }
 
       // If a later project in the list is a subproject of this one, it will
       // have removed ancestors from the map. If this project does not point
       // at itself in the ancestor map, it should be discarded in favor of a
       // subproject that comes later.
       if (idx($ancestor_map, $phid) !== $phid) {
         unset($phids[$phid]);
         continue;
       }
 
       // If a later project in the list is an ancestor of this one, it will
       // have added itself to the map. If any ancestor of this project points
       // at itself in the map, this project should be dicarded in favor of
       // that later ancestor.
       foreach ($project->getAncestorProjects() as $ancestor) {
         $ancestor_phid = $ancestor->getPHID();
         if (isset($ancestor_map[$ancestor_phid])) {
           unset($phids[$phid]);
           continue 2;
         }
       }
     }
 
     return $phids;
   }
 
+  /**
+   * When the view policy for an object is changed, scramble the secret keys
+   * for attached files to invalidate existing URIs.
+   */
+  private function scrambleFileSecrets($object) {
+    // If this is a newly created object, we don't need to scramble anything
+    // since it couldn't have been previously published.
+    if ($this->getIsNewObject()) {
+      return;
+    }
+
+    // If the object is a file itself, scramble it.
+    if ($object instanceof PhabricatorFile) {
+      if ($this->shouldScramblePolicy($object->getViewPolicy())) {
+        $object->scrambleSecret();
+        $object->save();
+      }
+    }
+
+    $phid = $object->getPHID();
+
+    $attached_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+      $phid,
+      PhabricatorObjectHasFileEdgeType::EDGECONST);
+    if (!$attached_phids) {
+      return;
+    }
+
+    $omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
+
+    $files = id(new PhabricatorFileQuery())
+      ->setViewer($omnipotent_viewer)
+      ->withPHIDs($attached_phids)
+      ->execute();
+    foreach ($files as $file) {
+      $view_policy = $file->getViewPolicy();
+      if ($this->shouldScramblePolicy($view_policy)) {
+        $file->scrambleSecret();
+        $file->save();
+      }
+    }
+  }
+
+
+  /**
+   * Check if a policy is strong enough to justify scrambling. Objects which
+   * are set to very open policies don't need to scramble their files, and
+   * files with very open policies don't need to be scrambled when associated
+   * objects change.
+   */
+  private function shouldScramblePolicy($policy) {
+    switch ($policy) {
+      case PhabricatorPolicies::POLICY_PUBLIC:
+      case PhabricatorPolicies::POLICY_USER:
+        return false;
+    }
+
+    return true;
+  }
+
 }
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index ee8a7a47a..e3fe7707f 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1,1427 +1,1537 @@
 <?php
 
 abstract class PhabricatorApplicationTransaction
   extends PhabricatorLiskDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorDestructibleInterface {
 
   const TARGET_TEXT = 'text';
   const TARGET_HTML = 'html';
 
   protected $phid;
   protected $objectPHID;
   protected $authorPHID;
   protected $viewPolicy;
   protected $editPolicy;
 
   protected $commentPHID;
   protected $commentVersion = 0;
   protected $transactionType;
   protected $oldValue;
   protected $newValue;
   protected $metadata = array();
 
   protected $contentSource;
 
   private $comment;
   private $commentNotLoaded;
 
   private $handles;
   private $renderingTarget = self::TARGET_HTML;
   private $transactionGroup = array();
   private $viewer = self::ATTACHABLE;
   private $object = self::ATTACHABLE;
   private $oldValueHasBeenSet = false;
 
   private $ignoreOnNoEffect;
 
 
   /**
    * Flag this transaction as a pure side-effect which should be ignored when
    * applying transactions if it has no effect, even if transaction application
    * would normally fail. This both provides users with better error messages
    * and allows transactions to perform optional side effects.
    */
   public function setIgnoreOnNoEffect($ignore) {
     $this->ignoreOnNoEffect = $ignore;
     return $this;
   }
 
   public function getIgnoreOnNoEffect() {
     return $this->ignoreOnNoEffect;
   }
 
   public function shouldGenerateOldValue() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_BUILDABLE:
       case PhabricatorTransactions::TYPE_TOKEN:
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
       case PhabricatorTransactions::TYPE_INLINESTATE:
         return false;
     }
     return true;
   }
 
   abstract public function getApplicationTransactionType();
 
   private function getApplicationObjectTypeName() {
     $types = PhabricatorPHIDType::getAllTypes();
 
     $type = idx($types, $this->getApplicationTransactionType());
     if ($type) {
       return $type->getTypeName();
     }
 
     return pht('Object');
   }
 
   public function getApplicationTransactionCommentObject() {
     throw new PhutilMethodNotImplementedException();
   }
 
   public function getApplicationTransactionViewObject() {
     return new PhabricatorApplicationTransactionView();
   }
 
   public function getMetadataValue($key, $default = null) {
     return idx($this->metadata, $key, $default);
   }
 
   public function setMetadataValue($key, $value) {
     $this->metadata[$key] = $value;
     return $this;
   }
 
   public function generatePHID() {
     $type = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST;
     $subtype = $this->getApplicationTransactionType();
 
     return PhabricatorPHID::generateNewPHID($type, $subtype);
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'oldValue' => self::SERIALIZATION_JSON,
         'newValue' => self::SERIALIZATION_JSON,
         'metadata' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'commentPHID' => 'phid?',
         'commentVersion' => 'uint32',
         'contentSource' => 'text',
         'transactionType' => 'text32',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_object' => array(
           'columns' => array('objectPHID'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function setContentSource(PhabricatorContentSource $content_source) {
     $this->contentSource = $content_source->serialize();
     return $this;
   }
 
   public function getContentSource() {
     return PhabricatorContentSource::newFromSerialized($this->contentSource);
   }
 
   public function hasComment() {
     return $this->getComment() && strlen($this->getComment()->getContent());
   }
 
   public function getComment() {
     if ($this->commentNotLoaded) {
       throw new Exception(pht('Comment for this transaction was not loaded.'));
     }
     return $this->comment;
   }
 
   public function setIsCreateTransaction($create) {
     return $this->setMetadataValue('core.create', $create);
   }
 
   public function getIsCreateTransaction() {
     return (bool)$this->getMetadataValue('core.create', false);
   }
 
   public function setIsDefaultTransaction($default) {
     return $this->setMetadataValue('core.default', $default);
   }
 
   public function getIsDefaultTransaction() {
     return (bool)$this->getMetadataValue('core.default', false);
   }
 
   public function attachComment(
     PhabricatorApplicationTransactionComment $comment) {
     $this->comment = $comment;
     $this->commentNotLoaded = false;
     return $this;
   }
 
   public function setCommentNotLoaded($not_loaded) {
     $this->commentNotLoaded = $not_loaded;
     return $this;
   }
 
   public function attachObject($object) {
     $this->object = $object;
     return $this;
   }
 
   public function getObject() {
     return $this->assertAttached($this->object);
   }
 
   public function getRemarkupBlocks() {
     $blocks = array();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           $custom_blocks = $field->getApplicationTransactionRemarkupBlocks(
             $this);
           foreach ($custom_blocks as $custom_block) {
             $blocks[] = $custom_block;
           }
         }
         break;
     }
 
     if ($this->getComment()) {
       $blocks[] = $this->getComment()->getContent();
     }
 
     return $blocks;
   }
 
   public function setOldValue($value) {
     $this->oldValueHasBeenSet = true;
     $this->writeField('oldValue', $value);
     return $this;
   }
 
   public function hasOldValue() {
     return $this->oldValueHasBeenSet;
   }
 
 
 /* -(  Rendering  )---------------------------------------------------------- */
 
   public function setRenderingTarget($rendering_target) {
     $this->renderingTarget = $rendering_target;
     return $this;
   }
 
   public function getRenderingTarget() {
     return $this->renderingTarget;
   }
 
   public function attachViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
   }
 
   public function getViewer() {
     return $this->assertAttached($this->viewer);
   }
 
   public function getRequiredHandlePHIDs() {
     $phids = array();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     $phids[] = array($this->getAuthorPHID());
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           $phids[] = $field->getApplicationTransactionRequiredHandlePHIDs(
             $this);
         }
         break;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         $phids[] = $old;
         $phids[] = $new;
         break;
       case PhabricatorTransactions::TYPE_EDGE:
         $phids[] = ipull($old, 'dst');
         $phids[] = ipull($new, 'dst');
         break;
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        foreach ($new as $move) {
+          $phids[] = array(
+            $move['columnPHID'],
+            $move['boardPHID'],
+          );
+          $phids[] = $move['fromColumnPHIDs'];
+        }
+        break;
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) {
           $phids[] = array($old);
         }
         if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) {
           $phids[] = array($new);
         }
         break;
       case PhabricatorTransactions::TYPE_SPACE:
         if ($old) {
           $phids[] = array($old);
         }
         if ($new) {
           $phids[] = array($new);
         }
         break;
       case PhabricatorTransactions::TYPE_TOKEN:
         break;
       case PhabricatorTransactions::TYPE_BUILDABLE:
         $phid = $this->getMetadataValue('harbormaster:buildablePHID');
         if ($phid) {
           $phids[] = array($phid);
         }
         break;
     }
 
     if ($this->getComment()) {
       $phids[] = array($this->getComment()->getAuthorPHID());
     }
 
     return array_mergev($phids);
   }
 
   public function setHandles(array $handles) {
     $this->handles = $handles;
     return $this;
   }
 
   public function getHandle($phid) {
     if (empty($this->handles[$phid])) {
       throw new Exception(
         pht(
           'Transaction ("%s", of type "%s") requires a handle ("%s") that it '.
           'did not load.',
           $this->getPHID(),
           $this->getTransactionType(),
           $phid));
     }
     return $this->handles[$phid];
   }
 
   public function getHandleIfExists($phid) {
     return idx($this->handles, $phid);
   }
 
   public function getHandles() {
     if ($this->handles === null) {
       throw new Exception(
         pht('Transaction requires handles and it did not load them.'));
     }
     return $this->handles;
   }
 
   public function renderHandleLink($phid) {
     if ($this->renderingTarget == self::TARGET_HTML) {
       return $this->getHandle($phid)->renderLink();
     } else {
       return $this->getHandle($phid)->getLinkName();
     }
   }
 
   public function renderHandleList(array $phids) {
     $links = array();
     foreach ($phids as $phid) {
       $links[] = $this->renderHandleLink($phid);
     }
     if ($this->renderingTarget == self::TARGET_HTML) {
       return phutil_implode_html(', ', $links);
     } else {
       return implode(', ', $links);
     }
   }
 
   private function renderSubscriberList(array $phids, $change_type) {
     if ($this->getRenderingTarget() == self::TARGET_TEXT) {
       return $this->renderHandleList($phids);
     } else {
       $handles = array_select_keys($this->getHandles(), $phids);
       return id(new SubscriptionListStringBuilder())
         ->setHandles($handles)
         ->setObjectPHID($this->getPHID())
         ->buildTransactionString($change_type);
     }
   }
 
   protected function renderPolicyName($phid, $state = 'old') {
     $policy = PhabricatorPolicy::newFromPolicyAndHandle(
       $phid,
       $this->getHandleIfExists($phid));
     if ($this->renderingTarget == self::TARGET_HTML) {
       switch ($policy->getType()) {
         case PhabricatorPolicyType::TYPE_CUSTOM:
           $policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/');
           $policy->setWorkflow(true);
           break;
         default:
           break;
       }
       $output = $policy->renderDescription();
     } else {
       $output = hsprintf('%s', $policy->getFullName());
     }
     return $output;
   }
 
   public function getIcon() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         $comment = $this->getComment();
         if ($comment && $comment->getIsRemoved()) {
           return 'fa-trash';
         }
         return 'fa-comment';
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         $old = $this->getOldValue();
         $new = $this->getNewValue();
         $add = array_diff($new, $old);
         $rem = array_diff($old, $new);
         if ($add && $rem) {
           return 'fa-user';
         } else if ($add) {
           return 'fa-user-plus';
         } else if ($rem) {
           return 'fa-user-times';
         } else {
           return 'fa-user';
         }
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         return 'fa-lock';
       case PhabricatorTransactions::TYPE_EDGE:
         return 'fa-link';
       case PhabricatorTransactions::TYPE_BUILDABLE:
         return 'fa-wrench';
       case PhabricatorTransactions::TYPE_TOKEN:
         return 'fa-trophy';
       case PhabricatorTransactions::TYPE_SPACE:
         return 'fa-th-large';
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        return 'fa-columns';
     }
 
     return 'fa-pencil';
   }
 
   public function getToken() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_TOKEN:
         $old = $this->getOldValue();
         $new = $this->getNewValue();
         if ($new) {
           $icon = substr($new, 10);
         } else {
           $icon = substr($old, 10);
         }
         return array($icon, !$this->getNewValue());
     }
 
     return array(null, null);
   }
 
   public function getColor() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT;
         $comment = $this->getComment();
         if ($comment && $comment->getIsRemoved()) {
           return 'black';
         }
         break;
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_PASSED:
             return 'green';
           case HarbormasterBuildable::STATUS_FAILED:
             return 'red';
         }
         break;
     }
     return null;
   }
 
   protected function getTransactionCustomField() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $key = $this->getMetadataValue('customfield:key');
         if (!$key) {
           return null;
         }
 
         $object = $this->getObject();
 
         if (!($object instanceof PhabricatorCustomFieldInterface)) {
           return null;
         }
 
         $field = PhabricatorCustomField::getObjectField(
           $object,
           PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
           $key);
         if (!$field) {
           return null;
         }
 
         $field->setViewer($this->getViewer());
         return $field;
     }
 
     return null;
   }
 
   public function shouldHide() {
     // Never hide comments.
     if ($this->hasComment()) {
       return false;
     }
 
     // Hide creation transactions if the old value is empty. These are
     // transactions like "alice set the task tile to: ...", which are
     // essentially never interesting.
     if ($this->getIsCreateTransaction()) {
       switch ($this->getTransactionType()) {
         case PhabricatorTransactions::TYPE_CREATE:
         case PhabricatorTransactions::TYPE_VIEW_POLICY:
         case PhabricatorTransactions::TYPE_EDIT_POLICY:
         case PhabricatorTransactions::TYPE_JOIN_POLICY:
         case PhabricatorTransactions::TYPE_SPACE:
           break;
         default:
           $old = $this->getOldValue();
 
           if (is_array($old) && !$old) {
             return true;
           }
 
-          if (!strlen($old)) {
+          if (!is_array($old) && !strlen($old)) {
             return true;
           }
+
           break;
       }
     }
 
     // Hide creation transactions setting values to defaults, even if
     // the old value is not empty. For example, tasks may have a global
     // default view policy of "All Users", but a particular form sets the
     // policy to "Administrators". The transaction corresponding to this
     // change is not interesting, since it is the default behavior of the
     // form.
 
     if ($this->getIsCreateTransaction()) {
       if ($this->getIsDefaultTransaction()) {
         return true;
       }
     }
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
       case PhabricatorTransactions::TYPE_SPACE:
         if ($this->getIsCreateTransaction()) {
           break;
         }
 
         // TODO: Remove this eventually, this is handling old changes during
         // object creation prior to the introduction of "create" and "default"
         // transaction display flags.
 
         // NOTE: We can also hit this case with Space transactions that later
         // update a default space (`null`) to an explicit space, so handling
         // the Space case may require some finesse.
 
         if ($this->getOldValue() === null) {
           return true;
         } else {
           return false;
         }
         break;
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           return $field->shouldHideInApplicationTransactions($this);
         }
+        break;
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        return !$this->getInterestingMoves($this->getNewValue());
       case PhabricatorTransactions::TYPE_EDGE:
         $edge_type = $this->getMetadataValue('edge:type');
         switch ($edge_type) {
           case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
             return true;
             break;
           case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
             $new = ipull($this->getNewValue(), 'dst');
             $old = ipull($this->getOldValue(), 'dst');
             $add = array_diff($new, $old);
             $add_value = reset($add);
             $add_handle = $this->getHandle($add_value);
             if ($add_handle->getPolicyFiltered()) {
               return true;
             }
             return false;
             break;
           default:
             break;
         }
         break;
     }
 
     return false;
   }
 
   public function shouldHideForMail(array $xactions) {
     if ($this->isSelfSubscription()) {
       return true;
     }
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_TOKEN:
         return true;
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_FAILED:
             // For now, only ever send mail when builds fail. We might let
             // you customize this later, but in most cases this is probably
             // completely uninteresting.
             return false;
         }
         return true;
      case PhabricatorTransactions::TYPE_EDGE:
         $edge_type = $this->getMetadataValue('edge:type');
         switch ($edge_type) {
           case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
           case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
             return true;
             break;
           default:
             break;
         }
         break;
     }
 
     if ($this->isInlineCommentTransaction()) {
       $inlines = array();
 
       // If there's a normal comment, we don't need to publish the inline
       // transaction, since the normal comment covers things.
       foreach ($xactions as $xaction) {
         if ($xaction->isInlineCommentTransaction()) {
           $inlines[] = $xaction;
           continue;
         }
 
         // We found a normal comment, so hide this inline transaction.
         if ($xaction->hasComment()) {
           return true;
         }
       }
 
       // If there are several inline comments, only publish the first one.
       if ($this !== head($inlines)) {
         return true;
       }
     }
 
     return $this->shouldHide();
   }
 
   public function shouldHideForFeed() {
     if ($this->isSelfSubscription()) {
       return true;
     }
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_TOKEN:
         return true;
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_FAILED:
             // For now, don't notify on build passes either. These are pretty
             // high volume and annoying, with very little present value. We
             // might want to turn them back on in the specific case of
             // build successes on the current document?
             return false;
         }
         return true;
      case PhabricatorTransactions::TYPE_EDGE:
         $edge_type = $this->getMetadataValue('edge:type');
         switch ($edge_type) {
           case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
           case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
             return true;
             break;
           default:
             break;
         }
         break;
      case PhabricatorTransactions::TYPE_INLINESTATE:
        return true;
     }
 
     return $this->shouldHide();
   }
 
   public function getTitleForMail() {
     return id(clone $this)->setRenderingTarget('text')->getTitle();
   }
 
   public function getBodyForMail() {
     if ($this->isInlineCommentTransaction()) {
       // We don't return inline comment content as mail body content, because
       // applications need to contextualize it (by adding line numbers, for
       // example) in order for it to make sense.
       return null;
     }
 
     $comment = $this->getComment();
     if ($comment && strlen($comment->getContent())) {
       return $comment->getContent();
     }
 
     return null;
   }
 
   public function getNoEffectDescription() {
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         return pht('You can not post an empty comment.');
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         return pht(
           'This %s already has that view policy.',
           $this->getApplicationObjectTypeName());
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         return pht(
           'This %s already has that edit policy.',
           $this->getApplicationObjectTypeName());
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         return pht(
           'This %s already has that join policy.',
           $this->getApplicationObjectTypeName());
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return pht(
           'All users are already subscribed to this %s.',
           $this->getApplicationObjectTypeName());
       case PhabricatorTransactions::TYPE_SPACE:
         return pht('This object is already in that space.');
       case PhabricatorTransactions::TYPE_EDGE:
         return pht('Edges already exist; transaction has no effect.');
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        return pht(
+          'You have not moved this object to any columns it is not '.
+          'already in.');
     }
 
     return pht(
       'Transaction (of type "%s") has no effect.',
       $this->getTransactionType());
   }
 
   public function getTitle() {
     $author_phid = $this->getAuthorPHID();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return pht(
           '%s created this object.',
           $this->renderHandleLink($author_phid));
       case PhabricatorTransactions::TYPE_COMMENT:
         return pht(
           '%s added a comment.',
           $this->renderHandleLink($author_phid));
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         if ($this->getIsCreateTransaction()) {
           return pht(
             '%s created this object with visibility "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($new, 'new'));
         } else {
           return pht(
             '%s changed the visibility from "%s" to "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($old, 'old'),
             $this->renderPolicyName($new, 'new'));
         }
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         if ($this->getIsCreateTransaction()) {
           return pht(
             '%s created this object with edit policy "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($new, 'new'));
         } else {
           return pht(
             '%s changed the edit policy from "%s" to "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($old, 'old'),
             $this->renderPolicyName($new, 'new'));
         }
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         if ($this->getIsCreateTransaction()) {
           return pht(
             '%s created this object with join policy "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($new, 'new'));
         } else {
           return pht(
             '%s changed the join policy from "%s" to "%s".',
             $this->renderHandleLink($author_phid),
             $this->renderPolicyName($old, 'old'),
             $this->renderPolicyName($new, 'new'));
         }
       case PhabricatorTransactions::TYPE_SPACE:
         if ($this->getIsCreateTransaction()) {
           return pht(
             '%s created this object in space %s.',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($new));
         } else {
           return pht(
             '%s shifted this object from the %s space to the %s space.',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($old),
             $this->renderHandleLink($new));
         }
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         $add = array_diff($new, $old);
         $rem = array_diff($old, $new);
 
         if ($add && $rem) {
           return pht(
             '%s edited subscriber(s), added %d: %s; removed %d: %s.',
             $this->renderHandleLink($author_phid),
             count($add),
             $this->renderSubscriberList($add, 'add'),
             count($rem),
             $this->renderSubscriberList($rem, 'rem'));
         } else if ($add) {
           return pht(
             '%s added %d subscriber(s): %s.',
             $this->renderHandleLink($author_phid),
             count($add),
             $this->renderSubscriberList($add, 'add'));
         } else if ($rem) {
           return pht(
             '%s removed %d subscriber(s): %s.',
             $this->renderHandleLink($author_phid),
             count($rem),
             $this->renderSubscriberList($rem, 'rem'));
         } else {
           // This is used when rendering previews, before the user actually
           // selects any CCs.
           return pht(
             '%s updated subscribers...',
             $this->renderHandleLink($author_phid));
         }
         break;
       case PhabricatorTransactions::TYPE_EDGE:
         $new = ipull($new, 'dst');
         $old = ipull($old, 'dst');
         $add = array_diff($new, $old);
         $rem = array_diff($old, $new);
         $type = $this->getMetadata('edge:type');
         $type = head($type);
 
         $type_obj = PhabricatorEdgeType::getByConstant($type);
 
         if ($add && $rem) {
           return $type_obj->getTransactionEditString(
             $this->renderHandleLink($author_phid),
             new PhutilNumber(count($add) + count($rem)),
             phutil_count($add),
             $this->renderHandleList($add),
             phutil_count($rem),
             $this->renderHandleList($rem));
         } else if ($add) {
           return $type_obj->getTransactionAddString(
             $this->renderHandleLink($author_phid),
             phutil_count($add),
             $this->renderHandleList($add));
         } else if ($rem) {
           return $type_obj->getTransactionRemoveString(
             $this->renderHandleLink($author_phid),
             phutil_count($rem),
             $this->renderHandleList($rem));
         } else {
           return $type_obj->getTransactionPreviewString(
             $this->renderHandleLink($author_phid));
         }
 
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           return $field->getApplicationTransactionTitle($this);
         } else {
           return pht(
             '%s edited a custom field.',
             $this->renderHandleLink($author_phid));
         }
 
       case PhabricatorTransactions::TYPE_TOKEN:
         if ($old && $new) {
           return pht(
             '%s updated a token.',
             $this->renderHandleLink($author_phid));
         } else if ($old) {
           return pht(
             '%s rescinded a token.',
             $this->renderHandleLink($author_phid));
         } else {
           return pht(
             '%s awarded a token.',
             $this->renderHandleLink($author_phid));
         }
 
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_BUILDING:
             return pht(
               '%s started building %s.',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')));
           case HarbormasterBuildable::STATUS_PASSED:
             return pht(
               '%s completed building %s.',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')));
           case HarbormasterBuildable::STATUS_FAILED:
             return pht(
               '%s failed to build %s!',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')));
           default:
             return null;
         }
 
       case PhabricatorTransactions::TYPE_INLINESTATE:
         $done = 0;
         $undone = 0;
         foreach ($new as $phid => $state) {
           if ($state == PhabricatorInlineCommentInterface::STATE_DONE) {
             $done++;
           } else {
             $undone++;
           }
         }
         if ($done && $undone) {
           return pht(
             '%s marked %s inline comment(s) as done and %s inline comment(s) '.
             'as not done.',
             $this->renderHandleLink($author_phid),
             new PhutilNumber($done),
             new PhutilNumber($undone));
         } else if ($done) {
           return pht(
             '%s marked %s inline comment(s) as done.',
             $this->renderHandleLink($author_phid),
             new PhutilNumber($done));
         } else {
           return pht(
             '%s marked %s inline comment(s) as not done.',
             $this->renderHandleLink($author_phid),
             new PhutilNumber($undone));
         }
         break;
 
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        $moves = $this->getInterestingMoves($new);
+        if (count($moves) == 1) {
+          $move = head($moves);
+          $from_columns = $move['fromColumnPHIDs'];
+          $to_column = $move['columnPHID'];
+          $board_phid = $move['boardPHID'];
+          if (count($from_columns) == 1) {
+            return pht(
+              '%s moved this task from %s to %s on the %s board.',
+              $this->renderHandleLink($author_phid),
+              $this->renderHandleLink(head($from_columns)),
+              $this->renderHandleLink($to_column),
+              $this->renderHandleLink($board_phid));
+          } else {
+            return pht(
+              '%s moved this task to %s on the %s board.',
+              $this->renderHandleLink($author_phid),
+              $this->renderHandleLink($to_column),
+              $this->renderHandleLink($board_phid));
+          }
+        } else {
+          $fragments = array();
+          foreach ($moves as $move) {
+            $fragments[] = pht(
+              '%s (%s)',
+              $this->renderHandleLink($board_phid),
+              $this->renderHandleLink($to_column));
+          }
+
+          return pht(
+            '%s moved this task on %s board(s): %s.',
+            $this->renderHandleLink($author_phid),
+            phutil_count($moves),
+            phutil_implode_html(', ', $fragments));
+        }
+        break;
+
       default:
         return pht(
           '%s edited this %s.',
           $this->renderHandleLink($author_phid),
           $this->getApplicationObjectTypeName());
     }
   }
 
   public function getTitleForFeed() {
     $author_phid = $this->getAuthorPHID();
     $object_phid = $this->getObjectPHID();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CREATE:
         return pht(
           '%s created %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_COMMENT:
         return pht(
           '%s added a comment to %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         return pht(
           '%s changed the visibility for %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
         return pht(
           '%s changed the edit policy for %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         return pht(
           '%s changed the join policy for %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return pht(
           '%s updated subscribers of %s.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid));
       case PhabricatorTransactions::TYPE_SPACE:
         return pht(
           '%s shifted %s from the %s space to the %s space.',
           $this->renderHandleLink($author_phid),
           $this->renderHandleLink($object_phid),
           $this->renderHandleLink($old),
           $this->renderHandleLink($new));
       case PhabricatorTransactions::TYPE_EDGE:
         $new = ipull($new, 'dst');
         $old = ipull($old, 'dst');
         $add = array_diff($new, $old);
         $rem = array_diff($old, $new);
         $type = $this->getMetadata('edge:type');
         $type = head($type);
 
         $type_obj = PhabricatorEdgeType::getByConstant($type);
 
         if ($add && $rem) {
           return $type_obj->getFeedEditString(
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($object_phid),
             new PhutilNumber(count($add) + count($rem)),
             phutil_count($add),
             $this->renderHandleList($add),
             phutil_count($rem),
             $this->renderHandleList($rem));
         } else if ($add) {
           return $type_obj->getFeedAddString(
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($object_phid),
             phutil_count($add),
             $this->renderHandleList($add));
         } else if ($rem) {
           return $type_obj->getFeedRemoveString(
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($object_phid),
             phutil_count($rem),
             $this->renderHandleList($rem));
         } else {
           return pht(
             '%s edited edge metadata for %s.',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($object_phid));
         }
 
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           return $field->getApplicationTransactionTitleForFeed($this);
         } else {
           return pht(
             '%s edited a custom field on %s.',
             $this->renderHandleLink($author_phid),
             $this->renderHandleLink($object_phid));
         }
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_BUILDING:
             return pht(
               '%s started building %s for %s.',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')),
               $this->renderHandleLink($object_phid));
           case HarbormasterBuildable::STATUS_PASSED:
             return pht(
               '%s completed building %s for %s.',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')),
               $this->renderHandleLink($object_phid));
           case HarbormasterBuildable::STATUS_FAILED:
             return pht(
               '%s failed to build %s for %s.',
               $this->renderHandleLink($author_phid),
               $this->renderHandleLink(
                 $this->getMetadataValue('harbormaster:buildablePHID')),
               $this->renderHandleLink($object_phid));
           default:
             return null;
         }
 
+      case PhabricatorTransactions::TYPE_COLUMNS:
+        $moves = $this->getInterestingMoves($new);
+        if (count($moves) == 1) {
+          $move = head($moves);
+          $from_columns = $move['fromColumnPHIDs'];
+          $to_column = $move['columnPHID'];
+          $board_phid = $move['boardPHID'];
+          if (count($from_columns) == 1) {
+            return pht(
+              '%s moved %s from %s to %s on the %s board.',
+              $this->renderHandleLink($author_phid),
+              $this->renderHandleLink($object_phid),
+              $this->renderHandleLink(head($from_columns)),
+              $this->renderHandleLink($to_column),
+              $this->renderHandleLink($board_phid));
+          } else {
+            return pht(
+              '%s moved %s to %s on the %s board.',
+              $this->renderHandleLink($author_phid),
+              $this->renderHandleLink($object_phid),
+              $this->renderHandleLink($to_column),
+              $this->renderHandleLink($board_phid));
+          }
+        } else {
+          $fragments = array();
+          foreach ($moves as $move) {
+            $fragments[] = pht(
+              '%s (%s)',
+              $this->renderHandleLink($board_phid),
+              $this->renderHandleLink($to_column));
+          }
+
+          return pht(
+            '%s moved %s on %s board(s): %s.',
+            $this->renderHandleLink($author_phid),
+            $this->renderHandleLink($object_phid),
+            phutil_count($moves),
+            phutil_implode_html(', ', $fragments));
+        }
+        break;
+
     }
 
     return $this->getTitle();
   }
 
   public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) {
     $fields = array();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         $text = $this->getComment()->getContent();
         if (strlen($text)) {
           $fields[] = 'comment/'.$this->getID();
         }
         break;
     }
 
     return $fields;
   }
 
   public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         $text = $this->getComment()->getContent();
         return PhabricatorMarkupEngine::summarize($text);
     }
 
     return null;
   }
 
   public function getBodyForFeed(PhabricatorFeedStory $story) {
     $remarkup = $this->getRemarkupBodyForFeed($story);
     if ($remarkup !== null) {
       $remarkup = PhabricatorMarkupEngine::summarize($remarkup);
       return new PHUIRemarkupView($this->viewer, $remarkup);
     }
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     $body = null;
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         $text = $this->getComment()->getContent();
         if (strlen($text)) {
           $body = $story->getMarkupFieldOutput('comment/'.$this->getID());
         }
         break;
     }
 
     return $body;
   }
 
   public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) {
     return null;
   }
 
   public function getActionStrength() {
     if ($this->isInlineCommentTransaction()) {
       return 0.25;
     }
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         return 0.5;
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         if ($this->isSelfSubscription()) {
           // Make this weaker than TYPE_COMMENT.
           return 0.25;
         }
         break;
     }
 
     return 1.0;
   }
 
   public function isCommentTransaction() {
     if ($this->hasComment()) {
       return true;
     }
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         return true;
     }
 
     return false;
   }
 
   public function isInlineCommentTransaction() {
     return false;
   }
 
   public function getActionName() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_COMMENT:
         return pht('Commented On');
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
       case PhabricatorTransactions::TYPE_EDIT_POLICY:
       case PhabricatorTransactions::TYPE_JOIN_POLICY:
         return pht('Changed Policy');
       case PhabricatorTransactions::TYPE_SUBSCRIBERS:
         return pht('Changed Subscribers');
       case PhabricatorTransactions::TYPE_BUILDABLE:
         switch ($this->getNewValue()) {
           case HarbormasterBuildable::STATUS_PASSED:
             return pht('Build Passed');
           case HarbormasterBuildable::STATUS_FAILED:
             return pht('Build Failed');
           default:
             return pht('Build Status');
         }
       default:
         return pht('Updated');
     }
   }
 
   public function getMailTags() {
     return array();
   }
 
   public function hasChangeDetails() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           return $field->getApplicationTransactionHasChangeDetails($this);
         }
         break;
     }
     return false;
   }
 
   public function renderChangeDetails(PhabricatorUser $viewer) {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_CUSTOMFIELD:
         $field = $this->getTransactionCustomField();
         if ($field) {
           return $field->getApplicationTransactionChangeDetails($this, $viewer);
         }
         break;
     }
 
     return $this->renderTextCorpusChangeDetails(
       $viewer,
       $this->getOldValue(),
       $this->getNewValue());
   }
 
   public function renderTextCorpusChangeDetails(
     PhabricatorUser $viewer,
     $old,
     $new) {
 
     require_celerity_resource('differential-changeset-view-css');
 
     $view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
       ->setUser($viewer)
       ->setOldText($old)
       ->setNewText($new);
 
     return $view->render();
   }
 
   public function attachTransactionGroup(array $group) {
     assert_instances_of($group, __CLASS__);
     $this->transactionGroup = $group;
     return $this;
   }
 
   public function getTransactionGroup() {
     return $this->transactionGroup;
   }
 
   /**
    * Should this transaction be visually grouped with an existing transaction
    * group?
    *
    * @param list<PhabricatorApplicationTransaction> List of transactions.
    * @return bool True to display in a group with the other transactions.
    */
   public function shouldDisplayGroupWith(array $group) {
     $this_source = null;
     if ($this->getContentSource()) {
       $this_source = $this->getContentSource()->getSource();
     }
 
     foreach ($group as $xaction) {
       // Don't group transactions by different authors.
       if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
         return false;
       }
 
       // Don't group transactions for different objects.
       if ($xaction->getObjectPHID() != $this->getObjectPHID()) {
         return false;
       }
 
       // Don't group anything into a group which already has a comment.
       if ($xaction->isCommentTransaction()) {
         return false;
       }
 
       // Don't group transactions from different content sources.
       $other_source = null;
       if ($xaction->getContentSource()) {
         $other_source = $xaction->getContentSource()->getSource();
       }
 
       if ($other_source != $this_source) {
         return false;
       }
 
       // Don't group transactions which happened more than 2 minutes apart.
       $apart = abs($xaction->getDateCreated() - $this->getDateCreated());
       if ($apart > (60 * 2)) {
         return false;
       }
     }
 
     return true;
   }
 
   public function renderExtraInformationLink() {
     $herald_xscript_id = $this->getMetadataValue('herald:transcriptID');
 
     if ($herald_xscript_id) {
       return phutil_tag(
         'a',
         array(
           'href' => '/herald/transcript/'.$herald_xscript_id.'/',
         ),
         pht('View Herald Transcript'));
     }
 
     return null;
   }
 
   public function renderAsTextForDoorkeeper(
     DoorkeeperFeedStoryPublisher $publisher,
     PhabricatorFeedStory $story,
     array $xactions) {
 
     $text = array();
     $body = array();
 
     foreach ($xactions as $xaction) {
       $xaction_body = $xaction->getBodyForMail();
       if ($xaction_body !== null) {
         $body[] = $xaction_body;
       }
 
       if ($xaction->shouldHideForMail($xactions)) {
         continue;
       }
 
       $old_target = $xaction->getRenderingTarget();
       $new_target = self::TARGET_TEXT;
       $xaction->setRenderingTarget($new_target);
 
       if ($publisher->getRenderWithImpliedContext()) {
         $text[] = $xaction->getTitle();
       } else {
         $text[] = $xaction->getTitleForFeed();
       }
 
       $xaction->setRenderingTarget($old_target);
     }
 
     $text = implode("\n", $text);
     $body = implode("\n\n", $body);
 
     return rtrim($text."\n\n".$body);
   }
 
   /**
    * Test if this transaction is just a user subscribing or unsubscribing
    * themselves.
    */
   private function isSelfSubscription() {
     $type = $this->getTransactionType();
     if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) {
       return false;
     }
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     $add = array_diff($old, $new);
     $rem = array_diff($new, $old);
 
     if ((count($add) + count($rem)) != 1) {
       // More than one user affected.
       return false;
     }
 
     $affected_phid = head(array_merge($add, $rem));
     if ($affected_phid != $this->getAuthorPHID()) {
       // Affected user is someone else.
       return false;
     }
 
     return true;
   }
 
+  private function getInterestingMoves(array $moves) {
+    // Remove moves which only shift the position of a task within a column.
+    foreach ($moves as $key => $move) {
+      $from_phids = array_fuse($move['fromColumnPHIDs']);
+      if (isset($from_phids[$move['columnPHID']])) {
+        unset($moves[$key]);
+      }
+    }
+
+    return $moves;
+  }
+
 
 /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return ($viewer->getPHID() == $this->getAuthorPHID());
   }
 
   public function describeAutomaticCapability($capability) {
     return pht(
       'Transactions are visible to users that can see the object which was '.
       'acted upon. Some transactions - in particular, comments - are '.
       'editable by the transaction author.');
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $comment_template = null;
       try {
         $comment_template = $this->getApplicationTransactionCommentObject();
       } catch (Exception $ex) {
         // Continue; no comments for these transactions.
       }
 
       if ($comment_template) {
         $comments = $comment_template->loadAllWhere(
           'transactionPHID = %s',
           $this->getPHID());
         foreach ($comments as $comment) {
           $engine->destroyObject($comment);
         }
       }
 
       $this->delete();
     $this->saveTransaction();
   }
 
 
 }
diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
index a771bf074..3084b2d43 100644
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
@@ -1,146 +1,143 @@
 <?php
 
 final class PhabricatorTypeaheadFunctionHelpController
   extends PhabricatorTypeaheadDatasourceController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $this->getViewer();
     $class = $request->getURIData('class');
 
     $sources = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorTypeaheadDatasource')
       ->execute();
 
     if (!isset($sources[$class])) {
       return new Aphront404Response();
     }
 
     $source = $sources[$class];
 
     $application_class = $source->getDatasourceApplicationClass();
     if ($application_class) {
       $result = id(new PhabricatorApplicationQuery())
         ->setViewer($this->getViewer())
         ->withClasses(array($application_class))
         ->execute();
       if (!$result) {
         return new Aphront404Response();
       }
     }
 
     $source->setViewer($viewer);
 
     $title = pht('Typeahead Function Help');
 
     $functions = $source->getAllDatasourceFunctions();
     ksort($functions);
 
     $content = array();
 
     $content[] = '= '.pht('Overview');
     $content[] = pht(
       'Typeahead functions are an advanced feature which allow you to build '.
       'more powerful queries. This document explains functions available '.
       'for the selected control.'.
       "\n\n".
       'For general help with search, see the [[ %s | Search User Guide ]] in '.
       'the documentation.'.
       "\n\n".
       'Note that different controls support //different// functions '.
       '(depending on what the control is doing), so these specific functions '.
       'may not work everywhere. You can always check the help for a control '.
       'to review which functions are available for that control.',
       PhabricatorEnv::getDoclink('Search User Guide'));
 
     $table = array();
 
     $table_header = array(
       pht('Function'),
       pht('Token Name'),
       pht('Summary'),
     );
     $table[] = '| '.implode(' | ', $table_header).' |';
     $table[] = '|---|---|---|';
 
     foreach ($functions as $function => $spec) {
       $spec = $spec + array(
         'summary' => null,
         'arguments' => null,
       );
 
       if (idx($spec, 'arguments')) {
         $signature = '**'.$function.'(**//'.$spec['arguments'].'//**)**';
       } else {
         $signature = '**'.$function.'()**';
       }
 
       $name = idx($spec, 'name', '');
       $summary = idx($spec, 'summary', '');
 
       $table[] = '| '.$signature.' | '.$name.' | '.$summary.' |';
     }
 
     $table = implode("\n", $table);
     $content[] = '= '.pht('Function Quick Reference');
     $content[] = pht(
       'This table briefly describes available functions for this control. '.
       'For details on a particular function, see the corresponding section '.
       'below.');
     $content[] = $table;
 
     $content[] = '= '.pht('Using Typeahead Functions');
     $content[] = pht(
       "In addition to typing user and project names to build queries, you can ".
       "also type the names of special functions which give you more options ".
       "and the ability to express more complex queries.\n\n".
       "Functions have an internal name (like `%s`) and a human-readable name, ".
       "like `Current Viewer`. In general, you can type either one to select ".
       "the function. You can also click the {nav icon=search} button on any ".
       "typeahead control to browse available functions and find this ".
       "documentation.\n\n".
       "This documentation uses the internal names to make it clear where ".
       "tokens begin and end. Specifically, you will find queries written ".
       "out like this in the documentation:\n\n%s\n\n".
       "When this query is actually shown in the control, it will look more ".
       "like this:\n\n%s",
       'viewer()',
       '> viewer(), alincoln',
       '> {nav Current Viewer} {nav alincoln (Abraham Lincoln)}');
 
 
     $middot = "\xC2\xB7";
     foreach ($functions as $function => $spec) {
       $arguments = idx($spec, 'arguments', '');
       $name = idx($spec, 'name');
       $content[] = '= '.$function.'('.$arguments.') '.$middot.' '.$name;
       $content[] = $spec['description'];
     }
 
     $content = implode("\n\n", $content);
     $content_box = new PHUIRemarkupView($viewer, $content);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($title);
 
-    $document = id(new PHUIDocumentView())
+    $document = id(new PHUIDocumentViewPro())
       ->setHeader($header)
       ->appendChild($content_box);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Function Help'));
+    $crumbs->setBorder(true);
 
-    return $this->buildApplicationPage(
-      array(
-        $crumbs,
-        $document,
-      ),
-      array(
-        'title' => $title,
-      ));
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($document);
   }
 
 }
diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
index b3687b85e..15650a33f 100644
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
@@ -1,345 +1,353 @@
 <?php
 
 final class PhabricatorTypeaheadModularDatasourceController
   extends PhabricatorTypeaheadDatasourceController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $query = $request->getStr('q');
     $offset = $request->getInt('offset');
     $select_phid = null;
     $is_browse = ($request->getURIData('action') == 'browse');
 
     $select = $request->getStr('select');
     if ($select) {
       $select = phutil_json_decode($select);
       $query = idx($select, 'q');
       $offset = idx($select, 'offset');
       $select_phid = idx($select, 'phid');
     }
 
     // Default this to the query string to make debugging a little bit easier.
     $raw_query = nonempty($request->getStr('raw'), $query);
 
     // This makes form submission easier in the debug view.
     $class = nonempty($request->getURIData('class'), $request->getStr('class'));
 
     $sources = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorTypeaheadDatasource')
       ->execute();
 
     if (isset($sources[$class])) {
       $source = $sources[$class];
       $source->setParameters($request->getRequestData());
       $source->setViewer($viewer);
 
       // NOTE: Wrapping the source in a Composite datasource ensures we perform
       // application visibility checks for the viewer, so we do not need to do
       // those separately.
       $composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
       $composite->addDatasource($source);
 
       $hard_limit = 1000;
       $limit = 100;
 
       $composite
         ->setViewer($viewer)
         ->setQuery($query)
         ->setRawQuery($raw_query)
         ->setLimit($limit + 1);
 
       if ($is_browse) {
         if (!$composite->isBrowsable()) {
           return new Aphront404Response();
         }
 
         if (($offset + $limit) >= $hard_limit) {
           // Offset-based paging is intrinsically slow; hard-cap how far we're
           // willing to go with it.
           return new Aphront404Response();
         }
 
         $composite
           ->setOffset($offset);
       }
 
       $results = $composite->loadResults();
 
       if ($is_browse) {
         // If this is a request for a specific token after the user clicks
         // "Select", return the token in wire format so it can be added to
         // the tokenizer.
         if ($select_phid !== null) {
           $map = mpull($results, null, 'getPHID');
           $token = idx($map, $select_phid);
           if (!$token) {
             return new Aphront404Response();
           }
 
           $payload = array(
             'key' => $token->getPHID(),
             'token' => $token->getWireFormat(),
           );
 
           return id(new AphrontAjaxResponse())->setContent($payload);
         }
 
         $format = $request->getStr('format');
         switch ($format) {
           case 'html':
           case 'dialog':
             // These are the acceptable response formats.
             break;
           default:
             // Return a dialog if format information is missing or invalid.
             $format = 'dialog';
             break;
         }
 
         $next_link = null;
         if (count($results) > $limit) {
           $results = array_slice($results, 0, $limit, $preserve_keys = true);
           if (($offset + (2 * $limit)) < $hard_limit) {
             $next_uri = id(new PhutilURI($request->getRequestURI()))
               ->setQueryParam('offset', $offset + $limit)
               ->setQueryParam('q', $query)
               ->setQueryParam('raw', $raw_query)
               ->setQueryParam('format', 'html');
 
             $next_link = javelin_tag(
               'a',
               array(
                 'href' => $next_uri,
                 'class' => 'typeahead-browse-more',
                 'sigil' => 'typeahead-browse-more',
                 'mustcapture' => true,
               ),
               pht('More Results'));
           } else {
             // If the user has paged through more than 1K results, don't
             // offer to page any further.
             $next_link = javelin_tag(
               'div',
               array(
                 'class' => 'typeahead-browse-hard-limit',
               ),
               pht('You reach the edge of the abyss.'));
           }
         }
 
         $exclude = $request->getStrList('exclude');
         $exclude = array_fuse($exclude);
 
         $select = array(
           'offset' => $offset,
           'q' => $query,
         );
 
         $items = array();
         foreach ($results as $result) {
           $token = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
             $result);
 
           // Disable already-selected tokens.
           $disabled = isset($exclude[$result->getPHID()]);
 
           $value = $select + array('phid' => $result->getPHID());
           $value = json_encode($value);
 
           $button = phutil_tag(
             'button',
             array(
               'class' => 'small grey',
               'name' => 'select',
               'value' => $value,
               'disabled' => $disabled ? 'disabled' : null,
             ),
             pht('Select'));
 
           $items[] = phutil_tag(
             'div',
             array(
               'class' => 'typeahead-browse-item grouped',
             ),
             array(
               $token,
               $button,
             ));
         }
 
         $markup = array(
           $items,
           $next_link,
         );
 
         if ($format == 'html') {
           $content = array(
             'markup' => hsprintf('%s', $markup),
           );
           return id(new AphrontAjaxResponse())->setContent($content);
         }
 
         $this->requireResource('typeahead-browse-css');
         $this->initBehavior('typeahead-browse');
 
         $input_id = celerity_generate_unique_node_id();
         $frame_id = celerity_generate_unique_node_id();
 
         $config = array(
           'inputID' => $input_id,
           'frameID' => $frame_id,
           'uri' => (string)$request->getRequestURI(),
         );
         $this->initBehavior('typeahead-search', $config);
 
         $search = javelin_tag(
           'input',
           array(
             'type' => 'text',
             'id' => $input_id,
             'class' => 'typeahead-browse-input',
             'autocomplete' => 'off',
             'placeholder' => $source->getPlaceholderText(),
           ));
 
         $frame = phutil_tag(
           'div',
           array(
             'class' => 'typeahead-browse-frame',
             'id' => $frame_id,
           ),
           $markup);
 
         $browser = array(
           phutil_tag(
             'div',
             array(
               'class' => 'typeahead-browse-header',
             ),
             $search),
           $frame,
         );
 
         $function_help = null;
         if ($source->getAllDatasourceFunctions()) {
           $reference_uri = '/typeahead/help/'.get_class($source).'/';
 
           $reference_link = phutil_tag(
             'a',
             array(
               'href' => $reference_uri,
               'target' => '_blank',
             ),
             pht('Reference: Advanced Functions'));
 
           $function_help = array(
             id(new PHUIIconView())
               ->setIcon('fa-book'),
             ' ',
             $reference_link,
           );
         }
 
         return $this->newDialog()
           ->setWidth(AphrontDialogView::WIDTH_FORM)
           ->setRenderDialogAsDiv(true)
           ->setTitle($source->getBrowseTitle())
           ->appendChild($browser)
           ->addFooter($function_help)
           ->addCancelButton('/', pht('Close'));
       }
 
     } else if ($is_browse) {
       return new Aphront404Response();
     } else {
       $results = array();
     }
 
     $content = mpull($results, 'getWireFormat');
     $content = array_values($content);
 
     if ($request->isAjax()) {
       return id(new AphrontAjaxResponse())->setContent($content);
     }
 
     // If there's a non-Ajax request to this endpoint, show results in a tabular
     // format to make it easier to debug typeahead output.
 
     foreach ($sources as $key => $source) {
       // This can happen with composite or generic sources.
       if (!$source->getDatasourceApplicationClass()) {
         continue;
       }
       if (!PhabricatorApplication::isClassInstalledForViewer(
         $source->getDatasourceApplicationClass(),
         $viewer)) {
         unset($sources[$key]);
       }
     }
     $options = array_fuse(array_keys($sources));
     asort($options);
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->setAction('/typeahead/class/')
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel(pht('Source Class'))
           ->setName('class')
           ->setValue($class)
           ->setOptions($options))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Query'))
           ->setName('q')
           ->setValue($request->getStr('q')))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Raw Query'))
           ->setName('raw')
           ->setValue($request->getStr('raw')))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Query')));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Token Query'))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
     $table = new AphrontTableView($content);
     $table->setHeaders(
       array(
         pht('Name'),
         pht('URI'),
         pht('PHID'),
         pht('Priority'),
         pht('Display Name'),
         pht('Display Type'),
         pht('Image URI'),
         pht('Priority Type'),
         pht('Icon'),
         pht('Closed'),
         pht('Sprite'),
       ));
 
     $result_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Token Results (%s)', $class))
+      ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($table);
 
-    return $this->buildApplicationPage(
-      array(
+    $title = pht('Typeahead Results');
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title);
+
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter(array(
         $form_box,
         $result_box,
-      ),
-      array(
-        'title' => pht('Typeahead Results'),
-        'device' => false,
       ));
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->appendChild($view);
   }
 
 }
diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
index 8da553104..6be978a6a 100644
--- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
+++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
@@ -1,60 +1,58 @@
 <?php
 
 final class PhabricatorUIExampleRenderController extends PhabricatorController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $id = $request->getURIData('class');
 
     $classes = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorUIExample')
       ->setSortMethod('getName')
       ->execute();
 
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI($this->getApplicationURI('view/')));
 
     foreach ($classes as $class => $obj) {
       $name = $obj->getName();
       $nav->addFilter($class, $name);
     }
 
     $selected = $nav->selectFilter($id, head_key($classes));
 
     $example = $classes[$selected];
     $example->setRequest($this->getRequest());
 
     $result = $example->renderExample();
     if ($result instanceof AphrontResponse) {
       // This allows examples to generate dialogs, etc., for demonstration.
       return $result;
     }
 
     require_celerity_resource('phabricator-ui-example-css');
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($example->getName());
 
     $note = id(new PHUIInfoView())
       ->setTitle(pht('%s (%s)', $example->getName(), get_class($example)))
       ->appendChild($example->getDescription())
       ->setSeverity(PHUIInfoView::SEVERITY_NODATA);
 
     $nav->appendChild(
       array(
         $crumbs,
         $note,
         $result,
       ));
 
-    return $this->buildApplicationPage(
-      $nav,
-      array(
-        'title'   => $example->getName(),
-      ));
+    return $this->newPage()
+      ->setTitle($example->getName())
+      ->appendChild($nav);
   }
 
 }
diff --git a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
index 65ca5593f..5faab70c7 100644
--- a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
+++ b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
@@ -1,97 +1,98 @@
 <?php
 
 final class PhabricatorXHProfSampleListController
   extends PhabricatorXHProfController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $view = $request->getURIData('view');
 
     if (!$view) {
       $view = 'all';
     }
 
     $pager = new PHUIPagerView();
     $pager->setOffset($request->getInt('page'));
 
     switch ($view) {
       case 'sampled':
         $clause = 'sampleRate > 0';
         $show_type = false;
         break;
       case 'my-runs':
         $clause = qsprintf(
           id(new PhabricatorXHProfSample())->establishConnection('r'),
           'sampleRate = 0 AND userPHID = %s',
           $request->getUser()->getPHID());
         $show_type = false;
         break;
       case 'manual':
         $clause = 'sampleRate = 0';
         $show_type = false;
         break;
       case 'all':
       default:
         $clause = '1 = 1';
         $show_type = true;
         break;
     }
 
     $samples = id(new PhabricatorXHProfSample())->loadAllWhere(
       '%Q ORDER BY id DESC LIMIT %d, %d',
       $clause,
       $pager->getOffset(),
       $pager->getPageSize() + 1);
 
     $samples = $pager->sliceResults($samples);
     $pager->setURI($request->getRequestURI(), 'page');
 
     $list = new PHUIObjectItemListView();
     foreach ($samples as $sample) {
       $file_phid = $sample->getFilePHID();
 
       $item = id(new PHUIObjectItemView())
         ->setObjectName($sample->getID())
         ->setHeader($sample->getRequestPath())
         ->setHref($this->getApplicationURI('profile/'.$file_phid.'/'))
         ->addAttribute(
           number_format($sample->getUsTotal())." \xCE\xBCs");
 
       if ($sample->getController()) {
         $item->addAttribute($sample->getController());
       }
 
       $item->addAttribute($sample->getHostName());
 
       $rate = $sample->getSampleRate();
       if ($rate == 0) {
         $item->addIcon('flag-6', pht('Manual Run'));
       } else {
         $item->addIcon('flag-7', pht('Sampled (1/%d)', $rate));
       }
 
       $item->addIcon(
         'none',
         phabricator_datetime($sample->getDateCreated(), $viewer));
 
       $list->addItem($item);
     }
 
     $list->setPager($pager);
     $list->setNoDataString(pht('There are no profiling samples.'));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('XHProf Samples'));
 
-    return $this->buildApplicationPage(
-      array($crumbs, $list),
-      array(
-        'title' => pht('XHProf Samples'),
-      ));
+    $title = pht('XHProf Samples');
+
+    return $this->newPage()
+      ->setTitle($title)
+      ->setCrumbs($crumbs)
+      ->appendChild($list);
 
   }
 }
diff --git a/src/docs/contributor/using_oauthserver.diviner b/src/docs/contributor/using_oauthserver.diviner
index b09f595a5..b40496d7c 100644
--- a/src/docs/contributor/using_oauthserver.diviner
+++ b/src/docs/contributor/using_oauthserver.diviner
@@ -1,120 +1,116 @@
 @title Using the Phabricator OAuth Server
 @group developer
 
 How to use the Phabricator OAuth Server.
 
 = Overview =
 
 Phabricator includes an OAuth Server which supports the
 `Authorization Code Grant` flow as described in the OAuth 2.0
 specification:
 
 http://tools.ietf.org/html/draft-ietf-oauth-v2-23
 
 This functionality can allow clients to integrate with a given
 Phabricator instance in a secure way with granular data access.
 For example, Phabricator can be used as a central identity store for any
 clients that implement OAuth 2.0.
 
 = Vocabulary =
 
 - **Access token** - a token which allows a client to ask for data on behalf
  of a resource owner. A given client will only be able to access data included
  in the scope(s) the resource owner authorized that client for.
 - **Authorization code** - a short-lived code which allows an authenticated
  client to ask for an access token on behalf of some resource owner.
 - **Client** - this is the application or system asking for data from the
  OAuth Server on behalf of the resource owner.
 - **Resource owner** - this is the user the client and OAuth Server are
  concerned with on a given request.
 - **Scope** - this defines a specific piece of granular data a client can
  or can not access on behalf of a user. For example, if authorized for the
  "whoami" scope on behalf of a given resource owner, the client can get the
  results of Conduit.whoami for that resource owner when authenticated with
  a valid access token.
 
 = Setup - Creating a Client =
 
 # Visit {nav Your Local Install > OAuth Server > Create Application}
 # Fill out the form
 # Profit
 
 = Obtaining an Authorization Code =
 
 POST or GET `https://phabricator.example.com/oauthserver/auth/` with the
 following parameters:
 
 - Required - **client_id** - the id of the newly registered client.
 - Required - **response_type** - the desired type of authorization code
  response. Only code is supported at this time.
 - Optional - **redirect_uri** - override the redirect_uri the client
  registered. This redirect_uri must have the same fully-qualified domain,
  path, port and have at least the same query parameters as the redirect_uri
  the client registered, as well as have no fragments.
 - Optional - **scope** - specify what scope(s) the client needs access to
  in a space-delimited list.
 - Optional - **state** - an opaque value the client can send to the server
  for programmatic excellence. Some clients use this value to implement XSRF
  protection or for debugging purposes.
 
 If done correctly and the resource owner has not yet authorized the client
 for the desired scope, then the resource owner will be presented with an
 interface to authorize the client for the desired scope. The OAuth Server
 will redirect to the pertinent redirect_uri with an authorization code or
 an error indicating the resource owner did not authorize the client, depending.
 
 If done correctly and the resource owner has already authorized the client for
 the desired scope, then the OAuth Server will redirect to the pertinent
 redirect_uri with a valid authorization code.
 
 If there is an error, the OAuth Server will return a descriptive error
 message. This error will be presented to the resource owner on the
 Phabricator domain if there is reason to believe there is something fishy
 with the client. For example, if there is an issue with the redirect_uri.
 Otherwise, the OAuth Server will redirect to the pertinent redirect_uri
 and include the pertinent error information.
 
 = Obtaining an Access Token =
 
 POST or GET `https://phabricator.example.com/oauthserver/token/`
 with the following parameters:
 
 - Required - **client_id** - the id of the client
 - Required - **client_secret** - the secret of the client.
  This is used to authenticate the client.
 - Required - **code** - the authorization code obtained earlier.
 - Required - **grant_type** - the desired type of access grant.
  Only token is supported at this time.
 - Optional - **redirect_uri** - should be the exact same redirect_uri as
  the redirect_uri specified to obtain the authorization code. If no
  redirect_uri was specified to obtain the authorization code then this
  should not be specified.
 
 If done correctly, the OAuth Server will redirect to the pertinent
 redirect_uri with an access token.
 
 If there is an error, the OAuth Server will return a descriptive error
 message.
 
 = Using an Access Token =
 
 Simply include a query param with the key of "access_token" and the value
 as the earlier obtained access token. For example:
 
 ```https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp```
 
 If the token has expired or is otherwise invalid, the client will receive
 an error indicating as such. In these cases, the client should re-initiate
 the entire `Authorization Code Grant` flow.
 
 NOTE: See "Scopes" section below for more information on what data is
 currently exposed through the OAuth Server.
 
-= Scopes =
+Scopes
+======
 
-There are only two scopes supported at this time.
-
-- **offline_access** - allows an access token to work indefinitely without
- expiring.
-- **whoami** - allows the client to access the results of Conduit.whoami on
- behalf of the resource owner.
+//This section has not been written yet.//
diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner
index c56600ebd..379c09e5c 100644
--- a/src/docs/user/userguide/remarkup.diviner
+++ b/src/docs/user/userguide/remarkup.diviner
@@ -1,635 +1,639 @@
 @title Remarkup Reference
 @group userguide
 
 Explains how to make bold text; this makes your words louder so you can win
 arguments.
 
 = Overview =
 
 Phabricator uses a lightweight markup language called "Remarkup", similar to
 other lightweight markup languages like Markdown and Wiki markup.
 
 This document describes how to format text using Remarkup.
 
 = Quick Reference =
 
 All the syntax is explained in more detail below, but this is a quick guide to
 formatting text in Remarkup.
 
 These are inline styles, and can be applied to most text:
 
   **bold** //italic// `monospaced` ##monospaced## ~~deleted~~ __underlined__
   !!highlighted!!
   D123 T123 rX123           # Link to Objects
   {D123} {T123}             # Link to Objects (Full Name)
   {F123}                    # Embed Images
   {M123}                    # Embed Pholio Mock
   @username                 # Mention a User
   #project                  # Mention a Project
   [[wiki page]]             # Link to Phriction
   [[wiki page | name]]      # Named link to Phriction
   http://xyz/               # Link to web
   [[http://xyz/ | name]]    # Named link to web
   [name](http://xyz/)       # Alternate Link
 
 These are block styles, and must be separated from surrounding text by
 empty lines:
 
   = Large Header =
 
   == Smaller Header ==
 
   ## This is a Header As Well
 
   Also a Large Header
   ===================
 
   Also a Smaller Header
   ---------------------
 
   > Quoted Text
 
   Use `- ` or `* ` for bulleted lists, and `# ` for numbered lists.
   Use ``` or indent two spaces for code.
   Use %%% for a literal block.
   Use | ... | ... for tables.
 
 = Basic Styling =
 
 Format **basic text styles** like this:
 
   **bold text**
   //italic text//
   `monospaced text`
   ##monospaced text##
   ~~deleted text~~
   __underlined text__
   !!highlighted text!!
 
 Those produce **bold text**, //italic text//, `monospaced text`, ##monospaced
 text##, ~~deleted text~~, __underlined text__, and !!highlighted text!!
 respectively.
 
 = Layout =
 
 Make **headers** like this:
 
   = Large Header =
 
   == Smaller Header ==
 
   ===== Very Small Header =====
 
   Alternate Large Header
   ======================
 
   Alternate Smaller Header
   ------------------------
 
 You can optionally omit the trailing `=` signs -- that is, these are the same:
 
   == Smaller Header ==
 
   == Smaller Header
 
 This produces headers like the ones in this document. Make sure you have an
 empty line before and after the header.
 
 Lists
 =====
 
 Make **lists** by beginning each item with a `-` or a `*`:
 
   lang=text
   - milk
   - eggs
   - bread
 
   * duck
   * duck
   * goose
 
 This produces a list like this:
 
   - milk
   - eggs
   - bread
 
 (Note that you need to put a space after the `-` or `*`.)
 
 You can make numbered lists with a `#` instead of `-` or `*`:
 
   # Articuno
   # Zapdos
   # Moltres
 
 Numbered lists can also be started with `1.` or `1)`. If you use a number other
 than `1`, the list will start at that number instead. For example, this:
 
 ```
   200) OK
   201) Created
   202) Accepted
 ```
 
 ...produces this:
 
   200) OK
   201) Created
   202) Accepted
 
 You can also nest lists:
 
 ```- Body
   - Head
   - Arm
     - Elbow
     - Hand
       # Thumb
       # Index
       # Middle
       # Ring
       # Pinkie
   - Leg
     - Knee
     - Foot```
 
 ...which produces:
 
   - Body
   - Head
   - Arm
     - Elbow
     - Hand
       # Thumb
       # Index
       # Middle
       # Ring
       # Pinkie
   - Leg
     - Knee
     - Foot
 
 If you prefer, you can indent lists using multiple characters to show indent
 depth, like this:
 
 ```- Tree
 -- Branch
 --- Twig```
 
 As expected, this produces:
 
 - Tree
 -- Branch
 --- Twig
 
 You can add checkboxes to items by prefacing them with `[ ]` or `[X]`, like
 this:
 
 ```
   - [X] Preheat oven to 450 degrees.
   - [ ] Zest 35 lemons.
 ```
 
 When rendered, this produces:
 
   - [X] Preheat oven to 450 degrees.
   - [ ] Zest 35 lemons.
 
 Make **code blocks** by indenting two spaces:
 
   f(x, y);
 
 You can also use three backticks to enclose the code block:
 
   ```f(x, y);
   g(f);```
 
 You can specify a language for syntax highlighting with `lang=xxx`:
 
   lang=text
   lang=html
   <a href="#">...</a>
 
 This will highlight the block using a highlighter for that language, if one is
 available (in most cases, this means you need to configure Pygments):
 
   lang=html
   <a href="#">...</a>
 
 You can also use a `COUNTEREXAMPLE` header to show that a block of code is
 bad and shouldn't be copied:
 
   lang=text
   COUNTEREXAMPLE
   function f() {
     global $$variable_variable;
   }
 
 This produces a block like this:
 
   COUNTEREXAMPLE
   function f() {
     global $$variable_variable;
   }
 
 You can use `lines=N` to limit the vertical size of a chunk of code, and
 `name=some_name.ext` to give it a name. For example, this:
 
   lang=text
   lang=html, name=example.html, lines=12, counterexample
   ...
 
 ...produces this:
 
   lang=html, name=example.html, lines=12, counterexample
   <p>Apple</p>
   <p>Apricot</p>
   <p>Avocado</p>
   <p>Banana</p>
   <p>Bilberry</p>
   <p>Blackberry</p>
   <p>Blackcurrant</p>
   <p>Blueberry</p>
   <p>Currant</p>
   <p>Cherry</p>
   <p>Cherimoya</p>
   <p>Clementine</p>
   <p>Date</p>
   <p>Damson</p>
   <p>Durian</p>
   <p>Eggplant</p>
   <p>Elderberry</p>
   <p>Feijoa</p>
   <p>Gooseberry</p>
   <p>Grape</p>
   <p>Grapefruit</p>
   <p>Guava</p>
   <p>Huckleberry</p>
   <p>Jackfruit</p>
   <p>Jambul</p>
   <p>Kiwi fruit</p>
   <p>Kumquat</p>
   <p>Legume</p>
   <p>Lemon</p>
   <p>Lime</p>
   <p>Lychee</p>
   <p>Mandarine</p>
   <p>Mango</p>
   <p>Mangostine</p>
   <p>Melon</p>
 
 
 You can use the `NOTE:`, `WARNING:` or `IMPORTANT:` elements to call attention
 to an important idea.
 
 For example, write this:
 
 ```
 NOTE: Best practices in proton pack operation include not crossing the streams.
 ```
 
 ...to produce this:
 
 NOTE: Best practices in proton pack operation include not crossing the streams.
 
 Using `WARNING:` or `IMPORTANT:` at the beginning of the line changes the
 color of the callout:
 
 WARNING: Crossing the streams can result in total protonic reversal!
 
 IMPORTANT: Don't cross the streams!
 
 In addition, you can use `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` to get the
 same effect but without `(NOTE)`, `(WARNING)`, or `(IMPORTANT)` appearing in
 the rendered result. For example, this callout uses `(NOTE)`:
 
 (NOTE) Dr. Egon Spengler is the best resource for additional proton pack
  questions.
 
 = Linking URIs =
 
 URIs are automatically linked: http://phabricator.org/
 
 If you have a URI with problematic characters in it, like
 "`http://comma.org/,`", you can surround it with angle brackets:
 
   <http://comma.org/,>
 
 This will force the parser to consume the whole URI: <http://comma.org/,>
 
 You can also use create named links, where you choose the displayed text. These
 work within Phabricator or on the internet at large:
 
   [[/herald/transcript/ | Herald Transcripts]]
   [[http://www.boring-legal-documents.com/ | exciting legal documents]]
 
 Markdown-style links are also supported:
 
   [Toil](http://www.trouble.com)
 
 = Linking to Objects =
 
 You can link to Phabricator objects, such as Differential revisions, Diffusion
 commits and Maniphest tasks, by mentioning the name of an object:
 
   D123          # Link to Differential revision D123
   rX123         # Link to SVN commit 123 from the "X" repository
   rXaf3192cd5   # Link to Git commit "af3192cd5..." from the "X" repository.
                 # You must specify at least 7 characters of the hash.
   T123          # Link to Maniphest task T123
 
 You can also link directly to a comment in Maniphest and Differential:
 
   T123#4        # Link to comment #4 of T123
 
 See the Phabricator configuraton setting `remarkup.ignored-object-names` to
 modify this behavior.
 
 = Embedding Objects
 
 You can also generate full-name references to some objects by using braces:
 
   {D123}        # Link to Differential revision D123 with the full name
   {T123}        # Link to Maniphest task T123 with the full name
 
 These references will also show when an object changes state (for instance, a
 task or revision is closed). Some types of objects support rich embedding.
 
 == Linking to Project Tags
 
 Projects can be linked to with the use of a hashtag `#`. This works by default
 using the name of the Project (lowercase, underscored). Additionally you
 can set multiple additional hashtags by editing the Project details.
 
   #qa, #quality_assurance
 
 == Embedding Mocks (Pholio)
 
 You can embed a Pholio mock by using braces to refer to it:
 
   {M123}
 
 By default the first four images from the mock set are displayed. This behavior
 can be overridden with the **image** option. With the **image** option you can
 provide one or more image IDs to display.
 
 You can set the image (or images) to display like this:
 
   {M123, image=12345}
   {M123, image=12345 & 6789}
 
 == Embedding Pastes
 
 You can embed a Paste using braces:
 
   {P123}
 
 You can adjust the embed height with the `lines` option:
 
   {P123, lines=15}
 
 You can highlight specific lines with the `highlight` option:
 
   {P123, highlight=15}
   {P123, highlight="23-25, 31"}
 
 == Embedding Images
 
 You can embed an image or other file by using braces to refer to it:
 
   {F123}
 
 In most interfaces, you can drag-and-drop an image from your computer into the
 text area to upload and reference it.
 
 Some browsers (e.g. Chrome) support uploading an image data just by pasting them
 from clipboard into the text area.
 
 You can set file display options like this:
 
   {F123, layout=left, float, size=full, alt="a duckling"}
 
 Valid options are:
 
   - **layout** left (default), center, right, inline, link (render a link
     instead of a thumbnail for images)
   - **float** If layout is set to left or right, the image will be floated so
     text wraps around it.
   - **size** thumb (default), full
   - **name** with `layout=link` or for non-images, use this name for the link
     text
   - **width** Scale image to a specific width.
   - **height** Scale image to a specific height.
   - **alt** Provide alternate text for assistive technologies.
 
 == Embedding Countdowns
 
 You can embed a countdown by using braces:
 
   {C123}
 
 = Quoting Text =
 
 To quote text, preface it with an `>`:
 
   > This is quoted text.
 
 This appears like this:
 
 > This is quoted text.
 
 = Embedding Media =
 
 If you set a configuration flag, you can embed media directly in text:
 
   - **remarkup.enable-embedded-youtube**: allows you to paste in YouTube videos
     and have them render inline.
 
 This option is disabled by default because it has security and/or
 silliness implications. Carefully read the description before enabling it.
 
 = Image Macros =
 
 You can upload image macros (More Stuff -> Macro) which will replace text
 strings with the image you specify. For instance, you could upload an image of a
 dancing banana to create a macro named "peanutbutterjellytime", and then any
 time you type that string on a separate line it will be replaced with the image
 of a dancing banana.
 
 = Memes =
 
 You can also use image macros in the context of memes. For example, if you
 have an image macro named `grumpy`, you can create a meme by doing the
 following:
 
   {meme, src = grumpy, above = toptextgoeshere, below = bottomtextgoeshere}
 
 By default, the font used to create the text for the meme is `tuffy.ttf`. For
 the more authentic feel of `impact.ttf`, you simply have to place the Impact
 TrueType font in the Phabricator subfolder `/resources/font/`. If Remarkup
 detects the presence of `impact.ttf`, it will automatically use it.
 
 = Mentioning Users =
 
 In Differential and Maniphest, you can mention another user by writing:
 
   @username
 
 When you submit your comment, this will add them as a CC on the revision or task
 if they aren't already CC'd.
 
 Icons
 =====
 
 You can add icons to comments using the `{icon ...}` syntax. For example:
 
   {icon camera}
 
 This renders: {icon camera}
 
 You can select a color for icons:
 
   {icon camera color=blue}
 
 This renders: {icon camera color=blue}
 
 For a list of available icons and colors, check the UIExamples application.
 (The icons are sourced from
 [[ http://fortawesome.github.io/Font-Awesome/ | FontAwesome ]], so you can also
 browse the collection there.)
 
 You can add `spin` to make the icon spin:
 
   {icon cog spin}
 
 This renders: {icon cog spin}
 
 
 = Phriction Documents =
 
 You can link to Phriction documents with a name or path:
 
   Make sure you sign and date your [[legal/Letter of Marque and Reprisal]]!
 
 With a pipe (`|`), you can retitle the link. Use this to mislead your
 opponents:
 
   Check out these [[legal/boring_documents/ | exciting legal documents]]!
 
 = Literal Blocks =
 
 To place text in a literal block use `%%%`:
 
   %%%Text that won't be processed by remarkup
   [[http://www.example.com | example]]
   %%%
 
 Remarkup will not process the text inside of literal blocks (other than to
 escape HTML and preserve line breaks).
 
 = Tables =
 
 Remarkup supports simple table syntax. For example, this:
 
-  | Fruit  | Color  | Price   | Peel?
-  | -----  | -----  | -----   | -----
-  | Apple  | red    | `$0.93` | no
-  | Banana | yellow | `$0.19` | **YES**
+```
+| Fruit  | Color  | Price   | Peel?
+| -----  | -----  | -----   | -----
+| Apple  | red    | `$0.93` | no
+| Banana | yellow | `$0.19` | **YES**
+```
 
 ...produces this:
 
 | Fruit  | Color  | Price   | Peel?
 | -----  | -----  | -----   | -----
 | Apple  | red    | `$0.93` | no
 | Banana | yellow | `$0.19` | **YES**
 
 Remarkup also supports a simplified HTML table syntax. For example, this:
 
-  <table>
-    <tr>
-      <th>Fruit</th>
-      <th>Color</th>
-      <th>Price</th>
-      <th>Peel?</th>
-    </tr>
-    <tr>
-      <td>Apple</td>
-      <td>red</td>
-      <td>`$0.93`</td>
-      <td>no</td>
-    </tr>
-    <tr>
-      <td>Banana</td>
-      <td>yellow</td>
-      <td>`$0.19`</td>
-      <td>**YES**</td>
-    </tr>
-  </table>
+```
+<table>
+  <tr>
+    <th>Fruit</th>
+    <th>Color</th>
+    <th>Price</th>
+    <th>Peel?</th>
+  </tr>
+  <tr>
+    <td>Apple</td>
+    <td>red</td>
+    <td>`$0.93`</td>
+    <td>no</td>
+  </tr>
+  <tr>
+    <td>Banana</td>
+    <td>yellow</td>
+    <td>`$0.19`</td>
+    <td>**YES**</td>
+  </tr>
+</table>
+```
 
 ...produces this:
 
 <table>
   <tr>
     <th>Fruit</th>
     <th>Color</th>
     <th>Price</th>
     <th>Peel?</th>
   </tr>
   <tr>
     <td>Apple</td>
     <td>red</td>
     <td>`$0.93`</td>
     <td>no</td>
   </tr>
   <tr>
     <td>Banana</td>
     <td>yellow</td>
     <td>`$0.19`</td>
     <td>**YES**</td>
   </tr>
 </table>
 
 Some general notes about this syntax:
 
   - your tags must all be properly balanced;
   - your tags must NOT include attributes (`<td>` is OK, `<td style="...">` is
     not);
   - you can use other Remarkup rules (like **bold**, //italics//, etc.) inside
     table cells.
 
 Navigation Sequences
 ====================
 
 You can use `{nav ...}` to render a stylized navigation sequence when helping
 someone to locate something. This can be useful when writing documentation.
 For example, you could give someone directions to purchase lemons:
 
 {nav icon=home, name=Home >
 Grocery Store >
 Produce Section >
 icon=lemon-o, name=Lemons}
 
 To render this example, use this markup:
 
 ```
 {nav icon=home, name=Home >
 Grocery Store >
 Produce Section >
 icon=lemon-o, name=Lemons}
 ```
 
 In general:
 
   - Separate sections with `>`.
   - Each section can just have a name to add an element to the navigation
     sequence, or a list of key-value pairs.
   - Supported keys are `icon`, `name`, `type` and `href`.
   - The `type` option can be set to `instructions` to indicate that an element
     is asking the user to make a choice or follow specific instructions.
 
 = Fullscreen Mode =
 
 Remarkup editors provide a fullscreen composition mode. This can make it easier
 to edit large blocks of text, or improve focus by removing distractions. You can
 exit **Fullscreen** mode by clicking the button again or by pressing escape.
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
index 694c793e0..421d7eca2 100644
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -1,795 +1,795 @@
 <?php
 
 /**
  * Manages the execution environment configuration, exposing APIs to read
  * configuration settings and other similar values that are derived directly
  * from configuration settings.
  *
  *
  * = Reading Configuration =
  *
  * The primary role of this class is to provide an API for reading
  * Phabricator configuration, @{method:getEnvConfig}:
  *
  *   $value = PhabricatorEnv::getEnvConfig('some.key', $default);
  *
  * The class also handles some URI construction based on configuration, via
  * the methods @{method:getURI}, @{method:getProductionURI},
  * @{method:getCDNURI}, and @{method:getDoclink}.
  *
  * For configuration which allows you to choose a class to be responsible for
  * some functionality (e.g., which mail adapter to use to deliver email),
  * @{method:newObjectFromConfig} provides a simple interface that validates
  * the configured value.
  *
  *
  * = Unit Test Support =
  *
  * In unit tests, you can use @{method:beginScopedEnv} to create a temporary,
  * mutable environment. The method returns a scope guard object which restores
  * the environment when it is destroyed. For example:
  *
  *   public function testExample() {
  *     $env = PhabricatorEnv::beginScopedEnv();
  *     $env->overrideEnv('some.key', 'new-value-for-this-test');
  *
  *     // Some test which depends on the value of 'some.key'.
  *
  *   }
  *
  * Your changes will persist until the `$env` object leaves scope or is
  * destroyed.
  *
  * You should //not// use this in normal code.
  *
  *
  * @task read     Reading Configuration
  * @task uri      URI Validation
  * @task test     Unit Test Support
  * @task internal Internals
  */
 final class PhabricatorEnv extends Phobject {
 
   private static $sourceStack;
   private static $repairSource;
   private static $overrideSource;
   private static $requestBaseURI;
   private static $cache;
   private static $localeCode;
 
   /**
    * @phutil-external-symbol class PhabricatorStartup
    */
   public static function initializeWebEnvironment() {
     self::initializeCommonEnvironment();
   }
 
   public static function initializeScriptEnvironment() {
     self::initializeCommonEnvironment();
 
     // NOTE: This is dangerous in general, but we know we're in a script context
     // and are not vulnerable to CSRF.
     AphrontWriteGuard::allowDangerousUnguardedWrites(true);
 
     // There are several places where we log information (about errors, events,
     // service calls, etc.) for analysis via DarkConsole or similar. These are
     // useful for web requests, but grow unboundedly in long-running scripts and
     // daemons. Discard data as it arrives in these cases.
     PhutilServiceProfiler::getInstance()->enableDiscardMode();
     DarkConsoleErrorLogPluginAPI::enableDiscardMode();
     DarkConsoleEventPluginAPI::enableDiscardMode();
   }
 
 
   private static function initializeCommonEnvironment() {
     PhutilErrorHandler::initialize();
 
     self::buildConfigurationSourceStack();
 
     // Force a valid timezone. If both PHP and Phabricator configuration are
     // invalid, use UTC.
     $tz = self::getEnvConfig('phabricator.timezone');
     if ($tz) {
       @date_default_timezone_set($tz);
     }
     $ok = @date_default_timezone_set(date_default_timezone_get());
     if (!$ok) {
       date_default_timezone_set('UTC');
     }
 
     // Prepend '/support/bin' and append any paths to $PATH if we need to.
     $env_path = getenv('PATH');
     $phabricator_path = dirname(phutil_get_library_root('phabricator'));
     $support_path = $phabricator_path.'/support/bin';
     $env_path = $support_path.PATH_SEPARATOR.$env_path;
     $append_dirs = self::getEnvConfig('environment.append-paths');
     if (!empty($append_dirs)) {
       $append_path = implode(PATH_SEPARATOR, $append_dirs);
       $env_path = $env_path.PATH_SEPARATOR.$append_path;
     }
     putenv('PATH='.$env_path);
 
     // Write this back into $_ENV, too, so ExecFuture picks it up when creating
     // subprocess environments.
     $_ENV['PATH'] = $env_path;
 
 
     // If an instance identifier is defined, write it into the environment so
     // it's available to subprocesses.
     $instance = self::getEnvConfig('cluster.instance');
     if (strlen($instance)) {
       putenv('PHABRICATOR_INSTANCE='.$instance);
       $_ENV['PHABRICATOR_INSTANCE'] = $instance;
     }
 
     PhabricatorEventEngine::initialize();
 
     // TODO: Add a "locale.default" config option once we have some reasonable
     // defaults which aren't silly nonsense.
     self::setLocaleCode('en_US');
   }
 
   public static function beginScopedLocale($locale_code) {
     return new PhabricatorLocaleScopeGuard($locale_code);
   }
 
   public static function getLocaleCode() {
     return self::$localeCode;
   }
 
   public static function setLocaleCode($locale_code) {
     if (!$locale_code) {
       return;
     }
 
     if ($locale_code == self::$localeCode) {
       return;
     }
 
     try {
       $locale = PhutilLocale::loadLocale($locale_code);
       $translations = PhutilTranslation::getTranslationMapForLocale(
         $locale_code);
 
       $override = self::getEnvConfig('translation.override');
       if (!is_array($override)) {
         $override = array();
       }
 
       PhutilTranslator::getInstance()
         ->setLocale($locale)
         ->setTranslations($override + $translations);
 
       self::$localeCode = $locale_code;
     } catch (Exception $ex) {
       // Just ignore this; the user likely has an out-of-date locale code.
     }
   }
 
   private static function buildConfigurationSourceStack() {
     self::dropConfigCache();
 
     $stack = new PhabricatorConfigStackSource();
     self::$sourceStack = $stack;
 
     $default_source = id(new PhabricatorConfigDefaultSource())
       ->setName(pht('Global Default'));
     $stack->pushSource($default_source);
 
     $env = self::getSelectedEnvironmentName();
     if ($env) {
       $stack->pushSource(
         id(new PhabricatorConfigFileSource($env))
           ->setName(pht("File '%s'", $env)));
     }
 
     $stack->pushSource(
       id(new PhabricatorConfigLocalSource())
         ->setName(pht('Local Config')));
 
     // If the install overrides the database adapter, we might need to load
     // the database adapter class before we can push on the database config.
     // This config is locked and can't be edited from the web UI anyway.
     foreach (self::getEnvConfig('load-libraries') as $library) {
       phutil_load_library($library);
     }
 
     // If custom libraries specify config options, they won't get default
     // values as the Default source has already been loaded, so we get it to
     // pull in all options from non-phabricator libraries now they are loaded.
     $default_source->loadExternalOptions();
 
     // If this install has site config sources, load them now.
     $site_sources = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorConfigSiteSource')
       ->setSortMethod('getPriority')
       ->execute();
 
     foreach ($site_sources as $site_source) {
       $stack->pushSource($site_source);
     }
 
     try {
       $stack->pushSource(
         id(new PhabricatorConfigDatabaseSource('default'))
           ->setName(pht('Database')));
     } catch (AphrontQueryException $exception) {
       // If the database is not available, just skip this configuration
       // source. This happens during `bin/storage upgrade`, `bin/conf` before
       // schema setup, etc.
     }
   }
 
   public static function repairConfig($key, $value) {
     if (!self::$repairSource) {
       self::$repairSource = id(new PhabricatorConfigDictionarySource(array()))
         ->setName(pht('Repaired Config'));
       self::$sourceStack->pushSource(self::$repairSource);
     }
     self::$repairSource->setKeys(array($key => $value));
     self::dropConfigCache();
   }
 
   public static function overrideConfig($key, $value) {
     if (!self::$overrideSource) {
       self::$overrideSource = id(new PhabricatorConfigDictionarySource(array()))
         ->setName(pht('Overridden Config'));
       self::$sourceStack->pushSource(self::$overrideSource);
     }
     self::$overrideSource->setKeys(array($key => $value));
     self::dropConfigCache();
   }
 
   public static function getUnrepairedEnvConfig($key, $default = null) {
     foreach (self::$sourceStack->getStack() as $source) {
       if ($source === self::$repairSource) {
         continue;
       }
       $result = $source->getKeys(array($key));
       if ($result) {
         return $result[$key];
       }
     }
     return $default;
   }
 
   public static function getSelectedEnvironmentName() {
     $env_var = 'PHABRICATOR_ENV';
 
     $env = idx($_SERVER, $env_var);
 
     if (!$env) {
       $env = getenv($env_var);
     }
 
     if (!$env) {
       $env = idx($_ENV, $env_var);
     }
 
     if (!$env) {
       $root = dirname(phutil_get_library_root('phabricator'));
       $path = $root.'/conf/local/ENVIRONMENT';
       if (Filesystem::pathExists($path)) {
         $env = trim(Filesystem::readFile($path));
       }
     }
 
     return $env;
   }
 
 
 /* -(  Reading Configuration  )---------------------------------------------- */
 
 
   /**
    * Get the current configuration setting for a given key.
    *
    * If the key is not found, then throw an Exception.
    *
    * @task read
    */
   public static function getEnvConfig($key) {
     if (isset(self::$cache[$key])) {
       return self::$cache[$key];
     }
 
     if (array_key_exists($key, self::$cache)) {
       return self::$cache[$key];
     }
 
     $result = self::$sourceStack->getKeys(array($key));
     if (array_key_exists($key, $result)) {
       self::$cache[$key] = $result[$key];
       return $result[$key];
     } else {
       throw new Exception(
         pht(
           "No config value specified for key '%s'.",
           $key));
     }
   }
 
 
   /**
    * Get the current configuration setting for a given key. If the key
    * does not exist, return a default value instead of throwing. This is
    * primarily useful for migrations involving keys which are slated for
    * removal.
    *
    * @task read
    */
   public static function getEnvConfigIfExists($key, $default = null) {
     try {
       return self::getEnvConfig($key);
     } catch (Exception $ex) {
       return $default;
     }
   }
 
 
   /**
    * Get the fully-qualified URI for a path.
    *
    * @task read
    */
   public static function getURI($path) {
     return rtrim(self::getAnyBaseURI(), '/').$path;
   }
 
 
   /**
    * Get the fully-qualified production URI for a path.
    *
    * @task read
    */
   public static function getProductionURI($path) {
     // If we're passed a URI which already has a domain, simply return it
     // unmodified. In particular, files may have URIs which point to a CDN
     // domain.
     $uri = new PhutilURI($path);
     if ($uri->getDomain()) {
       return $path;
     }
 
     $production_domain = self::getEnvConfig('phabricator.production-uri');
     if (!$production_domain) {
       $production_domain = self::getAnyBaseURI();
     }
     return rtrim($production_domain, '/').$path;
   }
 
   public static function getAllowedURIs($path) {
     $uri = new PhutilURI($path);
     if ($uri->getDomain()) {
       return $path;
     }
 
     $allowed_uris = self::getEnvConfig('phabricator.allowed-uris');
     $return = array();
     foreach ($allowed_uris as $allowed_uri) {
       $return[] = rtrim($allowed_uri, '/').$path;
     }
 
     return $return;
   }
 
 
   /**
    * Get the fully-qualified production URI for a static resource path.
    *
    * @task read
    */
   public static function getCDNURI($path) {
     $alt = self::getEnvConfig('security.alternate-file-domain');
     if (!$alt) {
       $alt = self::getAnyBaseURI();
     }
     $uri = new PhutilURI($alt);
     $uri->setPath($path);
     return (string)$uri;
   }
 
 
   /**
    * Get the fully-qualified production URI for a documentation resource.
    *
    * @task read
    */
   public static function getDoclink($resource, $type = 'article') {
     $uri = new PhutilURI('https://secure.phabricator.com/diviner/find/');
     $uri->setQueryParam('name', $resource);
     $uri->setQueryParam('type', $type);
     $uri->setQueryParam('jump', true);
     return (string)$uri;
   }
 
 
   /**
    * Build a concrete object from a configuration key.
    *
    * @task read
    */
   public static function newObjectFromConfig($key, $args = array()) {
     $class = self::getEnvConfig($key);
     return newv($class, $args);
   }
 
   public static function getAnyBaseURI() {
     $base_uri = self::getEnvConfig('phabricator.base-uri');
 
     if (!$base_uri) {
       $base_uri = self::getRequestBaseURI();
     }
 
     if (!$base_uri) {
       throw new Exception(
         pht(
           "Define '%s' in your configuration to continue.",
           'phabricator.base-uri'));
     }
 
     return $base_uri;
   }
 
   public static function getRequestBaseURI() {
     return self::$requestBaseURI;
   }
 
   public static function setRequestBaseURI($uri) {
     self::$requestBaseURI = $uri;
   }
 
 /* -(  Unit Test Support  )-------------------------------------------------- */
 
 
   /**
    * @task test
    */
   public static function beginScopedEnv() {
     return new PhabricatorScopedEnv(self::pushTestEnvironment());
   }
 
 
   /**
    * @task test
    */
   private static function pushTestEnvironment() {
     self::dropConfigCache();
     $source = new PhabricatorConfigDictionarySource(array());
     self::$sourceStack->pushSource($source);
     return spl_object_hash($source);
   }
 
 
   /**
    * @task test
    */
   public static function popTestEnvironment($key) {
     self::dropConfigCache();
     $source = self::$sourceStack->popSource();
     $stack_key = spl_object_hash($source);
     if ($stack_key !== $key) {
       self::$sourceStack->pushSource($source);
       throw new Exception(
         pht(
           'Scoped environments were destroyed in a different order than they '.
           'were initialized.'));
     }
   }
 
 
 /* -(  URI Validation  )----------------------------------------------------- */
 
 
   /**
    * Detect if a URI satisfies either @{method:isValidLocalURIForLink} or
    * @{method:isValidRemoteURIForLink}, i.e. is a page on this server or the
    * URI of some other resource which has a valid protocol. This rejects
    * garbage URIs and URIs with protocols which do not appear in the
    * `uri.allowed-protocols` configuration, notably 'javascript:' URIs.
    *
    * NOTE: This method is generally intended to reject URIs which it may be
    * unsafe to put in an "href" link attribute.
    *
    * @param string URI to test.
    * @return bool True if the URI identifies a web resource.
    * @task uri
    */
   public static function isValidURIForLink($uri) {
     return self::isValidLocalURIForLink($uri) ||
            self::isValidRemoteURIForLink($uri);
   }
 
 
   /**
    * Detect if a URI identifies some page on this server.
    *
    * NOTE: This method is generally intended to reject URIs which it may be
    * unsafe to issue a "Location:" redirect to.
    *
    * @param string URI to test.
    * @return bool True if the URI identifies a local page.
    * @task uri
    */
   public static function isValidLocalURIForLink($uri) {
     $uri = (string)$uri;
 
     if (!strlen($uri)) {
       return false;
     }
 
     if (preg_match('/\s/', $uri)) {
       // PHP hasn't been vulnerable to header injection attacks for a bunch of
       // years, but we can safely reject these anyway since they're never valid.
       return false;
     }
 
     // Chrome (at a minimum) interprets backslashes in Location headers and the
     // URL bar as forward slashes. This is probably intended to reduce user
     // error caused by confusion over which key is "forward slash" vs "back
     // slash".
     //
     // However, it means a URI like "/\evil.com" is interpreted like
     // "//evil.com", which is a protocol relative remote URI.
     //
     // Since we currently never generate URIs with backslashes in them, reject
     // these unconditionally rather than trying to figure out how browsers will
     // interpret them.
     if (preg_match('/\\\\/', $uri)) {
       return false;
     }
 
     // Valid URIs must begin with '/', followed by the end of the string or some
     // other non-'/' character. This rejects protocol-relative URIs like
     // "//evil.com/evil_stuff/".
     return (bool)preg_match('@^/([^/]|$)@', $uri);
   }
 
 
   /**
    * Detect if a URI identifies some valid linkable remote resource.
    *
    * @param string URI to test.
    * @return bool True if a URI idenfies a remote resource with an allowed
    *              protocol.
    * @task uri
    */
   public static function isValidRemoteURIForLink($uri) {
     try {
       self::requireValidRemoteURIForLink($uri);
       return true;
     } catch (Exception $ex) {
       return false;
     }
   }
 
 
   /**
    * Detect if a URI identifies a valid linkable remote resource, throwing a
    * detailed message if it does not.
    *
    * A valid linkable remote resource can be safely linked or redirected to.
    * This is primarily a protocol whitelist check.
    *
    * @param string URI to test.
    * @return void
    * @task uri
    */
-  public static function requireValidRemoteURIForLink($uri) {
-    $uri = new PhutilURI($uri);
+  public static function requireValidRemoteURIForLink($raw_uri) {
+    $uri = new PhutilURI($raw_uri);
 
     $proto = $uri->getProtocol();
     if (!strlen($proto)) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid linkable resource. A valid linkable '.
           'resource URI must specify a protocol.',
-          $uri));
+          $raw_uri));
     }
 
     $protocols = self::getEnvConfig('uri.allowed-protocols');
     if (!isset($protocols[$proto])) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid linkable resource. A valid linkable '.
           'resource URI must use one of these protocols: %s.',
-          $uri,
+          $raw_uri,
           implode(', ', array_keys($protocols))));
     }
 
     $domain = $uri->getDomain();
     if (!strlen($domain)) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid linkable resource. A valid linkable '.
           'resource URI must specify a domain.',
-          $uri));
+          $raw_uri));
     }
   }
 
 
   /**
    * Detect if a URI identifies a valid fetchable remote resource.
    *
    * @param string URI to test.
    * @param list<string> Allowed protocols.
    * @return bool True if the URI is a valid fetchable remote resource.
    * @task uri
    */
   public static function isValidRemoteURIForFetch($uri, array $protocols) {
     try {
       self::requireValidRemoteURIForFetch($uri, $protocols);
       return true;
     } catch (Exception $ex) {
       return false;
     }
   }
 
 
   /**
    * Detect if a URI identifies a valid fetchable remote resource, throwing
    * a detailed message if it does not.
    *
    * A valid fetchable remote resource can be safely fetched using a request
    * originating on this server. This is a primarily an address check against
    * the outbound address blacklist.
    *
    * @param string URI to test.
    * @param list<string> Allowed protocols.
    * @return pair<string, string> Pre-resolved URI and domain.
    * @task uri
    */
   public static function requireValidRemoteURIForFetch(
     $uri,
     array $protocols) {
 
     $uri = new PhutilURI($uri);
 
     $proto = $uri->getProtocol();
     if (!strlen($proto)) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid fetchable resource. A valid fetchable '.
           'resource URI must specify a protocol.',
           $uri));
     }
 
     $protocols = array_fuse($protocols);
     if (!isset($protocols[$proto])) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid fetchable resource. A valid fetchable '.
           'resource URI must use one of these protocols: %s.',
           $uri,
           implode(', ', array_keys($protocols))));
     }
 
     $domain = $uri->getDomain();
     if (!strlen($domain)) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid fetchable resource. A valid fetchable '.
           'resource URI must specify a domain.',
           $uri));
     }
 
     $addresses = gethostbynamel($domain);
     if (!$addresses) {
       throw new Exception(
         pht(
           'URI "%s" is not a valid fetchable resource. The domain "%s" could '.
           'not be resolved.',
           $uri,
           $domain));
     }
 
     foreach ($addresses as $address) {
       if (self::isBlacklistedOutboundAddress($address)) {
         throw new Exception(
           pht(
             'URI "%s" is not a valid fetchable resource. The domain "%s" '.
             'resolves to the address "%s", which is blacklisted for '.
             'outbound requests.',
             $uri,
             $domain,
             $address));
       }
     }
 
     $resolved_uri = clone $uri;
     $resolved_uri->setDomain(head($addresses));
 
     return array($resolved_uri, $domain);
   }
 
 
   /**
    * Determine if an IP address is in the outbound address blacklist.
    *
    * @param string IP address.
    * @return bool True if the address is blacklisted.
    */
   public static function isBlacklistedOutboundAddress($address) {
     $blacklist = self::getEnvConfig('security.outbound-blacklist');
 
     return PhutilCIDRList::newList($blacklist)->containsAddress($address);
   }
 
   public static function isClusterRemoteAddress() {
     $address = idx($_SERVER, 'REMOTE_ADDR');
     if (!$address) {
       throw new Exception(
         pht(
           'Unable to test remote address against cluster whitelist: '.
           'REMOTE_ADDR is not defined.'));
     }
 
     return self::isClusterAddress($address);
   }
 
   public static function isClusterAddress($address) {
     $cluster_addresses = self::getEnvConfig('cluster.addresses');
     if (!$cluster_addresses) {
       throw new Exception(
         pht(
           'Phabricator is not configured to serve cluster requests. '.
           'Set `cluster.addresses` in the configuration to whitelist '.
           'cluster hosts before sending requests that use a cluster '.
           'authentication mechanism.'));
     }
 
     return PhutilCIDRList::newList($cluster_addresses)
       ->containsAddress($address);
   }
 
 /* -(  Internals  )---------------------------------------------------------- */
 
 
   /**
    * @task internal
    */
   public static function envConfigExists($key) {
     return array_key_exists($key, self::$sourceStack->getKeys(array($key)));
   }
 
 
   /**
    * @task internal
    */
   public static function getAllConfigKeys() {
     return self::$sourceStack->getAllKeys();
   }
 
   public static function getConfigSourceStack() {
     return self::$sourceStack;
   }
 
   /**
    * @task internal
    */
   public static function overrideTestEnvConfig($stack_key, $key, $value) {
     $tmp = array();
 
     // If we don't have the right key, we'll throw when popping the last
     // source off the stack.
     do {
       $source = self::$sourceStack->popSource();
       array_unshift($tmp, $source);
       if (spl_object_hash($source) == $stack_key) {
         $source->setKeys(array($key => $value));
         break;
       }
     } while (true);
 
     foreach ($tmp as $source) {
       self::$sourceStack->pushSource($source);
     }
 
     self::dropConfigCache();
   }
 
   private static function dropConfigCache() {
     self::$cache = array();
   }
 
 }
diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index 25dec8eef..38420748d 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -1,371 +1,374 @@
 <?php
 
 final class AphrontFormDateControl extends AphrontFormControl {
 
   private $initialTime;
   private $zone;
 
   private $valueDate;
   private $valueTime;
   private $allowNull;
   private $continueOnInvalidDate = false;
   private $isTimeDisabled;
   private $isDisabled;
   private $endDateID;
 
   public function setAllowNull($allow_null) {
     $this->allowNull = $allow_null;
     return $this;
   }
 
   public function setIsTimeDisabled($is_disabled) {
     $this->isTimeDisabled = $is_disabled;
     return $this;
   }
 
   public function setIsDisabled($is_datepicker_disabled) {
     $this->isDisabled = $is_datepicker_disabled;
     return $this;
   }
 
   public function setEndDateID($value) {
     $this->endDateID = $value;
     return $this;
   }
 
   const TIME_START_OF_DAY         = 'start-of-day';
   const TIME_END_OF_DAY           = 'end-of-day';
   const TIME_START_OF_BUSINESS    = 'start-of-business';
   const TIME_END_OF_BUSINESS      = 'end-of-business';
 
   public function setInitialTime($time) {
     $this->initialTime = $time;
     return $this;
   }
 
   public function readValueFromRequest(AphrontRequest $request) {
     $date = $request->getStr($this->getDateInputName());
     $time = $request->getStr($this->getTimeInputName());
     $enabled = $request->getBool($this->getCheckboxInputName());
 
     if ($this->allowNull && !$enabled) {
       $this->setError(null);
       $this->setValue(null);
       return;
     }
 
     $err = $this->getError();
 
     if ($date || $time) {
       $this->valueDate = $date;
       $this->valueTime = $time;
 
       // Assume invalid.
       $err = pht('Invalid');
 
       $zone = $this->getTimezone();
 
       try {
         $datetime = new DateTime("{$date} {$time}", $zone);
         $value = $datetime->format('U');
       } catch (Exception $ex) {
         $value = null;
       }
 
       if ($value) {
         $this->setValue($value);
         $err = null;
       } else {
         $this->setValue(null);
       }
     } else {
       $value = $this->getInitialValue();
       if ($value) {
         $this->setValue($value);
       } else {
         $this->setValue(null);
       }
     }
 
     $this->setError($err);
 
     return $this->getValue();
   }
 
   protected function getCustomControlClass() {
     return 'aphront-form-control-date';
   }
 
   public function setValue($epoch) {
     if ($epoch instanceof AphrontFormDateControlValue) {
       $this->continueOnInvalidDate = true;
       $this->valueDate = $epoch->getValueDate();
       $this->valueTime  = $epoch->getValueTime();
       $this->allowNull = $epoch->getOptional();
       $this->isDisabled = $epoch->isDisabled();
 
       return parent::setValue($epoch->getEpoch());
     }
 
     $result = parent::setValue($epoch);
 
     if ($epoch === null) {
       return $result;
     }
 
     $readable = $this->formatTime($epoch, 'Y!m!d!g:i A');
     $readable = explode('!', $readable, 4);
 
     $year  = $readable[0];
     $month = $readable[1];
     $day   = $readable[2];
 
     $this->valueDate = $month.'/'.$day.'/'.$year;
     $this->valueTime  = $readable[3];
 
     return $result;
   }
 
   private function getDateInputValue() {
     $date_format = $this->getDateFormat();
     $timezone = $this->getTimezone();
 
-    $datetime = new DateTime($this->valueDate, $timezone);
-    $date = $datetime->format($date_format);
+    try {
+      $datetime = new DateTime($this->valueDate, $timezone);
+    } catch (Exception $ex) {
+      return $this->valueDate;
+    }
 
-    return $date;
+    return $datetime->format($date_format);
   }
 
   private function getTimeFormat() {
     return $this->getViewer()
       ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
   }
 
   private function getDateFormat() {
     return $this->getViewer()
       ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT);
   }
 
   private function getTimeInputValue() {
     return $this->valueTime;
   }
 
   private function formatTime($epoch, $fmt) {
     return phabricator_format_local_time(
       $epoch,
       $this->getViewer(),
       $fmt);
   }
 
   private function getDateInputName() {
     return $this->getName().'_d';
   }
 
   private function getTimeInputName() {
     return $this->getName().'_t';
   }
 
   private function getCheckboxInputName() {
     return $this->getName().'_e';
   }
 
   protected function renderInput() {
 
     $disabled = null;
     if ($this->getValue() === null && !$this->continueOnInvalidDate) {
       $this->setValue($this->getInitialValue());
       if ($this->allowNull) {
         $disabled = 'disabled';
       }
     }
 
     if ($this->isDisabled) {
       $disabled = 'disabled';
     }
 
     $checkbox = null;
     if ($this->allowNull) {
       $checkbox = javelin_tag(
         'input',
         array(
           'type' => 'checkbox',
           'name' => $this->getCheckboxInputName(),
           'sigil' => 'calendar-enable',
           'class' => 'aphront-form-date-enabled-input',
           'value' => 1,
           'checked' => ($disabled === null ? 'checked' : null),
         ));
     }
 
     $date_sel = javelin_tag(
       'input',
       array(
         'autocomplete' => 'off',
         'name'  => $this->getDateInputName(),
         'sigil' => 'date-input',
         'value' => $this->getDateInputValue(),
         'type'  => 'text',
         'class' => 'aphront-form-date-input',
       ),
       '');
 
     $date_div = javelin_tag(
       'div',
       array(
         'class' => 'aphront-form-date-input-container',
       ),
       $date_sel);
 
     $cicon = id(new PHUIIconView())
       ->setIcon('fa-calendar');
 
     $cal_icon = javelin_tag(
       'a',
       array(
         'href'  => '#',
         'class' => 'calendar-button',
         'sigil' => 'calendar-button',
       ),
       $cicon);
 
     $values = $this->getTimeTypeaheadValues();
 
     $time_id = celerity_generate_unique_node_id();
     Javelin::initBehavior('time-typeahead', array(
       'startTimeID' => $time_id,
       'endTimeID' => $this->endDateID,
       'timeValues' => $values,
       'format' => $this->getTimeFormat(),
       ));
 
 
     $time_sel = javelin_tag(
       'input',
       array(
         'autocomplete' => 'off',
         'name'  => $this->getTimeInputName(),
         'sigil' => 'time-input',
         'value' => $this->getTimeInputValue(),
         'type'  => 'text',
         'class' => 'aphront-form-time-input',
       ),
       '');
 
     $time_div = javelin_tag(
       'div',
       array(
         'id' => $time_id,
         'class' => 'aphront-form-time-input-container',
       ),
       $time_sel);
 
     $preferences = $this->getViewer()->loadPreferences();
     $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
     $week_start = $preferences->getPreference($pref_week_start, 0);
 
     Javelin::initBehavior('fancy-datepicker', array(
       'format' => $this->getDateFormat(),
       'weekStart' => $week_start,
       ));
 
     $classes = array();
     $classes[] = 'aphront-form-date-container';
     if ($disabled) {
       $classes[] = 'datepicker-disabled';
     }
     if ($this->isTimeDisabled) {
       $classes[] = 'no-time';
     }
 
     return javelin_tag(
       'div',
       array(
         'class' => implode(' ', $classes),
         'sigil' => 'phabricator-date-control',
         'meta'  => array(
           'disabled' => (bool)$disabled,
         ),
         'id' => $this->getID(),
       ),
       array(
         $checkbox,
         $date_div,
         $cal_icon,
         $time_div,
       ));
   }
 
   private function getTimezone() {
     if ($this->zone) {
       return $this->zone;
     }
 
     $viewer = $this->getViewer();
 
     $user_zone = $viewer->getTimezoneIdentifier();
     $this->zone = new DateTimeZone($user_zone);
     return $this->zone;
   }
 
   private function getInitialValue() {
     $zone = $this->getTimezone();
 
     // TODO: We could eventually allow these to be customized per install or
     // per user or both, but let's wait and see.
     switch ($this->initialTime) {
       case self::TIME_START_OF_DAY:
       default:
         $time = '12:00 AM';
         break;
       case self::TIME_START_OF_BUSINESS:
         $time = '9:00 AM';
         break;
       case self::TIME_END_OF_BUSINESS:
         $time = '5:00 PM';
         break;
       case self::TIME_END_OF_DAY:
         $time = '11:59 PM';
         break;
     }
 
     $today = $this->formatTime(time(), 'Y-m-d');
     try {
       $date = new DateTime("{$today} {$time}", $zone);
       $value = $date->format('U');
     } catch (Exception $ex) {
       $value = null;
     }
 
     return $value;
   }
 
   private function getTimeTypeaheadValues() {
     $time_format = $this->getTimeFormat();
     $times = array();
 
     if ($time_format == 'g:i A') {
       $am_pm_list = array('AM', 'PM');
 
       foreach ($am_pm_list as $am_pm) {
         for ($hour = 0; $hour < 12; $hour++) {
           $actual_hour = ($hour == 0) ? 12 : $hour;
           $times[] = $actual_hour.':00 '.$am_pm;
           $times[] = $actual_hour.':30 '.$am_pm;
         }
       }
     } else if ($time_format == 'H:i') {
       for ($hour = 0; $hour < 24; $hour++) {
         $written_hour = ($hour > 9) ? $hour : '0'.$hour;
         $times[] = $written_hour.':00';
         $times[] = $written_hour.':30';
       }
     }
 
     foreach ($times as $key => $time) {
       $times[$key] = array($key, $time);
     }
 
     return $times;
   }
 
 }
diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php
index f533bcadd..114340006 100644
--- a/src/view/form/control/AphrontFormDateControlValue.php
+++ b/src/view/form/control/AphrontFormDateControlValue.php
@@ -1,287 +1,347 @@
 <?php
 
 final class AphrontFormDateControlValue extends Phobject {
 
   private $valueDate;
   private $valueTime;
   private $valueEnabled;
 
   private $viewer;
   private $zone;
   private $optional;
 
   public function getValueDate() {
     return $this->valueDate;
   }
 
   public function getValueTime() {
     return $this->valueTime;
   }
 
   public function isValid() {
     if ($this->isDisabled()) {
       return true;
     }
     return ($this->getEpoch() !== null);
   }
 
   public function isEmpty() {
     if ($this->valueDate) {
       return false;
     }
 
     if ($this->valueTime) {
       return false;
     }
 
     return true;
   }
 
   public function isDisabled() {
     return ($this->optional && !$this->valueEnabled);
   }
 
   public function setEnabled($enabled) {
     $this->valueEnabled = $enabled;
     return $this;
   }
 
   public function setOptional($optional) {
     $this->optional = $optional;
     return $this;
   }
 
   public function getOptional() {
     return $this->optional;
   }
 
   public function getViewer() {
     return $this->viewer;
   }
 
   public static function newFromParts(
     PhabricatorUser $viewer,
     $year,
     $month,
     $day,
     $time = null,
     $enabled = true) {
 
     $value = new AphrontFormDateControlValue();
     $value->viewer = $viewer;
     list($value->valueDate, $value->valueTime) =
       $value->getFormattedDateFromParts(
         $year,
         $month,
         $day,
         coalesce($time, '12:00 AM'));
     $value->valueEnabled = $enabled;
 
     return $value;
   }
 
   public static function newFromRequest(AphrontRequest $request, $key) {
     $value = new AphrontFormDateControlValue();
     $value->viewer = $request->getViewer();
 
-    list($value->valueDate, $value->valueTime) =
-      $value->getFormattedDateFromDate(
-        $request->getStr($key.'_d'),
-        $request->getStr($key.'_t'));
+    $datetime = $request->getStr($key);
+    if (strlen($datetime)) {
+      $date = $datetime;
+      $time = null;
+    } else {
+      $date = $request->getStr($key.'_d');
+      $time = $request->getStr($key.'_t');
+    }
+
+    // If this looks like an epoch timestamp, prefix it with "@" so that
+    // DateTime() reads it as one. Assume small numbers are a "Ymd" digit
+    // string instead of an epoch timestamp for a time in 1970.
+    if (ctype_digit($date) && ($date > 30000000)) {
+      $date = '@'.$date;
+      $time = null;
+    }
+
+    $value->valueDate = $date;
+    $value->valueTime = $time;
+
+    $formatted = $value->getFormattedDateFromDate(
+      $value->valueDate,
+      $value->valueTime);
+
+    if ($formatted) {
+      list($value->valueDate, $value->valueTime) = $formatted;
+    }
 
     $value->valueEnabled = $request->getStr($key.'_e');
     return $value;
   }
 
   public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {
     $value = new AphrontFormDateControlValue();
     $value->viewer = $viewer;
+
+    if (!$epoch) {
+      return $value;
+    }
+
     $readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
     $readable = explode('!', $readable, 4);
 
     $year  = $readable[0];
     $month = $readable[1];
     $day   = $readable[2];
     $time  = $readable[3];
 
     list($value->valueDate, $value->valueTime) =
       $value->getFormattedDateFromParts(
         $year,
         $month,
         $day,
         $time);
 
     return $value;
   }
 
   public static function newFromDictionary(
     PhabricatorUser $viewer,
     array $dictionary) {
     $value = new AphrontFormDateControlValue();
     $value->viewer = $viewer;
 
-    list($value->valueDate, $value->valueTime) =
-      $value->getFormattedDateFromDate(
-        idx($dictionary, 'd'),
-        idx($dictionary, 't'));
+    $value->valueDate = idx($dictionary, 'd');
+    $value->valueTime = idx($dictionary, 't');
+
+    $formatted = $value->getFormattedDateFromDate(
+      $value->valueDate,
+      $value->valueTime);
+
+    if ($formatted) {
+      list($value->valueDate, $value->valueTime) = $formatted;
+    }
 
     $value->valueEnabled = idx($dictionary, 'e');
 
     return $value;
   }
 
   public static function newFromWild(PhabricatorUser $viewer, $wild) {
     if (is_array($wild)) {
       return self::newFromDictionary($viewer, $wild);
     } else if (is_numeric($wild)) {
       return self::newFromEpoch($viewer, $wild);
     } else {
       throw new Exception(
         pht(
           'Unable to construct a date value from value of type "%s".',
           gettype($wild)));
     }
   }
 
   public function getDictionary() {
     return array(
       'd' => $this->valueDate,
       't' => $this->valueTime,
       'e' => $this->valueEnabled,
     );
   }
 
   public function getValueAsFormat($format) {
     return phabricator_format_local_time(
       $this->getEpoch(),
       $this->viewer,
       $format);
   }
 
   private function formatTime($epoch, $format) {
     return phabricator_format_local_time(
       $epoch,
       $this->viewer,
       $format);
   }
 
   public function getEpoch() {
     if ($this->isDisabled()) {
       return null;
     }
 
-    $date = $this->valueDate;
-    $time = $this->valueTime;
-    $zone = $this->getTimezone();
-
-    if (!strlen($time)) {
+    $datetime = $this->newDateTime($this->valueDate, $this->valueTime);
+    if (!$datetime) {
       return null;
     }
 
-    $colloquial = array(
-      'elevenses' => '11:00 AM',
-      'morning tea' => '11:00 AM',
-      'noon' => '12:00 PM',
-      'high noon' => '12:00 PM',
-      'lunch' => '12:00 PM',
-      'tea time' => '3:00 PM',
-      'witching hour' => '12:00 AM',
-      'midnight' => '12:00 AM',
-    );
-
-    $normalized = phutil_utf8_strtolower($time);
-    if (isset($colloquial[$normalized])) {
-      $time = $colloquial[$normalized];
-    }
-
-    try {
-      $datetime = new DateTime("{$date} {$time}", $zone);
-      $value = $datetime->format('U');
-    } catch (Exception $ex) {
-      $value = null;
-    }
-    return $value;
+    return $datetime->format('U');
   }
 
   private function getTimeFormat() {
     return $this->getViewer()
       ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
   }
 
   private function getDateFormat() {
     return $this->getViewer()
       ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT);
   }
 
   private function getFormattedDateFromDate($date, $time) {
-    $original_input = $date;
-    $zone = $this->getTimezone();
-    $separator = $this->getFormatSeparator();
-    $parts = preg_split('@[,./:-]@', $date);
-    $date = implode($separator, $parts);
-    $date = id(new DateTime($date, $zone));
-
-    if ($date) {
-      $date = $date->format($this->getDateFormat());
-    } else {
-      $date = $original_input;
+    $datetime = $this->newDateTime($date, $time);
+    if (!$datetime) {
+      return null;
     }
 
-    $date = id(new DateTime("{$date} {$time}", $zone));
-
     return array(
-      $date->format($this->getDateFormat()),
-      $date->format($this->getTimeFormat()),
+      $datetime->format($this->getDateFormat()),
+      $datetime->format($this->getTimeFormat()),
     );
+
+    return array($date, $time);
+  }
+
+  private function newDateTime($date, $time) {
+    $date = $this->getStandardDateFormat($date);
+    $time = $this->getStandardTimeFormat($time);
+    try {
+      $datetime = new DateTime("{$date} {$time}");
+    } catch (Exception $ex) {
+      return null;
+    }
+
+    // Set the timezone explicitly because it is ignored in the constructor
+    // if the date is an epoch timestamp.
+    $zone = $this->getTimezone();
+    $datetime->setTimezone($zone);
+
+    return $datetime;
   }
 
   private function getFormattedDateFromParts(
     $year,
     $month,
     $day,
     $time) {
 
     $zone = $this->getTimezone();
     $date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));
 
     return array(
       $date_time->format($this->getDateFormat()),
       $date_time->format($this->getTimeFormat()),
     );
   }
 
   private function getFormatSeparator() {
     $format = $this->getDateFormat();
     switch ($format) {
       case 'n/j/Y':
         return '/';
       default:
         return '-';
     }
   }
 
   public function getDateTime() {
-    $epoch = $this->getEpoch();
-    $date = null;
-
-    if ($epoch) {
-      $zone = $this->getTimezone();
-      $date = new DateTime('@'.$epoch);
-      $date->setTimeZone($zone);
-    }
-
-    return $date;
+    return $this->newDateTime();
   }
 
   private function getTimezone() {
     if ($this->zone) {
       return $this->zone;
     }
 
     $viewer_zone = $this->viewer->getTimezoneIdentifier();
     $this->zone = new DateTimeZone($viewer_zone);
     return $this->zone;
   }
 
+  private function getStandardDateFormat($date) {
+    $colloquial = array(
+      'newyear' => 'January 1',
+      'valentine' => 'February 14',
+      'pi' => 'March 14',
+      'christma' => 'December 25',
+    );
+
+    // Lowercase the input, then remove punctuation, a "day" suffix, and an
+    // "s" if one is present. This allows all of these to match. This allows
+    // variations like "New Year's Day" and "New Year" to both match.
+    $normalized = phutil_utf8_strtolower($date);
+    $normalized = preg_replace('/[^a-z]/', '', $normalized);
+    $normalized = preg_replace('/day\z/', '', $normalized);
+    $normalized = preg_replace('/s\z/', '', $normalized);
+
+    if (isset($colloquial[$normalized])) {
+      return $colloquial[$normalized];
+    }
+
+    $separator = $this->getFormatSeparator();
+    $parts = preg_split('@[,./:-]@', $date);
+    return implode($separator, $parts);
+  }
+
+  private function getStandardTimeFormat($time) {
+    $colloquial = array(
+      'crack of dawn' => '5:00 AM',
+      'dawn' => '6:00 AM',
+      'early' => '7:00 AM',
+      'morning' => '8:00 AM',
+      'elevenses' => '11:00 AM',
+      'morning tea' => '11:00 AM',
+      'noon' => '12:00 PM',
+      'high noon' => '12:00 PM',
+      'lunch' => '12:00 PM',
+      'afternoon' => '2:00 PM',
+      'tea time' => '3:00 PM',
+      'evening' => '7:00 PM',
+      'late' => '11:00 PM',
+      'witching hour' => '12:00 AM',
+      'midnight' => '12:00 AM',
+    );
+
+    $normalized = phutil_utf8_strtolower($time);
+    if (isset($colloquial[$normalized])) {
+      $time = $colloquial[$normalized];
+    }
+
+    return $time;
+  }
 
 }
diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php
index d17454755..819e72630 100644
--- a/src/view/phui/PHUITwoColumnView.php
+++ b/src/view/phui/PHUITwoColumnView.php
@@ -1,192 +1,209 @@
 <?php
 
 final class PHUITwoColumnView extends AphrontTagView {
 
   private $mainColumn;
   private $sideColumn = null;
+  private $navigation;
   private $display;
   private $fluid;
   private $header;
   private $subheader;
   private $footer;
   private $propertySection = array();
   private $curtain;
 
   const DISPLAY_LEFT = 'phui-side-column-left';
   const DISPLAY_RIGHT = 'phui-side-column-right';
 
   public function setMainColumn($main) {
     $this->mainColumn = $main;
     return $this;
   }
 
   public function setSideColumn($side) {
     $this->sideColumn = $side;
     return $this;
   }
 
+  public function setNavigation($nav) {
+    $this->navigation = $nav;
+    $this->display = self::DISPLAY_LEFT;
+    return $this;
+  }
+
   public function setHeader(PHUIHeaderView $header) {
     $this->header = $header;
     return $this;
   }
 
   public function setSubheader($subheader) {
     $this->subheader = $subheader;
     return $this;
   }
 
   public function setFooter($footer) {
     $this->footer = $footer;
     return $this;
   }
 
   public function addPropertySection($title, $section) {
     $this->propertySection[] = array($title, $section);
     return $this;
   }
 
   public function setCurtain(PHUICurtainView $curtain) {
     $this->curtain = $curtain;
     return $this;
   }
 
   public function getCurtain() {
     return $this->curtain;
   }
 
   public function setFluid($fluid) {
     $this->fluid = $fluid;
     return $this;
   }
 
   public function setDisplay($display) {
     $this->display = $display;
     return $this;
   }
 
   private function getDisplay() {
     if ($this->display) {
       return $this->display;
     } else {
       return self::DISPLAY_RIGHT;
     }
   }
 
   protected function getTagAttributes() {
     $classes = array();
     $classes[] = 'phui-two-column-view';
     $classes[] = $this->getDisplay();
 
     if ($this->fluid) {
       $classes[] = 'phui-two-column-fluid';
     }
 
     if ($this->subheader) {
       $classes[] = 'with-subheader';
     }
 
     return array(
       'class' => implode(' ', $classes),
     );
   }
 
   protected function getTagContent() {
     require_celerity_resource('phui-two-column-view-css');
 
     $main = $this->buildMainColumn();
     $side = $this->buildSideColumn();
     $footer = $this->buildFooter();
 
     $order = array($side, $main);
 
     $inner = phutil_tag_div('phui-two-column-row grouped', $order);
     $table = phutil_tag_div('phui-two-column-content', $inner);
 
     $header = null;
     if ($this->header) {
       $curtain = $this->getCurtain();
       if ($curtain) {
         $action_list = $curtain->getActionList();
         $this->header->setActionList($action_list);
       }
 
       $header = phutil_tag_div(
         'phui-two-column-header', $this->header);
     }
 
     $subheader = null;
     if ($this->subheader) {
       $subheader = phutil_tag_div(
         'phui-two-column-subheader', $this->subheader);
     }
 
     return phutil_tag(
       'div',
       array(
         'class' => 'phui-two-column-container',
       ),
       array(
         $header,
         $subheader,
         $table,
         $footer,
       ));
   }
 
   private function buildMainColumn() {
 
     $view = array();
     $sections = $this->propertySection;
 
     if ($sections) {
       foreach ($sections as $content) {
         if ($content[1]) {
           $view[] = id(new PHUIObjectBoxView())
             ->setHeaderText($content[0])
             ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
             ->appendChild($content[1]);
         }
       }
     }
 
     return phutil_tag(
       'div',
       array(
         'class' => 'phui-main-column',
       ),
       array(
         $view,
         $this->mainColumn,
       ));
   }
 
   private function buildSideColumn() {
 
+    $classes = array();
+    $classes[] = 'phui-side-column';
+    $navigation = null;
+    if ($this->navigation) {
+      $classes[] = 'side-has-nav';
+      $navigation = id(new PHUIObjectBoxView())
+        ->appendChild($this->navigation);
+    }
+
     $curtain = $this->getCurtain();
 
     return phutil_tag(
       'div',
       array(
-        'class' => 'phui-side-column',
+        'class' => implode($classes, ' '),
       ),
       array(
+        $navigation,
         $curtain,
         $this->sideColumn,
       ));
   }
 
   private function buildFooter() {
 
     $footer = $this->footer;
 
     return phutil_tag(
       'div',
       array(
         'class' => 'phui-two-column-content phui-two-column-footer',
       ),
       array(
         $footer,
       ));
 
   }
 }
diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css
index b2efc8c3c..af304c930 100644
--- a/webroot/rsrc/css/aphront/table-view.css
+++ b/webroot/rsrc/css/aphront/table-view.css
@@ -1,310 +1,309 @@
 /**
  * @provides aphront-table-view-css
  */
 
-.device-phone .aphront-table-wrap,
-.device-tablet .aphront-table-wrap {
+.aphront-table-wrap {
   overflow-x: auto;
 }
 
 .aphront-table-view {
   width: 100%;
   border-collapse:  collapse;
   background: #fff;
   border: 1px solid {$lightblueborder};
   border-bottom: 1px solid {$blueborder};
 }
 
 .aphront-table-view-fixed {
   table-layout: fixed;
 }
 
 .aphront-table-view-fixed th {
   box-sizing: border-box;
 }
 
 .aphront-table-notice {
   padding: 12px 16px;
   font-size: {$normalfontsize};
   color: {$darkbluetext};
   border-bottom: 1px solid {$thinblueborder};
 }
 
 .aphront-table-view tr.alt {
   background: {$lightgreybackground};
 }
 
 .aphront-table-view th {
   font-weight: bold;
   font-size: {$normalfontsize};
   white-space: nowrap;
   color: {$bluetext};
   text-shadow: 0 1px 0 white;
   font-weight: bold;
   border-bottom: 1px solid {$thinblueborder};
   background-color: {$lightbluebackground};
 }
 
 th.aphront-table-view-sortable-selected {
   background-color: {$greybackground};
 }
 
 .aphront-table-view th a,
 .aphront-table-view th a:hover,
 .aphront-table-view th a:link {
   color: {$bluetext};
   text-shadow: 0 1px 0 white;
   display: block;
   text-decoration: none;
 }
 
 .aphront-table-view th a:hover {
   text-decoration: underline;
   color: {$darkbluetext};
 }
 
 .aphront-table-view td.header {
   padding: 4px 8px;
   white-space: nowrap;
   text-align: right;
   color: {$bluetext};
   font-weight: bold;
   vertical-align: top;
 }
 
 .aphront-table-view td {
   white-space: nowrap;
   vertical-align: middle;
 }
 
 .aphront-table-down-sort {
   display: inline-block;
   margin-top: 5px;
   width: 0;
   height: 0;
   vertical-align: top;
   border-top: 4px solid {$bluetext};
   border-right: 4px solid transparent;
   border-left: 4px solid transparent;
   content: "";
 }
 
 .aphront-table-up-sort {
   display: inline-block;
   margin-top: 5px;
   width: 0;
   height: 0;
   vertical-align: top;
   border-bottom: 4px solid {$bluetext};
   border-right: 4px solid transparent;
   border-left: 4px solid transparent;
   content: "";
 }
 
 /* - Padding -------------------------------------------------------------------
 
   On desktops, we have more horizontal space and use it to space columns out.
 
   On devices, we make each row slightly taller to create a larger hit target
   for links.
 
 */
 
 .aphront-table-view th {
   padding: 8px 10px;
   font-size: {$normalfontsize};
 }
 
 .aphront-table-view td {
   padding: 8px 10px;
   font-size: {$smallerfontsize};
 }
 
 .device-tablet .aphront-table-view td,
 .device-phone .aphront-table-view td {
   padding: 6px;
 }
 
 .device-tablet .aphront-table-view td + td,
 .device-phone .aphront-table-view td + td {
   padding-left: 0px;
 }
 
 .device-tablet .aphront-table-view th,
 .device-phone .aphront-table-view th {
   padding: 6px;
   overflow: hidden;
 }
 
 .device-tablet .aphront-table-view th + th,
 .device-phone .aphront-table-view th + th {
   padding-left: 0px;
 }
 
 .aphront-table-view td.sorted-column {
   background: {$lightbluebackground};
 }
 
 .aphront-table-view tr.alt td.sorted-column {
   background: {$greybackground};
 }
 
 .aphront-table-view td.action {
   padding-top: 1px;
   padding-bottom: 1px;
 }
 
 .aphront-table-view td.larger {
   font-size: {$biggerfontsize};
 }
 
 .aphront-table-view td.pri {
   font-weight: bold;
   color: {$darkbluetext};
 }
 
 .aphront-table-view td.top {
   vertical-align: top;
 }
 
 .aphront-table-view td.wide {
   white-space: normal;
   width: 100%;
 }
 
 .aphront-table-view th.right,
 .aphront-table-view td.right {
   text-align: right;
 }
 
 .aphront-table-view td.mono {
   font-family: "Monaco", monospace;
   font-size: {$smallestfontsize};
 }
 
 .aphront-table-view td.n {
   font-family: "Monaco", monospace;
   font-size: {$smallestfontsize};
   text-align: right;
 }
 
 .aphront-table-view td.nudgeright, .aphront-table-view th.nudgeright {
   padding-right: 0;
 }
 
 .aphront-table-view td.wrap {
   white-space:      normal;
 }
 
 .aphront-table-view td.prewrap {
   font-family: "Monaco", monospace;
   font-size: {$smallestfontsize};
   white-space: pre-wrap;
 }
 
 .aphront-table-view td.narrow {
   width: 1px;
 }
 
 .aphront-table-view td.icon, .aphront-table-view th.icon {
   width: 1px;
   padding: 0px;
 }
 
 
 
 div.single-display-line-bounds {
   width: 100%;
   position: relative;
   overflow: hidden;
 }
 
 span.single-display-line-content {
   white-space: pre;
   position: absolute;
 }
 
 .device-phone span.single-display-line-content {
   white-space: nowrap;
   position: static;
 }
 
 .aphront-table-view tr.highlighted {
   background: #fdf9e4;
 }
 
 .aphront-table-view tr.alt-highlighted {
   background: {$sh-yellowbackground};
 }
 
 .aphront-table-view tr.diff-removed,
 .aphront-table-view tr.alt-diff-removed {
   background: {$lightred}
 }
 
 .aphront-table-view tr.diff-added,
 .aphront-table-view tr.alt-diff-added {
   background: {$lightgreen}
 }
 
 .aphront-table-view tr.no-data td {
   padding: 16px;
   text-align: center;
   color: {$lightgreytext};
   font-style: italic;
   font-size: {$normalfontsize};
 }
 
 .aphront-table-view td.thumb img {
   max-width: 64px;
   max-height: 64px;
 }
 
 .aphront-table-view td.threads {
   font-family: monospace;
   white-space: pre;
   padding: 0 0 0 8px;
 }
 
 .aphront-table-view td.threads canvas {
   display: block;
 }
 
 .aphront-table-view td.radio {
   text-align: center;
   padding: 2px 4px 0px;
 }
 
 .aphront-table-view th.center,
 .aphront-table-view td.center {
   text-align: center;
 }
 
 .device .aphront-table-view td + td.center,
 .device .aphront-table-view th + th.center {
   padding-left: 3px;
   padding-right: 3px;
 }
 
 .device-desktop .aphront-table-view-device {
   display: none;
 }
 
 .device-tablet .aphront-table-view-nodevice,
 .device-phone .aphront-table-view-nodevice {
   display: none;
 }
 
 .aphront-table-view td.link {
   padding: 0;
 }
 
 .aphront-table-view td.link a {
   display: block;
   padding: 6px 8px;
   font-weight: bold;
 }
 
 .phui-object-box .aphront-table-view {
   border: none;
 }
diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css
index 045e63eb8..d8f4d3450 100644
--- a/webroot/rsrc/css/application/dashboard/dashboard.css
+++ b/webroot/rsrc/css/application/dashboard/dashboard.css
@@ -1,75 +1,81 @@
 /**
  * @provides phabricator-dashboard-css
  */
 
 .dashboard-view {
   margin: 16px;
 }
 
 .device-phone .dashboard-view {
   margin: 8px;
 }
 
 .dashboard-view .phui-object-box {
   margin: 0 0 16px 0;
 }
 
 .dashboard-view .phui-object-box .phui-object-box {
   margin: 0;
 }
 
 .device-desktop .aphront-multi-column-fluid .aphront-multi-column-2-up
 .aphront-multi-column-column-outer.half {
   width: 50%;
 }
 
 .device-desktop .aphront-multi-column-fluid .aphront-multi-column-2-up
 .aphront-multi-column-column-outer.third {
   width: 33.34%;
 }
 
 .device-desktop .aphront-multi-column-fluid .aphront-multi-column-2-up
 .aphront-multi-column-column-outer.thirds {
   width: 66.66%;
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column-outer.grippable
 .aphront-multi-column-column .dashboard-pane {
   cursor: move;
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column .drag-ghost {
   list-style-type: none;
   margin: 16px;
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column
 .dashboard-panel-placeholder {
   display: none;
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column.dashboard-column-empty
 .dashboard-panel-placeholder {
   display: block;
-  padding: 24px;
-  margin: 16px 16px 0px 16px;
+  padding: 20px;
+  margin: 0 0 12px 0;
   text-decoration: none;
   border: 1px {$greyborder} dashed;
   color: {$greytext};
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column.drag-target-list
 .dashboard-panel-placeholder {
   display: none;
 }
 
 .aphront-multi-column-fluid
 .aphront-multi-column-column-outer
 .aphront-multi-column-column .phui-info-view {
   margin: 0;
 }
+
+.dashboard-preview-box {
+  border: 1px solid {$lightblueborder};
+  border-radius: 3px;
+  background-color: rgba(255,255,255,.33);
+}
diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css
index 35f5067fe..7fed4a535 100644
--- a/webroot/rsrc/css/phui/phui-box.css
+++ b/webroot/rsrc/css/phui/phui-box.css
@@ -1,115 +1,115 @@
 /**
  * @provides phui-box-css
  */
 
 .phui-box-border {
   border: 1px solid {$lightblueborder};
   background-color: #fff;
   border-radius: 3px;
 }
 
 .phui-box.focus {
   box-shadow: 0 0 5px 5px rgba(255, 255, 0, 0.90);
 }
 
 .phui-box-grey {
   background-color: #F7F7F9;
   border-radius: 3px;
   border-color: rgba({$alphagrey},.2);
 }
 
 .phui-box-blue {
   background-color: {$bluebackground};
   border-radius: 3px;
   border-color: {$thinblueborder};
 }
 
 .phui-box-blue .phui-object-item,
 .phui-box-grey .phui-object-item {
   background: transparent;
 }
 
 .phui-box-blue .phui-object-item-link,
 .phui-box-grey .phui-object-item-link {
   color: {$darkbluetext};
 }
 
 .phui-box-blue .phui-object-item-list-view,
 .phui-box-grey .phui-object-item-list-view {
   background-color: #fff;
 }
 
 .phui-box-blue .phui-header-shell {
   border-color: {$thinblueborder};
 }
 
 .phui-box-grey .phui-header-shell {
   border-color: rgba({$alphagrey},.1);
 }
 
 .phui-object-box.phui-box-blue div.phui-info-severity-nodata,
 .phui-object-box.phui-box-grey div.phui-info-severity-nodata {
   background: transparent;
   padding: 20px 4px 24px;
   text-align: center;
   border: none;
   color: {$greytext};
 }
 
 
 /* Property Boxes */
 
 .phui-box.phui-box-blue-property {
   border-radius: 3px;
   border-color: {$lightblueborder};
   margin: 0;
   padding: 0;
 }
 
 .phui-box.phui-box-blue-property .phui-header-header .phui-header-icon {
   margin-right: 6px;
 }
 
 .phui-box.phui-box-blue-property .phui-header-action-link {
   margin-top: 0;
   margin-bottom: 0;
 }
 
 .device .phui-box.phui-box-blue-property {
   padding: 0;
 }
 
 .phui-box.phui-object-box.phui-box-blue-property .phui-header-shell {
   background-color: #eff3fc;
   border-top-right-radius: 3px;
   border-top-left-radius: 3px;
   padding: 6px 16px;
 }
 
 .device .phui-box.phui-box-blue-property .phui-header-shell,
 .device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed
   .phui-header-shell {
     padding: 6px 12px;
 }
 
 .phui-box.phui-box-blue-property .phui-header-header {
-  font-size: 13px;
+  font-size: {$biggerfontsize};
   color: {$bluetext};
 }
 
-.phui-box-blue-property .phui-object-item-list-view.phui-object-list-flush {
+.phui-box-blue-property .phui-object-item-list-view {
   padding: 2px 8px;
 }
 
 body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed {
   padding: 0;
 }
 
 body .phui-box-blue-property .phui-header-shell + .phui-object-box {
   margin-bottom: 0;
 }
 
 .phui-box-blue-property .phui-header-shell + .phui-object-box
   .phui-header-shell {
     background: #fff;
 }
diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css
index 47fd0a4da..6e99b70e1 100644
--- a/webroot/rsrc/css/phui/phui-crumbs-view.css
+++ b/webroot/rsrc/css/phui/phui-crumbs-view.css
@@ -1,125 +1,125 @@
 /**
  * @provides phui-crumbs-view-css
  */
 
 .phui-crumbs-view {
   overflow: hidden;
   vertical-align: top;
-  padding: 0 8px 0 16px;
+  padding: 0 20px 0 28px;
   /* TODO: Position this over the slider for Differential's file tree view.
      Remove this once that gets sorted out. */
   position: relative;
   -webkit-font-smoothing: antialiased;
 }
 
 .phui-crumbs-view,
 .phui-crumbs-view a.phui-crumb-view,
 .phui-crumbs-view a.phui-crumbs-action {
   color: {$darkbluetext};
   font-weight: bold;
   text-decoration: none;
 }
 
 .device-phone .phui-crumbs-view {
   padding-left: 8px;
   padding-right: 0;
 }
 
 .phui-crumbs-view a.phui-crumbs-action-disabled,
 .phui-crumbs-view a.phui-crumbs-action-disabled .phui-icon-view {
   color: {$lightgreytext};
 }
 
 .phui-crumbs-view + .phui-header-shell {
   border-top: none;
 }
 
 .device-desktop .phui-crumbs-view a:hover {
   text-decoration: underline;
 }
 
 .phui-crumb-view {
   float: left;
   padding: 8px 0;
 }
 
 .device-phone .phui-crumb-view.phabricator-last-crumb .phui-crumb-name,
 .device-phone .phui-crumb-view.phui-crumb-has-icon,
 .device-phone .phui-crumb-has-icon + .phui-crumb-divider {
   display: inline-block;
 }
 
 .device-phone .phui-crumb-name,
 .device-phone .phui-crumb-divider {
   display: none;
 }
 
 .phui-crumb-has-icon .phui-icon-view {
   margin: 0 4px;
 }
 
 .device-phone .phui-crumb-icon {
   margin-left: 7px;
 }
 
 .phui-crumbs-actions {
   float: right;
   white-space: nowrap;
 }
 
 .phui-crumbs-action {
   display: inline-block;
   height: 17px;
   padding: 8px 12px;
   position: relative;
 }
 
 .device-phone a.phui-crumbs-action {
   padding: 8px 6px;
 }
 
 .device-desktop .phui-crumbs-view a:hover,
 .device-desktop .phui-crumbs-view a:hover .phui-icon-view {
   color: {$blue};
   text-decoration: none;
 }
 
 .device-phone .phui-crumbs-action-name {
   display: none;
 }
 
 a.phui-crumbs-action .phui-icon-view {
   color: {$darkbluetext};
 }
 
 a.phui-crumbs-action .phui-crumbs-action-name {
   margin-left: 6px;
 }
 
 .device-phone a.phui-crumbs-action .phui-icon-view {
   margin-left: 4px;
 }
 
 .phui-crumb-divider {
   margin: 2px 8px;
 }
 
 .phui-crumbs-view.phui-crumbs-border {
   border-bottom: 1px solid rgba({$alphagrey},.1);
 }
 
 body .phui-crumbs-view + .phui-object-box {
   margin-top: 0;
 }
 
 body .phui-crumbs-view + .phui-object-item-list-view {
   padding-top: 0;
 }
 
 .phui-crumb-action-divider {
   border-left: 1px solid {$lightgreyborder};
 }
 
 .phui-crumbs-action-icon + .phui-crumbs-action-icon {
   padding-left: 4px;
 }
diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css
index c77ffa77e..85f815001 100644
--- a/webroot/rsrc/css/phui/phui-document-pro.css
+++ b/webroot/rsrc/css/phui/phui-document-pro.css
@@ -1,207 +1,203 @@
 /**
  * @provides phui-document-view-pro-css
  */
 
 .phui-document-view.phui-document-view-pro {
   max-width: 800px;
   padding: 16px 16px 64px 16px;
   margin: 0 auto;
 }
 
 .phui-document-container {
   background-color: #fff;
   position: relative;
   border-bottom: 1px solid #dedee1;
 }
 
 .phui-document-view-pro-box,
 .phui-document-properties {
   max-width: 800px;
   margin: 0 auto;
 }
 
 .device .phui-document-view-pro-box {
   margin: 0 8px;
 }
 
 .phui-document-view-pro-box .phui-property-list-section {
   margin: 16px auto;
 }
 
 .device .phui-document-view-pro-box .phui-property-list-section {
   margin: 0 8px 16px;
 }
 
 .device .phui-document-view-pro-box .phui-property-list-container {
   padding: 24px 0 0 0;
 }
 
 .device-phone .phui-document-view.phui-document-view-pro {
   padding: 0 8px;
   margin: 0 auto;
 }
 
 .phui-document-view-pro .phui-document-toc {
   position: absolute;
   top: 34px;
   left: -36px;
 }
 
 a.button.phui-document-toc {
   display: inline-block;
   height: 16px;
   width: 16px;
   padding: 3px 8px 4px 8px;
 }
 
 .phui-document-view-pro .phui-document-toc-list {
   margin: 8px;
   border: 1px solid {$blueborder};
   border-radius: 3px;
   box-shadow: {$dropshadow};
   width: 200px;
   position: absolute;
   z-index: 30;
   background-color: #fff;
   top: 52px;
   left: -44px;
 }
 
 .device .phui-document-view-pro .phui-document-toc {
   display: none;
 }
 
 .phui-document-toc-list {
   display: none;
 }
 
 .phui-document-toc-open .phui-document-toc-list {
   display: block;
 }
 
 .phui-document-toc-open .phui-document-toc {
   background-color: {$blue};
 }
 
 .phui-document-toc-open .phui-document-toc .phui-icon-view {
   color: #fff;
 }
 
 .phui-document-view-pro .phui-document-toc-content {
   margin: 4px 12px;
 }
 
 .phui-document-view-pro .phui-document-toc-header {
   font-weight: bold;
   color: {$bluetext};
   margin-bottom: 8px;
 }
 
 .phui-document-view-pro .phui-document-toc-content li {
   margin: 4px 8px;
 }
 
 .phui-document-view-pro .phui-document-content .phabricator-remarkup {
   padding: 16px 0;
   line-height: 1.7em;
 }
 
 .device-desktop .phui-document-view.phui-document-view-pro {
   border: 0;
 }
 
 .phui-document-view.phui-document-view-pro .phui-header-shell {
   background: transparent;
   border-bottom: 1px solid {$thinblueborder};
 }
 
 .phui-document-view.phui-document-view-pro .phui-header-shell {
   margin: 0;
   padding: 16px 0 32px;
 }
 
 .device-phone .phui-document-view.phui-document-view-pro .phui-header-shell {
   margin: 8px 0 0 0;
   padding: 8px 0 20px;
 }
 
 .phui-document-view.phui-document-view-pro .phui-header-tall
   .phui-header-header {
   font-size: 24px;
   line-height: 30px;
   color: #000;
 }
 
 .device-phone .phui-document-view-pro .phui-header-subheader {
   display: block;
   padding: 8px 0 0 0;
 }
 
 .phui-document-view-pro .phui-info-view {
   margin: 16px 0 0 0;
 }
 
 
 
 
 .phui-document-view-pro-box .phui-timeline-view {
   padding: 16px 0 0 0;
   background: none;
   border-top: 1px solid rgba({$alphablue}, 0.20);
 }
 
 .phui-document-view-pro-box .phui-timeline-image {
   border-radius: 25px;
 }
 
 .phui-document-view-pro-box .phui-timeline-wedge {
   display: none;
 }
 
 .phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-group {
   border: none;
 }
 
 .phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-content {
   border: none;
 }
 
 .device-desktop .phui-document-view-pro-box
   .phui-timeline-event-view.phui-timeline-minor-event {
     margin-left: 62px;
 }
 
 .phui-document-view-pro-box .phui-timeline-title {
   border-top-right-radius: 3px;
   border-top-left-radius: 3px;
   background-color: #fff;
   border-bottom: 1px solid #F1F1F4;
 }
 
 .phui-document-view-pro-box .phui-timeline-title-with-icon {
   padding-left: 12px;
 }
 
 .phui-document-view-pro-box .phui-timeline-icon-fill {
   display: none;
 }
 
 .phui-document-view-pro-box .phui-object-box {
   margin: 0;
 }
 
-.phui-document-view-pro-box .phui-object-box .phui-form-view {
-  padding-bottom: 0;
-}
-
 .phui-document-view-pro-box .phui-object-box .remarkup-assist-textarea {
   height: 9em;
 }
 
 .document-has-foot .phui-document-view-pro {
   padding-bottom: 0;
 }
 
 .phui-document-foot-content {
   margin: 64px 0 32px;
 }
diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css
index 600945103..4d71c3d5b 100644
--- a/webroot/rsrc/css/phui/phui-two-column-view.css
+++ b/webroot/rsrc/css/phui/phui-two-column-view.css
@@ -1,204 +1,233 @@
 /**
  * @provides phui-two-column-view-css
  */
 
 .phui-two-column-view .phui-two-column-header {
   background-color: #fff;
   border-bottom: 1px solid rgba({$alphagrey}, .12);
   margin-bottom: 24px;
 }
 
 .device .phui-two-column-view .phui-two-column-header {
   margin-bottom: 12px;
 }
 
 .phui-two-column-view.with-subheader .phui-two-column-header {
   margin-bottom: 0;
 }
 
 .phui-two-column-header .phui-header-header {
   font-size: 20px;
   font-family: 'Aleo', {$fontfamily};
   color: #000;
 }
 
 .device-phone .phui-two-column-header .phui-header-header {
   font-size: 18px;
 }
 
 .phui-two-column-view .phui-two-column-header .phui-header-shell {
   padding: 24px 32px 28px;
   border: none;
 }
 
 .phui-two-column-view .phui-two-column-header
   .phui-profile-header.phui-header-shell {
     padding-bottom: 20px;
 }
 
 .device .phui-two-column-view .phui-two-column-header .phui-header-shell {
   padding: 12px 12px 16px;
 }
 
 .phui-two-column-header .phui-header-subheader {
   margin-top: 12px;
 }
 
 .phui-two-column-subheader {
   padding: 12px 32px;
 }
 
 .device .phui-two-column-subheader {
   padding: 12px 16px;
 }
 
 .device-desktop .phui-two-column-content {
   padding: 0 32px;
 }
 
 .device .phui-two-column-content {
   padding: 0 12px;
 }
 
 .device-desktop .phui-two-column-view .phui-main-column {
   float: left;
   width: calc(100% - 320px);
 }
 
 .device-desktop .phui-two-column-view .phui-side-column {
   float: right;
   width: 300px;
 }
 
+.device-desktop .phui-two-column-view.phui-side-column-left .phui-main-column {
+  float: right;
+  width: calc(100% - 280px);
+}
+
+.device-desktop .phui-two-column-view.phui-side-column-left .phui-side-column {
+  float: left;
+  width: 260px;
+}
+
 .device .phui-side-column {
   margin-bottom: 20px;
 }
 
 .phui-two-column-view .phui-two-column-content
   .phui-object-box {
     margin: 0 0 20px 0;
 }
 
 /* Timeline */
 
 .phui-two-column-view .phui-timeline-view {
   padding: 0;
   background-position: 80px;
 }
 
 .phui-two-column-view .phui-main-column .phui-object-box + .phui-timeline-view {
   margin-top: -20px;
 }
 
 .device .phui-two-column-view .phui-timeline-view {
   background-position: 16px;
   padding: 0;
 }
 
 .device-phone .phui-two-column-view .phui-timeline-event-view {
   margin: 0;
 }
 
 .phui-main-column > .phui-timeline-view:first-child {
   border-top: 1px solid {$thinblueborder};
 }
 
 .device-phone .phui-main-column .phui-timeline-older-transactions-are-hidden {
   margin: 0;
 }
 
 /* Main Column Properties */
 
 .device-desktop .phui-main-column .phui-property-list-key {
   margin-left: 0;
   width: 160px;
 }
 
 .device-desktop .phui-main-column .phui-property-list-value {
   margin-left: 8px;
   width: calc(100% - 200px);
 }
 
 
 /* Property / Action List */
 
 .phui-two-column-properties .phabricator-action-list-view {
   padding-top: 4px;
   padding-bottom: 12px;
 }
 
 .device-desktop .phui-two-column-view .phui-property-list-container {
   padding: 16px 0;
 }
 
 .device-desktop .phui-two-column-view
   .phui-property-list-properties-wrap.phui-property-list-stacked {
   padding: 0 16px;
 }
 
 .device .phui-two-column-view .phui-property-list-container {
   padding: 12px 8px;
 }
 
 .phui-two-column-view .phui-property-list-container
   .keyboard-shortcuts-available {
     display: none;
 }
 
 .phui-two-column-properties.phui-object-box {
   border: 1px solid rgba({$alphablue}, .2);
 }
 
 .device .phui-two-column-content .phui-two-column-properties.phui-object-box {
   padding: 0 12px;
 }
 
 .phui-two-column-properties .phabricator-action-view-icon {
   top: 8px;
   left: 8px;
 }
 
 .phabricator-action-view button.phabricator-action-view-item,
 .phabricator-action-view-item {
   padding: 5px 4px 5px 28px;
 }
 
 .device-desktop .phui-two-column-properties .phabricator-action-view:hover
   .phabricator-action-view-item {
     text-decoration: none;
     background-color: rgba({$alphablue}, .08);
     color: {$sky};
     border-radius: 3px;
 }
 
 .device-desktop .phui-two-column-properties .phabricator-action-view:hover
   .phabricator-action-view-icon {
     color: {$sky};
 }
 
 .phui-two-column-view .phui-property-list-section-header,
 .phui-two-column-view .phui-property-list-text-content {
   margin: 0 16px;
 }
 
 .device .phui-two-column-view .phui-property-list-section-header,
 .device .phui-two-column-view .phui-property-list-text-content {
   margin: 0 8px;
 }
 
 /* Info View */
 
 .phui-two-column-view .phui-info-view {
   margin: 0 0 20px 0;
   padding: 16px;
 }
 
 .phui-two-column-view .phui-two-column-row .phui-object-box
   .phui-info-view {
     margin: 0;
 }
 
 .phui-two-column-view .phui-box-blue-property
   .phui-header-shell + .phui-info-view {
     margin: 16px;
 }
+
+/* Navigation */
+
+.phui-two-column-view .side-has-nav .phabricator-nav-local {
+  width: auto;
+  position: static;
+  margin: 0;
+}
+
+.device .phui-two-column-view .side-has-nav {
+  display: none;
+}
+
+/* Document View */
+
+.phui-two-column-view .phui-two-column-content .phui-document-fluid
+  .phui-document-view {
+    margin: 0 0 20px 0;
+}
diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js
index cd004b0e8..5a53bafd7 100644
--- a/webroot/rsrc/js/phuix/PHUIXFormControl.js
+++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js
@@ -1,187 +1,222 @@
 /**
  * @provides phuix-form-control-view
  * @requires javelin-install
  *           javelin-dom
  */
 
 JX.install('PHUIXFormControl', {
 
   members: {
     _node: null,
     _labelNode: null,
     _errorNode: null,
     _inputNode: null,
     _valueSetCallback: null,
     _valueGetCallback: null,
 
     setLabel: function(label) {
       JX.DOM.setContent(this._getLabelNode(), label);
       return this;
     },
 
     setError: function(error) {
       JX.DOM.setContent(this._getErrorNode(), error);
       return this;
     },
 
     setControl: function(type, spec) {
       var node = this._getInputNode();
 
       var input;
       switch (type) {
         case 'tokenizer':
           input = this._newTokenizer(spec);
           break;
         case 'select':
           input = this._newSelect(spec);
           break;
         case 'points':
           input = this._newPoints(spec);
           break;
+        case 'optgroups':
+          input = this._newOptgroups(spec);
+          break;
         default:
           // TODO: Default or better error?
           JX.$E('Bad Input Type');
           return;
       }
 
       JX.DOM.setContent(node, input.node);
       this._valueGetCallback = input.get;
       this._valueSetCallback = input.set;
 
       return this;
     },
 
     setValue: function(value) {
       this._valueSetCallback(value);
       return this;
     },
 
     getValue: function() {
       return this._valueGetCallback();
     },
 
     getNode: function() {
       if (!this._node) {
 
         var attrs = {
           className: 'aphront-form-control grouped'
         };
 
         var content = [
           this._getLabelNode(),
           this._getErrorNode(),
           this._getInputNode()
         ];
 
         this._node = JX.$N('div', attrs, content);
       }
 
       return this._node;
     },
 
     _getLabelNode: function() {
       if (!this._labelNode) {
         var attrs = {
           className: 'aphront-form-label'
         };
 
         this._labelNode = JX.$N('label', attrs);
       }
 
       return this._labelNode;
     },
 
     _getErrorNode: function() {
       if (!this._errorNode) {
         var attrs = {
           className: 'aphront-form-error'
         };
 
         this._errorNode = JX.$N('span', attrs);
       }
 
       return this._errorNode;
     },
 
     _getInputNode: function() {
       if (!this._inputNode) {
         var attrs = {
           className: 'aphront-form-input'
         };
 
         this._inputNode = JX.$N('div', attrs);
       }
 
       return this._inputNode;
     },
 
     _newTokenizer: function(spec) {
       var build = JX.Prefab.newTokenizerFromTemplate(
         spec.markup,
         spec.config);
       build.tokenizer.start();
 
       function get_value() {
         return JX.keys(build.tokenizer.getTokens());
       }
 
       function set_value(map) {
         var tokens = get_value();
         for (var ii = 0; ii < tokens.length; ii++) {
           build.tokenizer.removeToken(tokens[ii]);
         }
         for (var k in map) {
           var v = JX.Prefab.transformDatasourceResults(map[k]);
           build.tokenizer.addToken(k, v);
         }
       }
 
       set_value(spec.value || {});
 
       return {
         node: build.node,
         get: get_value,
         set: set_value
       };
     },
 
     _newSelect: function(spec) {
       var node = JX.Prefab.renderSelect(
         spec.options,
         spec.value,
         {},
         spec.order);
 
       return {
         node: node,
         get: function() {
           return node.value;
         },
         set: function(value) {
           node.value = value;
         }
       };
     },
 
     _newPoints: function(spec) {
       var attrs = {
         type: 'text',
         value: spec.value
       };
 
       var node = JX.$N('input', attrs);
 
+      return {
+        node: node,
+        get: function() {
+          return node.value;
+        },
+        set: function(value) {
+          node.value = value;
+        }
+      };
+    },
+
+    _newOptgroups: function(spec) {
+      var value = spec.value || null;
+
+      var optgroups = [];
+      for (var ii = 0; ii < spec.groups.length; ii++) {
+        var group = spec.groups[ii];
+        var options = [];
+        for (var jj = 0; jj < group.options.length; jj++) {
+          var option = group.options[jj];
+          options.push(JX.$N('option', {value: option.key}, option.label));
+
+          if (option.selected && (value === null)) {
+            value = option.key;
+          }
+        }
+        optgroups.push(JX.$N('optgroup', {label: group.label}, options));
+      }
+
+      var node = JX.$N('select', {}, optgroups);
+      node.value = value;
+
       return {
         node: node,
         get: function() {
           return node.value;
         },
         set: function(value) {
           node.value = value;
         }
       };
     }
 
   }
 
 });