diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php
index 091ade2d6..7ef5d0668 100644
--- a/src/applications/nuance/controller/NuanceItemViewController.php
+++ b/src/applications/nuance/controller/NuanceItemViewController.php
@@ -1,122 +1,126 @@
 <?php
 
 final class NuanceItemViewController 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());
     $name = $item->getDisplayName();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       pht('Items'),
       $this->getApplicationURI('item/'));
     $crumbs->addTextCrumb($title);
     $crumbs->setBorder(true);
 
     $curtain = $this->buildCurtain($item);
     $content = $this->buildContent($item);
     $commands = $this->buildCommands($item);
 
     $timeline = $this->buildTransactionTimeline(
       $item,
       new NuanceItemTransactionQuery());
 
     $main = array(
       $commands,
       $content,
       $timeline,
     );
 
     $header = id(new PHUIHeaderView())
       ->setHeader($name);
 
     $view = id(new PHUITwoColumnView())
       ->setHeader($header)
       ->setCurtain($curtain)
       ->setMainColumn($main);
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
   private function buildCurtain(NuanceItem $item) {
     $viewer = $this->getViewer();
     $id = $item->getID();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $item,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $curtain = $this->newCurtainView($item);
 
     $curtain->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Manage Item'))
         ->setIcon('fa-cogs')
         ->setHref($this->getApplicationURI("item/manage/{$id}/")));
 
     $impl = $item->getImplementation();
     $impl->setViewer($viewer);
 
     foreach ($impl->getItemActions($item) as $action) {
       $curtain->addAction($action);
     }
 
+    foreach ($impl->getItemCurtainPanels($item) as $panel) {
+      $curtain->addPanel($panel);
+    }
+
     return $curtain;
   }
 
   private function buildContent(NuanceItem $item) {
     $viewer = $this->getViewer();
     $impl = $item->getImplementation();
 
     $impl->setViewer($viewer);
     return $impl->buildItemView($item);
   }
 
   private function buildCommands(NuanceItem $item) {
     $viewer = $this->getViewer();
 
     $commands = id(new NuanceItemCommandQuery())
       ->setViewer($viewer)
       ->withItemPHIDs(array($item->getPHID()))
       ->execute();
     $commands = msort($commands, 'getID');
 
     if (!$commands) {
       return null;
     }
 
     $rows = array();
     foreach ($commands as $command) {
       $rows[] = array(
         $command->getCommand(),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Command'),
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Pending Commands'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setTable($table);
   }
 
 }
diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php
index 1283fb43b..4da2bb8e4 100644
--- a/src/applications/nuance/github/NuanceGitHubRawEvent.php
+++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php
@@ -1,380 +1,386 @@
 <?php
 
 final class NuanceGitHubRawEvent extends Phobject {
 
   private $raw;
   private $type;
 
   const TYPE_ISSUE = 'issue';
   const TYPE_REPOSITORY = 'repository';
 
   public static function newEvent($type, array $raw) {
     $event = new self();
     $event->type = $type;
     $event->raw = $raw;
     return $event;
   }
 
   public function getRepositoryFullName() {
     return $this->getRepositoryFullRawName();
   }
 
   public function isIssueEvent() {
     if ($this->isPullRequestEvent()) {
       return false;
     }
 
     if ($this->type == self::TYPE_ISSUE) {
       return true;
     }
 
     switch ($this->getIssueRawKind()) {
       case 'IssuesEvent':
         return true;
       case 'IssueCommentEvent':
         if (!$this->getRawPullRequestData()) {
           return true;
         }
         break;
     }
 
     return false;
   }
 
   public function isPullRequestEvent() {
     if ($this->type == self::TYPE_ISSUE) {
       // TODO: This is wrong, some of these are pull events.
       return false;
     }
 
     $raw = $this->raw;
 
     switch ($this->getIssueRawKind()) {
       case 'PullRequestEvent':
         return true;
       case 'IssueCommentEvent':
         if ($this->getRawPullRequestData()) {
           return true;
         }
         break;
     }
 
     return false;
   }
 
   public function getIssueNumber() {
     if (!$this->isIssueEvent()) {
       return null;
     }
 
     return $this->getRawIssueNumber();
   }
 
   public function getPullRequestNumber() {
     if (!$this->isPullRequestEvent()) {
       return null;
     }
 
     return $this->getRawIssueNumber();
   }
 
 
   public function getID() {
     $raw = $this->raw;
 
     $id = idx($raw, 'id');
     if ($id) {
       return (int)$id;
     }
 
     return null;
   }
 
   public function getComment() {
-    return 'TODO: Actually extract comment text.';
+    if (!$this->isIssueEvent() && !$this->isPullRequestEvent()) {
+      return null;
+    }
+
+    $raw = $this->raw;
+
+    return idxv($raw, array('payload', 'comment', 'body'));
   }
 
   public function getURI() {
     $raw = $this->raw;
 
     if ($this->isIssueEvent() || $this->isPullRequestEvent()) {
       if ($this->type == self::TYPE_ISSUE) {
         $uri = idxv($raw, array('issue', 'html_url'));
         $uri = $uri.'#event-'.$this->getID();
       } else {
         // The format of pull request events varies so we need to fish around
         // a bit to find the correct URI.
         $uri = idxv($raw, array('payload', 'pull_request', 'html_url'));
         $need_anchor = true;
 
         // For comments, we get a different anchor to link to the comment. In
         // this case, the URI comes with an anchor already.
         if (!$uri) {
           $uri = idxv($raw, array('payload', 'comment', 'html_url'));
           $need_anchor = false;
         }
 
         if (!$uri) {
           $uri = idxv($raw, array('payload', 'issue', 'html_url'));
           $need_anchor = true;
         }
 
         if ($need_anchor) {
           $uri = $uri.'#event-'.$this->getID();
         }
       }
     } else {
       switch ($this->getIssueRawKind()) {
         case 'CreateEvent':
           $ref = idxv($raw, array('payload', 'ref'));
 
           $repo = $this->getRepositoryFullRawName();
           return "https://github.com/{$repo}/commits/{$ref}";
         case 'PushEvent':
           // These don't really have a URI since there may be multiple commits
           // involved and GitHub doesn't bundle the push as an object on its
           // own. Just try to find the URI for the log. The API also does
           // not return any HTML URI for these events.
 
           $head = idxv($raw, array('payload', 'head'));
           if ($head === null) {
             return null;
           }
 
           $repo = $this->getRepositoryFullRawName();
           return "https://github.com/{$repo}/commits/{$head}";
         case 'WatchEvent':
           // These have no reasonable URI.
           return null;
         default:
           return null;
       }
     }
 
     return $uri;
   }
 
   private function getRepositoryFullRawName() {
     $raw = $this->raw;
 
     $full = idxv($raw, array('repo', 'name'));
     if (strlen($full)) {
       return $full;
     }
 
     // For issue events, the repository is not identified explicitly in the
     // response body. Parse it out of the URI.
 
     $matches = null;
     $ok = preg_match(
       '(/repos/((?:[^/]+)/(?:[^/]+))/issues/events/)',
       idx($raw, 'url'),
       $matches);
 
     if ($ok) {
       return $matches[1];
     }
 
     return null;
   }
 
   private function getIssueRawKind() {
     $raw = $this->raw;
     return idxv($raw, array('type'));
   }
 
   private function getRawIssueNumber() {
     $raw = $this->raw;
 
     if ($this->type == self::TYPE_ISSUE) {
       return idxv($raw, array('issue', 'number'));
     }
 
     if ($this->type == self::TYPE_REPOSITORY) {
       $issue_number = idxv($raw, array('payload', 'issue', 'number'));
       if ($issue_number) {
         return $issue_number;
       }
 
       $pull_number = idxv($raw, array('payload', 'number'));
       if ($pull_number) {
         return $pull_number;
       }
     }
 
     return null;
   }
 
   private function getRawPullRequestData() {
     $raw = $this->raw;
     return idxv($raw, array('payload', 'issue', 'pull_request'));
   }
 
   public function getEventFullTitle() {
     switch ($this->type) {
       case self::TYPE_ISSUE:
         $title = $this->getRawIssueEventTitle();
         break;
       case self::TYPE_REPOSITORY:
         $title = $this->getRawRepositoryEventTitle();
         break;
       default:
         $title = pht('Unknown Event Type ("%s")', $this->type);
         break;
     }
 
     return pht(
       'GitHub %s %s (%s)',
       $this->getRepositoryFullRawName(),
       $this->getTargetObjectName(),
       $title);
   }
 
   private function getTargetObjectName() {
     if ($this->isPullRequestEvent()) {
       $number = $this->getRawIssueNumber();
       return pht('Pull Request #%d', $number);
     } else if ($this->isIssueEvent()) {
       $number = $this->getRawIssueNumber();
       return pht('Issue #%d', $number);
     } else if ($this->type == self::TYPE_REPOSITORY) {
       $raw = $this->raw;
 
 
       $type = idx($raw, 'type');
       switch ($type) {
         case 'CreateEvent':
           $ref = idxv($raw, array('payload', 'ref'));
           $ref_type = idxv($raw, array('payload', 'ref_type'));
 
           switch ($ref_type) {
             case 'branch':
               return pht('Branch %s', $ref);
             case 'tag':
               return pht('Tag %s', $ref);
             default:
               return pht('Ref %s', $ref);
           }
           break;
         case 'PushEvent':
           $ref = idxv($raw, array('payload', 'ref'));
           if (preg_match('(^refs/heads/)', $ref)) {
             return pht('Branch %s', substr($ref, strlen('refs/heads/')));
           } else {
             return pht('Ref %s', $ref);
           }
           break;
         case 'WatchEvent':
           $actor = idxv($raw, array('actor', 'login'));
           return pht('User %s', $actor);
       }
 
       return pht('Unknown Object');
     } else {
       return pht('Unknown Object');
     }
   }
 
   private function getRawIssueEventTitle() {
     $raw = $this->raw;
 
     $action = idxv($raw, array('event'));
     switch ($action) {
       case 'assigned':
         $assignee = idxv($raw, array('assignee', 'login'));
         $title = pht('Assigned: %s', $assignee);
         break;
       case 'closed':
         $title = pht('Closed');
         break;
       case 'demilestoned':
         $milestone = idxv($raw, array('milestone', 'title'));
         $title = pht('Removed Milestone: %s', $milestone);
         break;
       case 'labeled':
         $label = idxv($raw, array('label', 'name'));
         $title = pht('Added Label: %s', $label);
         break;
       case 'locked':
         $title = pht('Locked');
         break;
       case 'milestoned':
         $milestone = idxv($raw, array('milestone', 'title'));
         $title = pht('Added Milestone: %s', $milestone);
         break;
       case 'renamed':
         $title = pht('Renamed');
         break;
       case 'reopened':
         $title = pht('Reopened');
         break;
       case 'unassigned':
         $assignee = idxv($raw, array('assignee', 'login'));
         $title = pht('Unassigned: %s', $assignee);
         break;
       case 'unlabeled':
         $label = idxv($raw, array('label', 'name'));
         $title = pht('Removed Label: %s', $label);
         break;
       case 'unlocked':
         $title = pht('Unlocked');
         break;
       default:
         $title = pht('"%s"', $action);
         break;
     }
 
 
     return $title;
   }
 
   private function getRawRepositoryEventTitle() {
     $raw = $this->raw;
 
     $type = idx($raw, 'type');
     switch ($type) {
       case 'CreateEvent':
         return pht('Created');
       case 'PushEvent':
         $head = idxv($raw, array('payload', 'head'));
         $head = substr($head, 0, 12);
         return pht('Pushed: %s', $head);
       case 'IssuesEvent':
         $action = idxv($raw, array('payload', 'action'));
         switch ($action) {
           case 'closed':
             return pht('Closed');
           case 'opened':
             return pht('Created');
           case 'reopened':
             return pht('Reopened');
           default:
             return pht('"%s"', $action);
         }
         break;
       case 'IssueCommentEvent':
         $action = idxv($raw, array('payload', 'action'));
         switch ($action) {
           case 'created':
             return pht('Comment');
           default:
             return pht('"%s"', $action);
         }
         break;
       case 'PullRequestEvent':
         $action = idxv($raw, array('payload', 'action'));
         switch ($action) {
           case 'opened':
             return pht('Created');
           default:
             return pht('"%s"', $action);
         }
         break;
       case 'WatchEvent':
         return pht('Watched');
     }
 
     return pht('"%s"', $type);
   }
 
 }
diff --git a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
index f5e211914..5bdc3f34a 100644
--- a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
+++ b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
@@ -1,111 +1,112 @@
 <?php
 
 final class NuanceGitHubRawEventTestCase
   extends PhabricatorTestCase {
 
   public function testIssueEvents() {
     $path = dirname(__FILE__).'/issueevents/';
 
     $cases = $this->readTestCases($path);
 
     foreach ($cases as $name => $info) {
       $input = $info['input'];
       $expect = $info['expect'];
 
       $event = NuanceGitHubRawEvent::newEvent(
         NuanceGitHubRawEvent::TYPE_ISSUE,
         $input);
 
       $this->assertGitHubRawEventParse($expect, $event, $name);
     }
   }
 
   public function testRepositoryEvents() {
     $path = dirname(__FILE__).'/repositoryevents/';
 
     $cases = $this->readTestCases($path);
 
     foreach ($cases as $name => $info) {
       $input = $info['input'];
       $expect = $info['expect'];
 
       $event = NuanceGitHubRawEvent::newEvent(
         NuanceGitHubRawEvent::TYPE_REPOSITORY,
         $input);
 
       $this->assertGitHubRawEventParse($expect, $event, $name);
     }
   }
 
   private function assertGitHubRawEventParse(
     array $expect,
     NuanceGitHubRawEvent $event,
     $name) {
 
     $actual = array(
       'repository.name.full' => $event->getRepositoryFullName(),
       'is.issue' => $event->isIssueEvent(),
       'is.pull' => $event->isPullRequestEvent(),
       'issue.number' => $event->getIssueNumber(),
       'pull.number' => $event->getPullRequestNumber(),
       'id' => $event->getID(),
       'uri' => $event->getURI(),
       'title.full' => $event->getEventFullTitle(),
+      'comment' => $event->getComment(),
     );
 
     // Only verify the keys which are actually present in the test. This
     // allows tests to specify only relevant keys.
     $actual = array_select_keys($actual, array_keys($expect));
 
     ksort($expect);
     ksort($actual);
 
     $this->assertEqual($expect, $actual, $name);
   }
 
   private function readTestCases($path) {
     $files = Filesystem::listDirectory($path, $include_hidden = false);
 
     $tests = array();
     foreach ($files as $file) {
       $data = Filesystem::readFile($path.$file);
 
       $parts = preg_split('/^~{5,}$/m', $data);
       if (count($parts) < 2) {
         throw new Exception(
           pht(
             'Expected test file "%s" to contain an input section in JSON, '.
             'then an expected result section in JSON, with the two sections '.
             'separated by a line of "~~~~~", but the divider is not present '.
             'in the file.',
             $file));
       } else if (count($parts) > 2) {
         throw new Exception(
           pht(
             'Expected test file "%s" to contain exactly two sections, '.
             'but it has more than two sections.'));
       }
 
       list($input, $expect) = $parts;
 
       try {
         $input = phutil_json_decode($input);
         $expect = phutil_json_decode($expect);
       } catch (Exception $ex) {
         throw new PhutilProxyException(
           pht(
             'Exception while decoding test data for test "%s".',
             $file),
           $ex);
       }
 
       $tests[$file] = array(
         'input' => $input,
         'expect' => $expect,
       );
     }
 
     return $tests;
   }
 
 }
diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
index 71abbceac..1991bb568 100644
--- a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
+++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
@@ -1,164 +1,165 @@
 {
   "id": "3740938746",
   "type": "IssueCommentEvent",
   "actor": {
     "id": 102631,
     "login": "epriestley",
     "gravatar_id": "",
     "url": "https://api.github.com/users/epriestley",
     "avatar_url": "https://avatars.githubusercontent.com/u/102631?"
   },
   "repo": {
     "id": 14627834,
     "name": "epriestley/poems",
     "url": "https://api.github.com/repos/epriestley/poems"
   },
   "payload": {
     "action": "created",
     "issue": {
       "url": "https://api.github.com/repos/epriestley/poems/issues/2",
       "repository_url": "https://api.github.com/repos/epriestley/poems",
       "labels_url": "https://api.github.com/repos/epriestley/poems/issues/2/labels{/name}",
       "comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments",
       "events_url": "https://api.github.com/repos/epriestley/poems/issues/2/events",
       "html_url": "https://github.com/epriestley/poems/pull/2",
       "id": 139568860,
       "number": 2,
       "title": "Please Merge Quack2 into Feature",
       "user": {
         "login": "epriestley",
         "id": 102631,
         "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
         "gravatar_id": "",
         "url": "https://api.github.com/users/epriestley",
         "html_url": "https://github.com/epriestley",
         "followers_url": "https://api.github.com/users/epriestley/followers",
         "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
         "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
         "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
         "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
         "organizations_url": "https://api.github.com/users/epriestley/orgs",
         "repos_url": "https://api.github.com/users/epriestley/repos",
         "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
         "received_events_url": "https://api.github.com/users/epriestley/received_events",
         "type": "User",
         "site_admin": false
       },
       "labels": [
         {
           "url": "https://api.github.com/repos/epriestley/poems/labels/bug",
           "name": "bug",
           "color": "fc2929"
         }
       ],
       "state": "open",
       "locked": false,
       "assignee": {
         "login": "epriestley",
         "id": 102631,
         "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
         "gravatar_id": "",
         "url": "https://api.github.com/users/epriestley",
         "html_url": "https://github.com/epriestley",
         "followers_url": "https://api.github.com/users/epriestley/followers",
         "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
         "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
         "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
         "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
         "organizations_url": "https://api.github.com/users/epriestley/orgs",
         "repos_url": "https://api.github.com/users/epriestley/repos",
         "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
         "received_events_url": "https://api.github.com/users/epriestley/received_events",
         "type": "User",
         "site_admin": false
       },
       "milestone": {
         "url": "https://api.github.com/repos/epriestley/poems/milestones/1",
         "html_url": "https://github.com/epriestley/poems/milestones/b",
         "labels_url": "https://api.github.com/repos/epriestley/poems/milestones/1/labels",
         "id": 1633589,
         "number": 1,
         "title": "b",
         "description": null,
         "creator": {
           "login": "epriestley",
           "id": 102631,
           "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
           "gravatar_id": "",
           "url": "https://api.github.com/users/epriestley",
           "html_url": "https://github.com/epriestley",
           "followers_url": "https://api.github.com/users/epriestley/followers",
           "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
           "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
           "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
           "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
           "organizations_url": "https://api.github.com/users/epriestley/orgs",
           "repos_url": "https://api.github.com/users/epriestley/repos",
           "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
           "received_events_url": "https://api.github.com/users/epriestley/received_events",
           "type": "User",
           "site_admin": false
         },
         "open_issues": 1,
         "closed_issues": 0,
         "state": "open",
         "created_at": "2016-03-09T12:42:50Z",
         "updated_at": "2016-03-09T12:52:41Z",
         "due_on": null,
         "closed_at": null
       },
       "comments": 1,
       "created_at": "2016-03-09T12:52:31Z",
       "updated_at": "2016-03-09T12:53:06Z",
       "closed_at": null,
       "pull_request": {
         "url": "https://api.github.com/repos/epriestley/poems/pulls/2",
         "html_url": "https://github.com/epriestley/poems/pull/2",
         "diff_url": "https://github.com/epriestley/poems/pull/2.diff",
         "patch_url": "https://github.com/epriestley/poems/pull/2.patch"
       },
       "body": ""
     },
     "comment": {
       "url": "https://api.github.com/repos/epriestley/poems/issues/comments/194282800",
       "html_url": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800",
       "issue_url": "https://api.github.com/repos/epriestley/poems/issues/2",
       "id": 194282800,
       "user": {
         "login": "epriestley",
         "id": 102631,
         "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
         "gravatar_id": "",
         "url": "https://api.github.com/users/epriestley",
         "html_url": "https://github.com/epriestley",
         "followers_url": "https://api.github.com/users/epriestley/followers",
         "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
         "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
         "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
         "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
         "organizations_url": "https://api.github.com/users/epriestley/orgs",
         "repos_url": "https://api.github.com/users/epriestley/repos",
         "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
         "received_events_url": "https://api.github.com/users/epriestley/received_events",
         "type": "User",
         "site_admin": false
       },
       "created_at": "2016-03-09T12:53:06Z",
       "updated_at": "2016-03-09T12:53:06Z",
       "body": "wub wub"
     }
   },
   "public": true,
   "created_at": "2016-03-09T12:53:06Z"
 }
 ~~~~~
 {
   "repository.name.full": "epriestley/poems",
   "is.issue": false,
   "is.pull": true,
   "issue.number": null,
   "pull.number": 2,
   "id": 3740938746,
   "uri": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800",
-  "title.full": "GitHub epriestley/poems Pull Request #2 (Comment)"
+  "title.full": "GitHub epriestley/poems Pull Request #2 (Comment)",
+  "comment": "wub wub"
 }
diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
index a1ca09404..d22fd86e2 100644
--- a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
+++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
@@ -1,101 +1,102 @@
 {
   "id": "3733510485",
   "type": "IssueCommentEvent",
   "actor": {
     "id": 102631,
     "login": "epriestley",
     "gravatar_id": "",
     "url": "https://api.github.com/users/epriestley",
     "avatar_url": "https://avatars.githubusercontent.com/u/102631?"
   },
   "repo": {
     "id": 14627834,
     "name": "epriestley/poems",
     "url": "https://api.github.com/repos/epriestley/poems"
   },
   "payload": {
     "action": "created",
     "issue": {
       "url": "https://api.github.com/repos/epriestley/poems/issues/1",
       "repository_url": "https://api.github.com/repos/epriestley/poems",
       "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}",
       "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments",
       "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events",
       "html_url": "https://github.com/epriestley/poems/issues/1",
       "id": 139138813,
       "number": 1,
       "title": "Enforce haiku in commit messages",
       "user": {
         "login": "epriestley",
         "id": 102631,
         "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
         "gravatar_id": "",
         "url": "https://api.github.com/users/epriestley",
         "html_url": "https://github.com/epriestley",
         "followers_url": "https://api.github.com/users/epriestley/followers",
         "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
         "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
         "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
         "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
         "organizations_url": "https://api.github.com/users/epriestley/orgs",
         "repos_url": "https://api.github.com/users/epriestley/repos",
         "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
         "received_events_url": "https://api.github.com/users/epriestley/received_events",
         "type": "User",
         "site_admin": false
       },
       "labels": [
 
       ],
       "state": "open",
       "locked": false,
       "assignee": null,
       "milestone": null,
       "comments": 1,
       "created_at": "2016-03-08T00:41:08Z",
       "updated_at": "2016-03-08T00:41:22Z",
       "closed_at": null,
       "body": "OK"
     },
     "comment": {
       "url": "https://api.github.com/repos/epriestley/poems/issues/comments/193528669",
       "html_url": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669",
       "issue_url": "https://api.github.com/repos/epriestley/poems/issues/1",
       "id": 193528669,
       "user": {
         "login": "epriestley",
         "id": 102631,
         "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
         "gravatar_id": "",
         "url": "https://api.github.com/users/epriestley",
         "html_url": "https://github.com/epriestley",
         "followers_url": "https://api.github.com/users/epriestley/followers",
         "following_url": "https://api.github.com/users/epriestley/following{/other_user}",
         "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
         "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
         "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
         "organizations_url": "https://api.github.com/users/epriestley/orgs",
         "repos_url": "https://api.github.com/users/epriestley/repos",
         "events_url": "https://api.github.com/users/epriestley/events{/privacy}",
         "received_events_url": "https://api.github.com/users/epriestley/received_events",
         "type": "User",
         "site_admin": false
       },
       "created_at": "2016-03-08T00:41:22Z",
       "updated_at": "2016-03-08T00:41:22Z",
       "body": "comment on issue"
     }
   },
   "public": true,
   "created_at": "2016-03-08T00:41:22Z"
 }
 ~~~~~
 {
   "repository.name.full": "epriestley/poems",
   "is.issue": true,
   "is.pull": false,
   "issue.number": 1,
   "id": 3733510485,
   "uri": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669",
-  "title.full": "GitHub epriestley/poems Issue #1 (Comment)"
+  "title.full": "GitHub epriestley/poems Issue #1 (Comment)",
+  "comment": "comment on issue"
 }
diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php
index 75f44e896..617d75e49 100644
--- a/src/applications/nuance/item/NuanceGitHubEventItemType.php
+++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php
@@ -1,347 +1,370 @@
 <?php
 
 final class NuanceGitHubEventItemType
   extends NuanceItemType {
 
   const ITEMTYPE = 'github.event';
 
   private $externalObject;
 
   public function getItemTypeDisplayName() {
     return pht('GitHub Event');
   }
 
   public function getItemTypeDisplayIcon() {
     return 'fa-github';
   }
 
   public function getItemDisplayName(NuanceItem $item) {
     return $this->newRawEvent($item)->getEventFullTitle();
   }
 
   public function canUpdateItems() {
     return true;
   }
 
   protected function updateItemFromSource(NuanceItem $item) {
     $viewer = $this->getViewer();
     $is_dirty = false;
 
     // TODO: Link up the requestor, etc.
 
     $is_dirty = false;
 
     $xobj = $this->reloadExternalObject($item);
 
     if ($xobj) {
       $item->setItemProperty('doorkeeper.xobj.phid', $xobj->getPHID());
       $is_dirty = true;
     }
 
     if ($item->getStatus() == NuanceItem::STATUS_IMPORTING) {
       $item->setStatus(NuanceItem::STATUS_ROUTING);
       $is_dirty = true;
     }
 
     if ($is_dirty) {
       $item->save();
     }
   }
 
   private function getDoorkeeperRef(NuanceItem $item) {
     $raw = $this->newRawEvent($item);
 
     $full_repository = $raw->getRepositoryFullName();
     if (!strlen($full_repository)) {
       return null;
     }
 
     if ($raw->isIssueEvent()) {
       $ref_type = DoorkeeperBridgeGitHubIssue::OBJTYPE_GITHUB_ISSUE;
       $issue_number = $raw->getIssueNumber();
       $full_ref = "{$full_repository}#{$issue_number}";
     } else {
       return null;
     }
 
     return id(new DoorkeeperObjectRef())
       ->setApplicationType(DoorkeeperBridgeGitHub::APPTYPE_GITHUB)
       ->setApplicationDomain(DoorkeeperBridgeGitHub::APPDOMAIN_GITHUB)
       ->setObjectType($ref_type)
       ->setObjectID($full_ref);
   }
 
   private function reloadExternalObject(NuanceItem $item, $local = false) {
     $ref = $this->getDoorkeeperRef($item);
     if (!$ref) {
       return null;
     }
 
     $source = $item->getSource();
     $token = $source->getSourceProperty('github.token');
     $token = new PhutilOpaqueEnvelope($token);
 
     $viewer = $this->getViewer();
 
     $ref = id(new DoorkeeperImportEngine())
       ->setViewer($viewer)
       ->setRefs(array($ref))
       ->setThrowOnMissingLink(true)
       ->setContextProperty('github.token', $token)
       ->needLocalOnly($local)
       ->executeOne();
 
     if ($ref->getSyncFailed()) {
       $xobj = null;
     } else {
       $xobj = $ref->getExternalObject();
     }
 
     if ($xobj) {
       $this->externalObject = $xobj;
     }
 
     return $xobj;
   }
 
   private function getExternalObject(NuanceItem $item) {
     if ($this->externalObject === null) {
       $xobj = $this->reloadExternalObject($item, $local = true);
       if ($xobj) {
         $this->externalObject = $xobj;
       } else {
         $this->externalObject = false;
       }
     }
 
     if ($this->externalObject) {
       return $this->externalObject;
     }
 
     return null;
   }
 
   private function newRawEvent(NuanceItem $item) {
     $type = $item->getItemProperty('api.type');
     $raw = $item->getItemProperty('api.raw', array());
 
     return NuanceGitHubRawEvent::newEvent($type, $raw);
   }
 
   public function getItemActions(NuanceItem $item) {
     $actions = array();
 
     $xobj = $this->getExternalObject($item);
     if ($xobj) {
       $actions[] = $this->newItemAction($item, 'reload')
         ->setName(pht('Reload from GitHub'))
         ->setIcon('fa-refresh')
         ->setWorkflow(true)
         ->setRenderAsForm(true);
     }
 
     $actions[] = $this->newItemAction($item, 'sync')
       ->setName(pht('Import to Maniphest'))
       ->setIcon('fa-anchor')
       ->setWorkflow(true)
       ->setRenderAsForm(true);
 
     $actions[] = $this->newItemAction($item, 'raw')
       ->setName(pht('View Raw Event'))
       ->setWorkflow(true)
       ->setIcon('fa-code');
 
     return $actions;
   }
 
+  public function getItemCurtainPanels(NuanceItem $item) {
+    $viewer = $this->getViewer();
+
+    $panels = array();
+
+    $xobj = $this->getExternalObject($item);
+    if ($xobj) {
+      $xobj_phid = $xobj->getPHID();
+
+      $task = id(new ManiphestTaskQuery())
+        ->setViewer($viewer)
+        ->withBridgedObjectPHIDs(array($xobj_phid))
+        ->executeOne();
+      if ($task) {
+        $panels[] = $this->newCurtainPanel($item)
+          ->setHeaderText(pht('Imported As'))
+          ->appendChild($viewer->renderHandle($task->getPHID()));
+      }
+    }
+
+    return $panels;
+  }
+
   protected function handleAction(NuanceItem $item, $action) {
     $viewer = $this->getViewer();
     $controller = $this->getController();
 
     switch ($action) {
       case 'raw':
         $raw = array(
           'api.type' => $item->getItemProperty('api.type'),
           'api.raw' => $item->getItemProperty('api.raw'),
         );
 
         $raw_output = id(new PhutilJSON())->encodeFormatted($raw);
 
         $raw_box = id(new AphrontFormTextAreaControl())
           ->setCustomClass('PhabricatorMonospaced')
           ->setLabel(pht('Raw Event'))
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
           ->setValue($raw_output);
 
         $form = id(new AphrontFormView())
           ->appendChild($raw_box);
 
         return $controller->newDialog()
           ->setWidth(AphrontDialogView::WIDTH_FULL)
           ->setTitle(pht('GitHub Raw Event'))
           ->appendForm($form)
           ->addCancelButton($item->getURI(), pht('Done'));
       case 'sync':
       case 'reload':
         $item->issueCommand($viewer->getPHID(), $action);
         return id(new AphrontRedirectResponse())->setURI($item->getURI());
     }
 
     return null;
   }
 
   protected function newItemView(NuanceItem $item) {
     $content = array();
 
     $content[] = $this->newGitHubEventItemPropertyBox($item);
 
     return $content;
   }
 
   private function newGitHubEventItemPropertyBox($item) {
     $viewer = $this->getViewer();
 
     $property_list = id(new PHUIPropertyListView())
       ->setViewer($viewer);
 
     $event = $this->newRawEvent($item);
 
     $property_list->addProperty(
       pht('GitHub Event ID'),
       $event->getID());
 
     $event_uri = $event->getURI();
     if ($event_uri && PhabricatorEnv::isValidRemoteURIForLink($event_uri)) {
       $event_uri = phutil_tag(
         'a',
         array(
           'href' => $event_uri,
         ),
         $event_uri);
     }
 
     if ($event_uri) {
       $property_list->addProperty(
         pht('GitHub Event URI'),
         $event_uri);
     }
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Event Properties'))
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->appendChild($property_list);
   }
 
   protected function handleCommand(
     NuanceItem $item,
     NuanceItemCommand $command) {
 
     $action = $command->getCommand();
     switch ($action) {
       case 'sync':
         return $this->syncItem($item, $command);
       case 'reload':
         $this->reloadExternalObject($item);
         return true;
     }
 
     return null;
   }
 
   private function syncItem(
     NuanceItem $item,
     NuanceItemCommand $command) {
 
     $xobj_phid = $item->getItemProperty('doorkeeper.xobj.phid');
     if (!$xobj_phid) {
       throw new Exception(
         pht(
           'Unable to sync: no external object PHID.'));
     }
 
     // TODO: Write some kind of marker to prevent double-synchronization.
 
     $viewer = $this->getViewer();
 
     $xobj = id(new DoorkeeperExternalObjectQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($xobj_phid))
       ->executeOne();
     if (!$xobj) {
       throw new Exception(
         pht(
           'Unable to sync: failed to load object "%s".',
           $xobj_phid));
     }
 
     $nuance_phid = id(new PhabricatorNuanceApplication())->getPHID();
 
     $xactions = array();
 
     $task = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
       ->withBridgedObjectPHIDs(array($xobj_phid))
       ->executeOne();
     if (!$task) {
       $task = ManiphestTask::initializeNewTask($viewer)
         ->setAuthorPHID($nuance_phid)
         ->setBridgedObjectPHID($xobj_phid);
 
       $title = $xobj->getProperty('task.title');
       if (!strlen($title)) {
         $title = pht('Nuance Item %d Task', $item->getID());
       }
 
       $description = $xobj->getProperty('task.description');
       $created = $xobj->getProperty('task.created');
       $state = $xobj->getProperty('task.state');
 
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(ManiphestTransaction::TYPE_TITLE)
         ->setNewValue($title)
         ->setDateCreated($created);
 
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
         ->setNewValue($description)
         ->setDateCreated($created);
 
       $task->setDateCreated($created);
 
       // TODO: Synchronize state.
     }
 
     $event = $this->newRawEvent($item);
     $comment = $event->getComment();
     if (strlen($comment)) {
       $xactions[] = id(new ManiphestTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
         ->attachComment(
           id(new ManiphestTransactionComment())
             ->setContent($comment));
     }
 
     // TODO: Preserve the item's original source.
     $source = PhabricatorContentSource::newForSource(
       PhabricatorDaemonContentSource::SOURCECONST);
 
     // TODO: This should really be the external source.
     $acting_phid = $nuance_phid;
 
     $editor = id(new ManiphestTransactionEditor())
       ->setActor($viewer)
       ->setActingAsPHID($acting_phid)
       ->setContentSource($source)
       ->setContinueOnNoEffect(true)
       ->setContinueOnMissingFields(true);
 
     $xactions = $editor->applyTransactions($task, $xactions);
 
     return array(
       'objectPHID' => $task->getPHID(),
       'xactionPHIDs' => mpull($xactions, 'getPHID'),
     );
   }
 
 
 }
diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php
index d4187bf41..a1186c6dd 100644
--- a/src/applications/nuance/item/NuanceItemType.php
+++ b/src/applications/nuance/item/NuanceItemType.php
@@ -1,139 +1,147 @@
 <?php
 
 abstract class NuanceItemType
   extends Phobject {
 
   private $viewer;
   private $controller;
 
   public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
   }
 
   public function getViewer() {
     return $this->viewer;
   }
 
   public function setController(PhabricatorController $controller) {
     $this->controller = $controller;
     return $this;
   }
 
   public function getController() {
     return $this->controller;
   }
 
   public function canUpdateItems() {
     return false;
   }
 
   final public function buildItemView(NuanceItem $item) {
     return $this->newItemView($item);
   }
 
   protected function newItemView(NuanceItem $item) {
     return null;
   }
 
   public function getItemTypeDisplayIcon() {
     return null;
   }
 
   public function getItemActions(NuanceItem $item) {
     return array();
   }
 
+  public function getItemCurtainPanels(NuanceItem $item) {
+    return array();
+  }
+
   abstract public function getItemTypeDisplayName();
   abstract public function getItemDisplayName(NuanceItem $item);
 
   final public function updateItem(NuanceItem $item) {
     if (!$this->canUpdateItems()) {
       throw new Exception(
         pht(
           'This item type ("%s", of class "%s") can not update items.',
           $this->getItemTypeConstant(),
           get_class($this)));
     }
 
     $this->updateItemFromSource($item);
   }
 
   protected function updateItemFromSource(NuanceItem $item) {
     throw new PhutilMethodNotImplementedException();
   }
 
   final public function getItemTypeConstant() {
     return $this->getPhobjectClassConstant('ITEMTYPE', 64);
   }
 
   final public static function getAllItemTypes() {
     return id(new PhutilClassMapQuery())
       ->setAncestorClass(__CLASS__)
       ->setUniqueMethod('getItemTypeConstant')
       ->execute();
   }
 
   final protected function newItemAction(NuanceItem $item, $key) {
     $id = $item->getID();
     $action_uri = "/nuance/item/action/{$id}/{$key}/";
 
     return id(new PhabricatorActionView())
       ->setHref($action_uri);
   }
 
+  final protected function newCurtainPanel(NuanceItem $item) {
+    return id(new PHUICurtainPanelView());
+  }
+
   final public function buildActionResponse(NuanceItem $item, $action) {
     $response = $this->handleAction($item, $action);
 
     if ($response === null) {
       return new Aphront404Response();
     }
 
     return $response;
   }
 
   protected function handleAction(NuanceItem $item, $action) {
     return null;
   }
 
   final public function applyCommand(
     NuanceItem $item,
     NuanceItemCommand $command) {
 
     $result = $this->handleCommand($item, $command);
 
     if ($result === null) {
       return;
     }
 
     $xaction = id(new NuanceItemTransaction())
       ->setTransactionType(NuanceItemTransaction::TYPE_COMMAND)
       ->setNewValue(
         array(
           'command' => $command->getCommand(),
           'parameters' => $command->getParameters(),
           'result' => $result,
         ));
 
     $viewer = $this->getViewer();
 
     // TODO: Maybe preserve the actor's original content source?
     $source = PhabricatorContentSource::newForSource(
       PhabricatorDaemonContentSource::SOURCECONST);
 
     $editor = id(new NuanceItemEditor())
       ->setActor($viewer)
       ->setActingAsPHID($command->getAuthorPHID())
       ->setContentSource($source)
       ->setContinueOnMissingFields(true)
       ->setContinueOnNoEffect(true)
       ->applyTransactions($item, array($xaction));
   }
 
   protected function handleCommand(
     NuanceItem $item,
     NuanceItemCommand $command) {
     return null;
   }
 
 }