diff --git a/rsrc/behavior-sprint-boards.js b/rsrc/behavior-sprint-boards.js
index 13432f19d55c7f766da0cfc77acda27bbb2da153..505fb3e9e6fdf9c270c296a9dd4f597d7074683e 100644
--- a/rsrc/behavior-sprint-boards.js
+++ b/rsrc/behavior-sprint-boards.js
@@ -160,196 +160,200 @@ JX.behavior('sprint-boards', function(config, statics) {
             data.beforePHID = before_phid;
         }
 
-        data.order = statics.order;
+    data.order = statics.order;
 
-        var workflow = new JX.Workflow(statics.moveURI, data)
-            .setHandler(function(response) {
-                onresponse(response, item, list);
-            });
+    var workflow = new JX.Workflow(statics.moveURI, data)
+      .setHandler(function(response) {
+        onresponse(response, item, list);
+      });
 
-        workflow.start();
-    }
+    workflow.start();
+  }
 
-    function onedit(column, r) {
-        var new_card = JX.$H(r.tasks).getNode();
-        var new_data = JX.Stratcom.getData(new_card);
-        var items = finditems(column);
-        var edited = false;
-        var remove_index = null;
-
-        for (var ii = 0; ii < items.length; ii++) {
-            var item = items[ii];
-
-            var data = JX.Stratcom.getData(item);
-            var phid = data.objectPHID;
-
-            if (phid == new_data.objectPHID) {
-                if (r.data.removeFromBoard) {
-                    remove_index = ii;
-                }
-                items[ii] = new_card;
-                data = new_data;
-                edited = true;
-            }
+  function onedit(column, r) {
+    var new_card = JX.$H(r.tasks).getNode();
+    var new_data = JX.Stratcom.getData(new_card);
+    var items = finditems(column);
+    var edited = false;
+    var remove_index = null;
 
-            data.sort = r.data.sortMap[data.objectPHID] || data.sort;
-        }
+    for (var ii = 0; ii < items.length; ii++) {
+      var item = items[ii];
 
-        // this is an add then...!
-        if (!edited) {
-            items[items.length + 1] = new_card;
-            new_data.sort = r.data.sortMap[new_data.objectPHID] || new_data.sort;
-        }
+      var data = JX.Stratcom.getData(item);
+      var phid = data.objectPHID;
 
-        if (remove_index !== null) {
-            items.splice(remove_index, 1);
+      if (phid == new_data.objectPHID) {
+        if (r.data.removeFromBoard) {
+          remove_index = ii;
         }
+        items[ii] = new_card;
+        data = new_data;
+        edited = true;
+      }
 
-        items.sort(colsort);
-
-        JX.DOM.setContent(column, items);
+      data.sort = r.data.sortMap[data.objectPHID] || data.sort;
+    }
 
-        onupdate(column);
+    // this is an add then...!
+    if (!edited) {
+      items[items.length + 1] = new_card;
+      new_data.sort = r.data.sortMap[new_data.objectPHID] || new_data.sort;
     }
 
-    function update_statics(update_config) {
-        statics.boardID = update_config.boardID;
-        statics.projectPHID = update_config.projectPHID;
-        statics.order = update_config.order;
-        statics.moveURI = update_config.moveURI;
-        statics.createURI = update_config.createURI;
+    if (remove_index !== null) {
+      items.splice(remove_index, 1);
     }
 
-    function init_board() {
-        var lists = [];
-        var ii;
-        var cols = getcolumns();
+    items.sort(colsort);
 
-        for (ii = 0; ii < cols.length; ii++) {
-            var list = new JX.DraggableList('project-card', cols[ii])
-                .setFindItemsHandler(JX.bind(null, finditems, cols[ii]))
-                .setOuterContainer(JX.$(config.boardID))
-                .setCanDragX(true);
+    JX.DOM.setContent(column, items);
 
-            list.listen('didSend', JX.bind(list, onupdate, cols[ii]));
-            list.listen('didReceive', JX.bind(list, onupdate, cols[ii]));
+    onupdate(column);
+  };
 
-            list.listen('didDrop', JX.bind(null, ondrop, list));
+  function update_statics(update_config) {
+    statics.boardID = update_config.boardID;
+    statics.projectPHID = update_config.projectPHID;
+    statics.order = update_config.order;
+    statics.moveURI = update_config.moveURI;
+    statics.createURI = update_config.createURI;
+  }
 
-            list.listen('didBeginDrag', JX.bind(null, onbegindrag));
-            list.listen('didEndDrag', JX.bind(null, onenddrag));
+  function init_board() {
+    var lists = [];
+    var ii;
+    var cols = getcolumns();
 
-            lists.push(list);
+    for (ii = 0; ii < cols.length; ii++) {
+      var list = new JX.DraggableList('project-card', cols[ii])
+        .setFindItemsHandler(JX.bind(null, finditems, cols[ii]))
+        .setOuterContainer(JX.$(config.boardID))
+        .setCanDragX(true);
 
-            onupdate(cols[ii]);
-        }
+      list.listen('didSend', JX.bind(list, onupdate, cols[ii]));
+      list.listen('didReceive', JX.bind(list, onupdate, cols[ii]));
 
-        for (ii = 0; ii < lists.length; ii++) {
-            lists[ii].setGroup(lists);
-        }
-    }
+      list.listen('didDrop', JX.bind(null, ondrop, list));
 
-    function setup() {
-
-        JX.Stratcom.listen(
-            'click',
-            ['edit-project-card'],
-            function(e) {
-                e.kill();
-                var column = e.getNode('project-column');
-                var request_data = {
-                    responseType: 'card',
-                    columnPHID: JX.Stratcom.getData(column).columnPHID,
-                    order: statics.order
-                };
-                new JX.Workflow(e.getNode('tag:a').href, request_data)
-                    .setHandler(JX.bind(null, onedit, column))
-                    .start();
-            });
-
-        JX.Stratcom.listen(
-            'click',
-            ['column-add-task'],
-            function (e) {
-
-                // We want the 'boards-dropdown-menu' behavior to see this event and
-                // close the dropdown, but don't want to follow the link.
-                e.prevent();
-
-                var column_phid = e.getNodeData('column-add-task').columnPHID;
-                var request_data = {
-                    responseType: 'card',
-                    columnPHID: column_phid,
-                    projects: statics.projectPHID,
-                    order: statics.order
-                };
-                var cols = getcolumns();
-                var ii;
-                var column;
-                for (ii = 0; ii < cols.length; ii++) {
-                    if (JX.Stratcom.getData(cols[ii]).columnPHID == column_phid) {
-                        column = cols[ii];
-                        break;
-                    }
-                }
-                new JX.Workflow(statics.createURI, request_data)
-                    .setHandler(JX.bind(null, onedit, column))
-                    .start();
-            });
-
-        JX.Stratcom.listen('click', 'boards-dropdown-menu', function(e) {
-            var data = e.getNodeData('boards-dropdown-menu');
-            if (data.menu) {
-                return;
-            }
+      list.listen('didBeginDrag', JX.bind(null, onbegindrag));
+      list.listen('didEndDrag', JX.bind(null, onenddrag));
+
+      lists.push(list);
 
-            e.kill();
-
-            var list = JX.$H(data.items).getFragment().firstChild;
-
-            var button = e.getNode('boards-dropdown-menu');
-            data.menu = new JX.PHUIXDropdownMenu(button);
-            data.menu.setContent(list);
-            data.menu.open();
-
-            JX.DOM.listen(list, 'click', 'tag:a', function(e) {
-                if (!e.isNormalClick()) {
-                    return;
-                }
-                data.menu.close();
-            });
-        });
-
-        JX.Stratcom.listen(
-            'quicksand-redraw',
-            null,
-            function (e) {
-                var data = e.getData();
-                if (!data.newResponse.boardConfig) {
-                    return;
-                }
-                var new_config;
-                if (data.fromServer) {
-                    new_config = data.newResponse.boardConfig;
-                    statics.boardConfigCache[data.newResponseID] = new_config;
-                } else {
-                    new_config = statics.boardConfigCache[data.newResponseID];
-                    statics.boardID = new_config.boardID;
-                }
-                update_statics(new_config);
-                if (data.fromServer) {
-                    init_board();
-                }
-            });
-        return true;
+      onupdate(cols[ii]);
     }
 
-    if (!statics.setup) {
-        update_statics(config);
-        var current_page_id = JX.Quicksand.getCurrentPageID();
-        statics.boardConfigCache = {};
-        statics.boardConfigCache[current_page_id] = config;
-        statics.setup = init_board();
+    for (ii = 0; ii < lists.length; ii++) {
+      lists[ii].setGroup(lists);
     }
+  }
+
+  function setup() {
+
+    JX.Stratcom.listen(
+      'click',
+      ['edit-project-card'],
+      function(e) {
+        e.kill();
+        var column = e.getNode('project-column');
+        var request_data = {
+          responseType: 'card',
+          columnPHID: JX.Stratcom.getData(column).columnPHID,
+          order: statics.order
+        };
+        new JX.Workflow(e.getNode('tag:a').href, request_data)
+          .setHandler(JX.bind(null, onedit, column))
+          .start();
+      });
+
+    JX.Stratcom.listen(
+      'click',
+      ['column-add-task'],
+      function (e) {
+
+        // We want the 'boards-dropdown-menu' behavior to see this event and
+        // close the dropdown, but don't want to follow the link.
+        e.prevent();
+
+        var column_data = e.getNodeData('column-add-task');
+        var column_phid = column_data.columnPHID;
+
+        var request_data = {
+          responseType: 'card',
+          columnPHID: column_phid,
+          projects: column_data.projectPHID,
+          order: statics.order
+        };
+
+        var cols = getcolumns();
+        var ii;
+        var column;
+        for (ii = 0; ii < cols.length; ii++) {
+          if (JX.Stratcom.getData(cols[ii]).columnPHID == column_phid) {
+            column = cols[ii];
+            break;
+          }
+        }
+        new JX.Workflow(statics.createURI, request_data)
+          .setHandler(JX.bind(null, onedit, column))
+          .start();
+      });
+
+    JX.Stratcom.listen('click', 'boards-dropdown-menu', function(e) {
+      var data = e.getNodeData('boards-dropdown-menu');
+      if (data.menu) {
+        return;
+      }
+
+      e.kill();
+
+      var list = JX.$H(data.items).getFragment().firstChild;
+
+      var button = e.getNode('boards-dropdown-menu');
+      data.menu = new JX.PHUIXDropdownMenu(button);
+      data.menu.setContent(list);
+      data.menu.open();
+
+      JX.DOM.listen(list, 'click', 'tag:a', function(e) {
+        if (!e.isNormalClick()) {
+          return;
+        }
+        data.menu.close();
+      });
+    });
+
+    JX.Stratcom.listen(
+      'quicksand-redraw',
+      null,
+      function (e) {
+        var data = e.getData();
+        if (!data.newResponse.boardConfig) {
+          return;
+        }
+        var new_config;
+        if (data.fromServer) {
+          new_config = data.newResponse.boardConfig;
+          statics.boardConfigCache[data.newResponseID] = new_config;
+        } else {
+          new_config = statics.boardConfigCache[data.newResponseID];
+          statics.boardID = new_config.boardID;
+        }
+        update_statics(new_config);
+        if (data.fromServer) {
+          init_board();
+        }
+      });
+    return true;
+  }
+
+  if (!statics.setup) {
+    update_statics(config);
+    var current_page_id = JX.Quicksand.getCurrentPageID();
+    statics.boardConfigCache = {};
+    statics.boardConfigCache[current_page_id] = config;
+    init_board();
+    statics.setup = setup();
+  }
 
 });
diff --git a/src/controller/board/SprintBoardColumnDetailController.php b/src/controller/board/SprintBoardColumnDetailController.php
index 9e59ecfae2fbe0105ec981ffa236bf4195b70e53..663e307e07c3c6a86b1132da8e91f5b662039a3b 100644
--- a/src/controller/board/SprintBoardColumnDetailController.php
+++ b/src/controller/board/SprintBoardColumnDetailController.php
@@ -23,6 +23,8 @@ final class SprintBoardColumnDetailController
     }
     $this->setProject($project);
 
+    $project_id = $project->getID();
+
     $column = id(new PhabricatorProjectColumnQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
@@ -46,6 +48,10 @@ final class SprintBoardColumnDetailController
     $actions = $this->buildActionView($column);
     $properties = $this->buildPropertyView($column, $actions);
 
+    $crumbs = $this->buildApplicationCrumbs();
+    $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/");
+    $crumbs->addTextCrumb(pht('Column: %s', $title));
+
     $box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
@@ -53,13 +59,14 @@ final class SprintBoardColumnDetailController
     $nav = $this->getProfileMenu();
 
     return $this->newPage()
-        ->setTitle($title)
-        ->setNavigation($nav)
-        ->appendChild(
-            array(
-                $box,
-                $timeline,
-            ));
+      ->setTitle($title)
+      ->setNavigation($nav)
+      ->setCrumbs($crumbs)
+      ->appendChild(
+        array(
+          $box,
+          $timeline,
+        ));
   }
 
   private function buildHeaderView(PhabricatorProjectColumn $column) {
diff --git a/src/controller/board/SprintBoardColumnEditController.php b/src/controller/board/SprintBoardColumnEditController.php
index 3c79119f41be4de79bb7526dc8f2ee624238ad43..3c2ad54cb9fc74fb976203b8e4ae7c78ab7c49c4 100644
--- a/src/controller/board/SprintBoardColumnEditController.php
+++ b/src/controller/board/SprintBoardColumnEditController.php
@@ -81,10 +81,12 @@ final class SprintBoardColumnEditController
 
       $xactions = array();
 
-      $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
-      $xactions[] = id(new PhabricatorProjectColumnTransaction())
-        ->setTransactionType($type_name)
-        ->setNewValue($v_name);
+      if (!$column->getProxy()) {
+        $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
+        $xactions[] = id(new PhabricatorProjectColumnTransaction())
+          ->setTransactionType($type_name)
+          ->setNewValue($v_name);
+      }
 
       $type_limit = PhabricatorProjectColumnTransaction::TYPE_LIMIT;
       $xactions[] = id(new PhabricatorProjectColumnTransaction())
@@ -105,25 +107,26 @@ final class SprintBoardColumnEditController
       }
     }
 
-    $form = new AphrontFormView();
-    $form
-      ->setUser($request->getUser())
-      ->appendChild(
+    $form = id(new AphrontFormView())
+      ->setUser($request->getUser());
+
+    if (!$column->getProxy()) {
+      $form->appendChild(
         id(new AphrontFormTextControl())
           ->setValue($v_name)
           ->setLabel(pht('Name'))
           ->setName('name')
-          ->setError($e_name)
-          ->setCaption(
-            pht('This will be displayed as the header of the column.')))
-      ->appendChild(
-        id(new AphrontFormTextControl())
-          ->setValue($v_limit)
-          ->setLabel(pht('Point Limit'))
-          ->setName('limit')
-          ->setError($e_limit)
-          ->setCaption(
-            pht('Maximum number of points of tasks allowed in the column.')));
+          ->setError($e_name));
+    }
+
+    $form->appendChild(
+      id(new AphrontFormTextControl())
+        ->setValue($v_limit)
+        ->setLabel(pht('Point Limit'))
+        ->setName('limit')
+        ->setError($e_limit)
+        ->setCaption(
+          pht('Maximum number of points of tasks allowed in the column.')));
 
 
     if ($is_new) {
@@ -147,8 +150,8 @@ final class SprintBoardColumnEditController
     $nav = $this->getProfileMenu();
 
     return $this->newPage()
-        ->setTitle($title)
-        ->setNavigation($nav)
-        ->appendChild($form_box);
+      ->setTitle($title)
+      ->setNavigation($nav)
+      ->appendChild($form_box);
   }
 }
diff --git a/src/controller/board/SprintBoardMoveController.php b/src/controller/board/SprintBoardMoveController.php
index f7d2f1fd6590c63b418aafad2271194666a9ce96..49eb609b414f11618d1c202d60d39146a0d1ea34 100644
--- a/src/controller/board/SprintBoardMoveController.php
+++ b/src/controller/board/SprintBoardMoveController.php
@@ -26,6 +26,7 @@ final class SprintBoardMoveController
       return new Aphront404Response();
     }
     $is_sprint = $this->isSprint($project);
+    $board_phid = $project->getPHID();
 
     $object = id(new ManiphestTaskQuery())
       ->setViewer($viewer)
@@ -55,11 +56,14 @@ final class SprintBoardMoveController
       return new Aphront404Response();
     }
 
-    $positions = id(new PhabricatorProjectColumnPositionQuery())
+    $engine = id(new PhabricatorBoardLayoutEngine())
       ->setViewer($viewer)
-      ->withColumns($columns)
-      ->withObjectPHIDs(array($object_phid))
-      ->execute();
+      ->setBoardPHIDs(array($board_phid))
+      ->setObjectPHIDs(array($object_phid))
+      ->executeLayout();
+
+    $columns = $engine->getObjectColumns($board_phid, $object_phid);
+    $old_column_phids = mpull($columns, 'getPHID');
 
     $xactions = array();
 
@@ -81,7 +85,7 @@ final class SprintBoardMoveController
         ) + $order_params)
       ->setOldValue(
         array(
-          'columnPHIDs' => mpull($positions, 'getColumnPHID'),
+          'columnPHIDs' => $old_column_phids,
           'projectPHID' => $column->getProjectPHID(),
         ));
 
@@ -135,7 +139,33 @@ final class SprintBoardMoveController
           ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
           ->setNewValue($sub);
       }
-   }
+    }
+
+    $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)
@@ -152,9 +182,41 @@ final class SprintBoardMoveController
         ->withPHIDs(array($object->getOwnerPHID()))
         ->executeOne();
     }
+
+    // Reload the object so it reflects edits which have been applied.
+    $object = id(new ManiphestTaskQuery())
+      ->setViewer($viewer)
+      ->withPHIDs(array($object_phid))
+      ->needProjectPHIDs(true)
+      ->requireCapabilities(
+        array(
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
+        ))
+      ->executeOne();
+
+    $except_phids = array($board_phid);
+    if ($project->getHasSubprojects() || $project->getHasMilestones()) {
+      $descendants = id(new PhabricatorProjectQuery())
+        ->setViewer($viewer)
+        ->withAncestorProjectPHIDs($except_phids)
+        ->execute();
+      foreach ($descendants as $descendant) {
+        $except_phids[] = $descendant->getPHID();
+      }
+    }
+
+    $except_phids = array_fuse($except_phids);
+    $handle_phids = array_fuse($object->getProjectPHIDs());
+    $handle_phids = array_diff_key($handle_phids, $except_phids);
+
+    $project_handles = $viewer->loadHandles($handle_phids);
+    $project_handles = iterator_to_array($project_handles);
+
     if ($is_sprint == true) {
       $card = id(new SprintBoardTaskCard())
           ->setProject($project)
+          ->setProjectHandles($project_handles)
           ->setViewer($viewer)
           ->setTask($object)
           ->setOwner($owner)
@@ -164,6 +226,7 @@ final class SprintBoardMoveController
       $card = id(new ProjectBoardTaskCard())
           ->setViewer($viewer)
           ->setTask($object)
+          ->setProjectHandles($project_handles)
           ->setOwner($owner)
           ->setCanEdit(true)
           ->setProject($project)
@@ -173,6 +236,6 @@ final class SprintBoardMoveController
 
     return id(new AphrontAjaxResponse())->setContent(
       array('task' => $card));
- }
+  }
 
 }
diff --git a/src/controller/board/SprintBoardViewController.php b/src/controller/board/SprintBoardViewController.php
index 3ce36c65b7e30829a02b3df816d5af905a582349..ed8dd231510410a4e264dcab1e645e1d2b941cfb 100755
--- a/src/controller/board/SprintBoardViewController.php
+++ b/src/controller/board/SprintBoardViewController.php
@@ -28,68 +28,34 @@ final class SprintBoardViewController
     $project = $this->getProject();
 
     $this->readRequestState();
-    $columns = $this->loadColumns($project);
 
-    // TODO: Expand the checks here if we add the ability
-    // to hide the Backlog column
-    if (!$columns) {
-      $can_edit = PhabricatorPolicyFilter::hasCapability(
-          $viewer,
-          $project,
-          PhabricatorPolicyCapability::CAN_EDIT);
-      if (!$can_edit) {
-        $content = $this->buildNoAccessContent($project);
-      } else {
-        $content = $this->buildInitializeContent($project);
-      }
-
-      if ($content instanceof AphrontResponse) {
-        return $content;
-      }
-
-      $nav = $this->getProfileMenu();
-      $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD);
-
-      $crumbs = $this->buildApplicationCrumbs();
-      $crumbs->addTextCrumb(pht('Workboard'));
-
-      return $this->newPage()
-          ->setTitle(
-              array(
-                  pht('Workboard'),
-                  $project->getName(),
-              ))
-          ->setNavigation($nav)
-          ->setCrumbs($crumbs)
-          ->appendChild($content);
-    }
     $is_sprint = $this->isSprint($project);
     $board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
 
-    $engine = id(new ManiphestTaskSearchEngine())
+    $search_engine = id(new ManiphestTaskSearchEngine())
       ->setViewer($viewer)
       ->setBaseURI($board_uri)
       ->setIsBoardView(true);
 
-    if ($request->isFormPost()) {
-      $saved = $engine->buildSavedQueryFromRequest($request);
-      $engine->saveQuery($saved);
+    if ($request->isFormPost() && !$request->getBool('initialize')) {
+      $saved = $search_engine->buildSavedQueryFromRequest($request);
+      $search_engine->saveQuery($saved);
       $filter_form = id(new AphrontFormView())
         ->setUser($viewer);
-      $engine->buildSearchForm($filter_form, $saved);
-      if ($engine->getErrors()) {
+      $search_engine->buildSearchForm($filter_form, $saved);
+      if ($search_engine->getErrors()) {
         return $this->newDialog()
           ->setWidth(AphrontDialogView::WIDTH_FULL)
           ->setTitle(pht('Advanced Filter'))
           ->appendChild($filter_form->buildLayoutView())
-          ->setErrors($engine->getErrors())
+          ->setErrors($search_engine->getErrors())
           ->setSubmitURI($board_uri)
           ->addSubmitButton(pht('Apply Filter'))
           ->addCancelButton($board_uri);
       }
       return id(new AphrontRedirectResponse())->setURI(
         $this->getURIWithState(
-          $engine->getQueryResultsPageURI($saved->getQueryKey())));
+          $search_engine->getQueryResultsPageURI($saved->getQueryKey())));
     }
 
     $query_key = $request->getURIData('queryKey');
@@ -103,8 +69,8 @@ final class SprintBoardViewController
     $this->queryKey = $query_key;
 
     $custom_query = null;
-    if ($engine->isBuiltinQuery($query_key)) {
-      $saved = $engine->buildSavedQueryFromBuiltin($query_key);
+    if ($search_engine->isBuiltinQuery($query_key)) {
+      $saved = $search_engine->buildSavedQueryFromBuiltin($query_key);
     } else {
       $saved = id(new PhabricatorSavedQueryQuery())
         ->setViewer($viewer)
@@ -121,7 +87,7 @@ final class SprintBoardViewController
     if ($request->getURIData('filter')) {
       $filter_form = id(new AphrontFormView())
         ->setUser($viewer);
-      $engine->buildSearchForm($filter_form, $saved);
+      $search_engine->buildSearchForm($filter_form, $saved);
 
       return $this->newDialog()
         ->setWidth(AphrontDialogView::WIDTH_FULL)
@@ -132,58 +98,68 @@ final class SprintBoardViewController
         ->addCancelButton($board_uri);
     }
 
-    $task_query = $engine->buildQueryFromSavedQuery($saved);
+    $task_query = $search_engine->buildQueryFromSavedQuery($saved);
+
+    $select_phids = array($project->getPHID());
+    if ($project->getHasSubprojects() || $project->getHasMilestones()) {
+      $descendants = id(new PhabricatorProjectQuery())
+        ->setViewer($viewer)
+        ->withAncestorProjectPHIDs($select_phids)
+        ->execute();
+      foreach ($descendants as $descendant) {
+        $select_phids[] = $descendant->getPHID();
+      }
+    }
 
     $tasks = $task_query
       ->withEdgeLogicPHIDs(
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
-        PhabricatorQueryConstraint::OPERATOR_AND,
-        array($project->getPHID()))
+        PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
+        array($select_phids))
       ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
       ->setViewer($viewer)
       ->execute();
     $tasks = mpull($tasks, null, 'getPHID');
 
-    if ($tasks) {
-      $positions = id(new PhabricatorProjectColumnPositionQuery())
-        ->setViewer($viewer)
-        ->withObjectPHIDs(mpull($tasks, 'getPHID'))
-        ->withColumns($columns)
-        ->execute();
-      $positions = mpull($positions, null, 'getObjectPHID');
-    } else {
-      $positions = array();
-    }
+    $board_phid = $project->getPHID();
 
-    $task_map = array();
-    foreach ($tasks as $task) {
-      $task_phid = $task->getPHID();
-      if (empty($positions[$task_phid])) {
-        // This shouldn't normally be possible because we create positions on
-        // demand, but we might have raced as an object was removed from the
-        // board. Just drop the task if we don't have a position for it.
-        continue;
-      }
+    $layout_engine = id(new PhabricatorBoardLayoutEngine())
+      ->setViewer($viewer)
+      ->setBoardPHIDs(array($board_phid))
+      ->setObjectPHIDs(array_keys($tasks))
+      ->executeLayout();
 
-      $position = $positions[$task_phid];
-      $task_map[$position->getColumnPHID()][] = $task_phid;
-    }
+    $columns = $layout_engine->getColumns($board_phid);
+    if (!$columns) {
+      $can_edit = PhabricatorPolicyFilter::hasCapability(
+        $viewer,
+        $project,
+        PhabricatorPolicyCapability::CAN_EDIT);
+      if (!$can_edit) {
+        $content = $this->buildNoAccessContent($project);
+      } else {
+        $content = $this->buildInitializeContent($project);
+      }
 
-    // If we're showing the board in "natural" order, sort columns by their
-    // column positions.
-    if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) {
-      foreach ($task_map as $column_phid => $task_phids) {
-        $order = array();
-        foreach ($task_phids as $task_phid) {
-          if (isset($positions[$task_phid])) {
-            $order[$task_phid] = $positions[$task_phid]->getOrderingKey();
-          } else {
-            $order[$task_phid] = 0;
-          }
-        }
-        asort($order);
-        $task_map[$column_phid] = array_keys($order);
+      if ($content instanceof AphrontResponse) {
+        return $content;
       }
+
+      $nav = $this->getProfileMenu();
+      $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD);
+
+      $crumbs = $this->buildApplicationCrumbs();
+      $crumbs->addTextCrumb(pht('Workboard'));
+
+      return $this->newPage()
+        ->setTitle(
+          array(
+            pht('Workboard'),
+            $project->getName(),
+          ))
+        ->setNavigation($nav)
+        ->setCrumbs($crumbs)
+        ->appendChild($content);
     }
 
     $task_can_edit_map = id(new PhabricatorPolicyFilter())
@@ -202,7 +178,10 @@ final class SprintBoardViewController
           return new Aphront404Response();
         }
 
-        $batch_task_phids = idx($task_map, $batch_column->getPHID(), array());
+        $batch_task_phids = $layout_engine->getColumnObjectPHIDs(
+          $board_phid,
+          $batch_column->getPHID());
+
         foreach ($batch_task_phids as $key => $batch_task_phid) {
           if (empty($task_can_edit_map[$batch_task_phid])) {
             unset($batch_task_phids[$key]);
@@ -270,10 +249,46 @@ final class SprintBoardViewController
 
     $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
 
+    $all_project_phids = array();
+    foreach ($tasks as $task) {
+      foreach ($task->getProjectPHIDs() as $project_phid) {
+        $all_project_phids[$project_phid] = $project_phid;
+      }
+    }
+
+    foreach ($select_phids as $phid) {
+      unset($all_project_phids[$phid]);
+    }
+
+    $all_handles = $viewer->loadHandles($all_project_phids);
+    $all_handles = iterator_to_array($all_handles);
+
     foreach ($columns as $column) {
-      $task_phids = idx($task_map, $column->getPHID(), array());
+      if (!$this->showHidden) {
+        if ($column->isHidden()) {
+          continue;
+        }
+      }
+
+      $proxy = $column->getProxy();
+      if ($proxy && !$proxy->isMilestone()) {
+        // TODO: For now, don't show subproject columns because we can't
+        // handle tasks with multiple positions yet.
+        continue;
+      }
+
+      $task_phids = $layout_engine->getColumnObjectPHIDs(
+        $board_phid,
+        $column->getPHID());
+
       $column_tasks = array_select_keys($tasks, $task_phids);
 
+      // If we aren't using "natural" order, reorder the column by the original
+      // query order.
+      if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) {
+        $column_tasks = array_select_keys($column_tasks, array_keys($tasks));
+      }
+
       $panel = id(new PHUIWorkpanelView())
         ->setHeader($column->getDisplayName())
         ->setSubHeader($column->getDisplayType())
@@ -284,6 +299,11 @@ final class SprintBoardViewController
         $panel->setHeaderIcon($header_icon);
       }
 
+      $display_class = $column->getDisplayClass();
+      if ($display_class) {
+        $panel->addClass($display_class);
+      }
+
       if ($column->isHidden()) {
         $panel->addClass('project-panel-hidden');
       }
@@ -323,9 +343,11 @@ final class SprintBoardViewController
           $owner = $this->handles[$task->getOwnerPHID()];
         }
         $can_edit = idx($task_can_edit_map, $task->getPHID(), false);
+        $handles = array_select_keys($all_handles, $task->getProjectPHIDs());
         if ($is_sprint == true) {
           $cards->addItem(id(new SprintBoardTaskCard())
               ->setProject($project)
+              ->setProjectHandles($handles)
               ->setViewer($viewer)
               ->setTask($task)
               ->setOwner($owner)
@@ -335,6 +357,7 @@ final class SprintBoardViewController
           $cards->addItem(id(new ProjectBoardTaskCard())
               ->setViewer($viewer)
               ->setProject($project)
+              ->setProjectHandles($handles)
               ->setTask($task)
               ->setOwner($owner)
               ->setCanEdit($can_edit)
@@ -352,7 +375,7 @@ final class SprintBoardViewController
     $filter_menu = $this->buildFilterMenu(
       $viewer,
       $custom_query,
-      $engine,
+      $search_engine,
       $query_key);
 
     $manage_menu = $this->buildManageMenu($project, $this->showHidden);
@@ -379,19 +402,19 @@ final class SprintBoardViewController
     $crumbs->addAction($manage_menu);
 
     return $this->newPage()
-        ->setTitle(pht('%s Board', $project->getName()))
-        ->setPageObjectPHIDs(array($project->getPHID()))
-        ->setShowFooter(false)
-        ->setNavigation($nav)
-        ->setCrumbs($crumbs)
-        ->addQuicksandConfig(
-            array(
-                'boardConfig' => $behavior_config,
-            ))
-        ->appendChild(
-            array(
-                $board_box,
-            ));
+      ->setTitle(pht('%s Board', $project->getName()))
+      ->setPageObjectPHIDs(array($project->getPHID()))
+      ->setShowFooter(false)
+      ->setNavigation($nav)
+      ->setCrumbs($crumbs)
+      ->addQuicksandConfig(
+        array(
+          'boardConfig' => $behavior_config,
+        ))
+      ->appendChild(
+        array(
+          $board_box,
+        ));
   }
 
   private function readRequestState() {
@@ -413,31 +436,12 @@ final class SprintBoardViewController
     $this->sortKey = $sort_key;
   }
 
-  private function loadColumns(PhabricatorProject $project) {
-    $viewer = $this->getViewer();
-
-    $column_query = id(new PhabricatorProjectColumnQuery())
-        ->setViewer($viewer)
-        ->withProjectPHIDs(array($project->getPHID()));
-
-    if (!$this->showHidden) {
-      $column_query->withStatuses(
-          array(PhabricatorProjectColumn::STATUS_ACTIVE));
-    }
-
-    $columns = $column_query->execute();
-    $columns = mpull($columns, null, 'getSequence');
-    ksort($columns);
-
-    return $columns;
-  }
-
   private function buildSortMenu(
     PhabricatorUser $viewer,
     $sort_key) {
 
     $sort_icon = id(new PHUIIconView())
-        ->setIcon('fa-sort-amount-asc bluegrey');
+      ->setIcon('fa-sort-amount-asc bluegrey');
 
     $named = array(
       PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'),
@@ -471,14 +475,14 @@ final class SprintBoardViewController
     }
 
     $sort_button = id(new PHUIListItemView())
-        ->setName(pht('Sort: %s', $active_order))
-        ->setIcon('fa-sort-amount-asc')
-        ->setHref('#')
-        ->addSigil('boards-dropdown-menu')
-        ->setMetadata(
-            array(
-                'items' => hsprintf('%s', $sort_menu),
-            ));
+      ->setName(pht('Sort: %s', $active_order))
+      ->setIcon('fa-sort-amount-asc')
+      ->setHref('#')
+      ->addSigil('boards-dropdown-menu')
+      ->setMetadata(
+        array(
+          'items' => hsprintf('%s', $sort_menu),
+        ));
 
     return $sort_button;
   }
@@ -545,14 +549,14 @@ final class SprintBoardViewController
     }
 
     $filter_button = id(new PHUIListItemView())
-        ->setName(pht('Filter: %s', $active_filter))
-        ->setIcon('fa-search')
-        ->setHref('#')
-        ->addSigil('boards-dropdown-menu')
-        ->setMetadata(
-            array(
-                'items' => hsprintf('%s', $filter_menu),
-            ));
+      ->setName(pht('Filter: %s', $active_filter))
+      ->setIcon('fa-search')
+      ->setHref('#')
+      ->addSigil('boards-dropdown-menu')
+      ->setMetadata(
+        array(
+          'items' => hsprintf('%s', $filter_menu),
+        ));
 
     return $filter_button;
   }
@@ -562,7 +566,7 @@ final class SprintBoardViewController
     $show_hidden) {
 
     $request = $this->getRequest();
-    $viewer = $request->getViewer();
+    $viewer = $request->getUser();
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
@@ -622,14 +626,14 @@ final class SprintBoardViewController
     }
 
     $manage_button = id(new PHUIListItemView())
-        ->setName(pht('Manage Board'))
-        ->setIcon('fa-cog')
-        ->setHref('#')
-        ->addSigil('boards-dropdown-menu')
-        ->setMetadata(
-            array(
-                'items' => hsprintf('%s', $manage_menu),
-            ));
+      ->setName(pht('Manage Board'))
+      ->setIcon('fa-cog')
+      ->setHref('#')
+      ->addSigil('boards-dropdown-menu')
+      ->setMetadata(
+        array(
+          'items' => hsprintf('%s', $manage_menu),
+        ));
 
     return $manage_button;
   }
@@ -648,6 +652,12 @@ final class SprintBoardViewController
 
     $column_items = array();
 
+    if ($column->getProxyPHID()) {
+      $default_phid = $column->getProxyPHID();
+    } else {
+      $default_phid = $column->getProjectPHID();
+    }
+
     $column_items[] = id(new PhabricatorActionView())
       ->setIcon('fa-plus')
       ->setName(pht('Create Task...'))
@@ -656,6 +666,7 @@ final class SprintBoardViewController
       ->setMetadata(
         array(
           'columnPHID' => $column->getPHID(),
+          'projectPHID' => $default_phid,
         ));
 
     $batch_edit_uri = $request->getRequestURI();
@@ -672,12 +683,12 @@ final class SprintBoardViewController
       ->setDisabled(!$can_batch_edit);
 
     $detail_uri = $this->getApplicationURI(
-        'board/'.$this->id.'/column/'.$column->getID().'/');
+      'board/'.$this->id.'/column/'.$column->getID().'/');
 
     $column_items[] = id(new PhabricatorActionView())
-        ->setIcon('fa-columns')
-        ->setName(pht('Column Details'))
-        ->setHref($detail_uri);
+      ->setIcon('fa-columns')
+      ->setName(pht('Column Details'))
+      ->setHref($detail_uri);
 
     $can_hide = ($can_edit && !$column->isDefaultColumn());
     $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/';
@@ -718,7 +729,8 @@ final class SprintBoardViewController
     return $column_button;
   }
 
-   /**
+
+  /**
    * Add current state parameters (like order and the visibility of hidden
    * columns) to a URI.
    *
@@ -760,8 +772,8 @@ final class SprintBoardViewController
     // TODO: This should be cleaned up, but maybe we're going to make options
     // for each column or board?
     $edit_config = id(new ManiphestEditEngine())
-        ->setViewer($viewer)
-        ->loadDefaultEditConfiguration();
+      ->setViewer($viewer)
+      ->loadDefaultEditConfiguration();
     if ($edit_config) {
       $form_key = $edit_config->getIdentifier();
       $create_uri = "/maniphest/task/edit/form/{$form_key}/";
@@ -788,63 +800,64 @@ final class SprintBoardViewController
     $set_default = $request->getBool('default');
     if ($set_default) {
       $this
-          ->getProfilePanelEngine()
-          ->adjustDefault(PhabricatorProject::PANEL_WORKBOARD);
+        ->getProfilePanelEngine()
+        ->adjustDefault(PhabricatorProject::PANEL_WORKBOARD);
     }
 
     if ($request->isFormPost()) {
       if ($type == 'backlog-only') {
         $column = PhabricatorProjectColumn::initializeNewColumn($viewer)
-            ->setSequence(0)
-            ->setProperty('isDefault', true)
-            ->setProjectPHID($project->getPHID())
-            ->save();
+          ->setSequence(0)
+          ->setProperty('isDefault', true)
+          ->setProjectPHID($project->getPHID())
+          ->save();
 
         $project->setHasWorkboard(1)->save();
 
         return id(new AphrontRedirectResponse())
-            ->setURI($board_uri);
+          ->setURI($board_uri);
       } else {
         return id(new AphrontRedirectResponse())
-            ->setURI($import_uri);
+          ->setURI($import_uri);
       }
     }
 
     $new_selector = id(new AphrontFormRadioButtonControl())
-        ->setLabel(pht('Columns'))
-        ->setName('initialize-type')
-        ->setValue('backlog-only')
-        ->addButton(
-            'backlog-only',
-            pht('New Empty Board'),
-            pht('Create a new board with just a backlog column.'))
-        ->addButton(
-            'import',
-            pht('Import Columns'),
-            pht('Import board columns from another project.'));
+      ->setLabel(pht('Columns'))
+      ->setName('initialize-type')
+      ->setValue('backlog-only')
+      ->addButton(
+        'backlog-only',
+        pht('New Empty Board'),
+        pht('Create a new board with just a backlog column.'))
+      ->addButton(
+        'import',
+        pht('Import Columns'),
+        pht('Import board columns from another project.'));
 
     $default_checkbox = id(new AphrontFormCheckboxControl())
-        ->setLabel(pht('Make Default'))
-        ->addCheckbox(
-            'default',
-            1,
-            pht('Make the workboard the default view for this project.'),
-            true);
+      ->setLabel(pht('Make Default'))
+      ->addCheckbox(
+        'default',
+        1,
+        pht('Make the workboard the default view for this project.'),
+        true);
 
     $form = id(new AphrontFormView())
-        ->setUser($viewer)
-        ->appendRemarkupInstructions(
-            pht('The workboard for this project has not been created yet.'))
-        ->appendControl($new_selector)
-        ->appendControl($default_checkbox)
-        ->appendControl(
-            id(new AphrontFormSubmitControl())
-                ->addCancelButton($profile_uri)
-                ->setValue(pht('Create Workboard')));
+      ->setUser($viewer)
+      ->addHiddenInput('initialize', 1)
+      ->appendRemarkupInstructions(
+        pht('The workboard for this project has not been created yet.'))
+      ->appendControl($new_selector)
+      ->appendControl($default_checkbox)
+      ->appendControl(
+        id(new AphrontFormSubmitControl())
+          ->addCancelButton($profile_uri)
+          ->setValue(pht('Create Workboard')));
 
     $box = id(new PHUIObjectBoxView())
-        ->setHeaderText(pht('Create Workboard'))
-        ->setForm($form);
+      ->setHeaderText(pht('Create Workboard'))
+      ->setForm($form);
 
     return $box;
   }
@@ -857,13 +870,13 @@ final class SprintBoardViewController
     $profile_uri = $this->getApplicationURI("profile/{$id}/");
 
     return $this->newDialog()
-        ->setTitle(pht('Unable to Create Workboard'))
-        ->appendParagraph(
-            pht(
-                'The workboard for this project has not been created yet, '.
-                'but you do not have permission to create it. Only users '.
-                'who can edit this project can create a workboard for it.'))
-        ->addCancelButton($profile_uri);
+      ->setTitle(pht('Unable to Create Workboard'))
+      ->appendParagraph(
+        pht(
+          'The workboard for this project has not been created yet, '.
+          'but you do not have permission to create it. Only users '.
+          'who can edit this project can create a workboard for it.'))
+      ->addCancelButton($profile_uri);
   }
 
 }
diff --git a/src/controller/board/SprintManiphestEditEngine.php b/src/controller/board/SprintManiphestEditEngine.php
index cce13c934db9e57b3a14647a1994b37d08d2d70a..2b348cb722b99aac08a71d78c694f1101b97325b 100644
--- a/src/controller/board/SprintManiphestEditEngine.php
+++ b/src/controller/board/SprintManiphestEditEngine.php
@@ -280,21 +280,36 @@ final class SprintManiphestEditEngine
       return new Aphront404Response();
     }
 
-    // If the workboard's project has been removed from the card's project
-    // list, we are going to remove it from the board completely.
+    // If the workboard's project and all descendant projects have been removed
+    // from the card's project list, we are going to remove it from the board
+    // completely.
+
+    // TODO: If the user did something sneaky and changed a subproject, we'll
+    // currently leave the card where it was but should really move it to the
+    // proper new column.
+
+    $descendant_projects = id(new PhabricatorProjectQuery())
+      ->setViewer($viewer)
+      ->withAncestorProjectPHIDs(array($column->getProjectPHID()))
+      ->execute();
+    $board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
+    $board_phids[$column->getProjectPHID()] = $column->getProjectPHID();
+
     $project_map = array_fuse($task->getProjectPHIDs());
-    $remove_card = empty($project_map[$column->getProjectPHID()]);
+    $remove_card = !array_intersect_key($board_phids, $project_map);
 
     $positions = id(new PhabricatorProjectColumnPositionQuery())
-        ->setViewer($viewer)
-        ->withColumns(array($column))
-        ->execute();
+      ->setViewer($viewer)
+      ->withBoardPHIDs(array($column->getProjectPHID()))
+      ->withColumnPHIDs(array($column->getPHID()))
+      ->execute();
     $task_phids = mpull($positions, 'getObjectPHID');
 
     $column_tasks = id(new ManiphestTaskQuery())
-        ->setViewer($viewer)
-        ->withPHIDs($task_phids)
-        ->execute();
+      ->setViewer($viewer)
+      ->withPHIDs($task_phids)
+      ->needProjectPHIDs(true)
+      ->execute();
 
     if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
       // TODO: This is a little bit awkward, because PHP and JS use
@@ -303,20 +318,20 @@ final class SprintManiphestEditEngine
       $sort_map = array();
       foreach ($positions as $position) {
         $sort_map[$position->getObjectPHID()] = array(
-            -$position->getSequence(),
-            $position->getID(),
+          -$position->getSequence(),
+          $position->getID(),
         );
       }
     } else {
       $sort_map = mpull(
-          $column_tasks,
-          'getPrioritySortVector',
-          'getPHID');
+        $column_tasks,
+        'getPrioritySortVector',
+        'getPHID');
     }
 
     $data = array(
-        'removeFromBoard' => $remove_card,
-        'sortMap' => $sort_map,
+      'removeFromBoard' => $remove_card,
+      'sortMap' => $sort_map,
     );
 
     // TODO: This should just use HandlePool once we get through the EditEngine
@@ -324,22 +339,32 @@ final class SprintManiphestEditEngine
     $owner = null;
     if ($task->getOwnerPHID()) {
       $owner = id(new PhabricatorHandleQuery())
-          ->setViewer($viewer)
-          ->withPHIDs(array($task->getOwnerPHID()))
-          ->executeOne();
+        ->setViewer($viewer)
+        ->withPHIDs(array($task->getOwnerPHID()))
+        ->executeOne();
     }
 
+    $handle_phids = $task->getProjectPHIDs();
+    $handle_phids = array_fuse($handle_phids);
+    $handle_phids = array_diff_key($handle_phids, $board_phids);
+
+    $project_handles = $viewer->loadHandles($handle_phids);
+    $project_handles = iterator_to_array($project_handles);
+
     $projects = $request->getArr('projectPHIDs');
     $project = $this->getSprintProjectforTask($viewer, $projects);
 
     $tasks = id(new SprintBoardTaskCard())
         ->setViewer($viewer)
         ->setProject($project)
+        ->setProjectHandles($project_handles)
         ->setTask($task)
         ->setOwner($owner)
         ->setCanEdit(true)
         ->getItem();
 
+    $tasks->addClass('phui-workcard');
+
     $payload = array(
         'tasks' => $tasks,
         'data' => $data,
diff --git a/src/query/SprintQuery.php b/src/query/SprintQuery.php
index 424807c43652380cbf41f961607454ac0a2e3a11..bb94457cf525504104a42d046dc63d521792ab25 100644
--- a/src/query/SprintQuery.php
+++ b/src/query/SprintQuery.php
@@ -304,8 +304,7 @@ final class SprintQuery extends SprintDAO {
             ->setViewer($this->viewer)
             ->withBoardPHIDs(array($this->projectPHID))
             ->withObjectPHIDs(mpull($tasks, 'getPHID'))
-            ->withColumns($columns)
-            ->needColumns(true)
+            ->withColumnPHIDs(mpull($columns, 'getPHID'))
             ->execute();
         $positions = mpull($positions, null, 'getObjectPHID');
      } else {
diff --git a/src/view/SprintBoardTaskCard.php b/src/view/SprintBoardTaskCard.php
index e40266fcefc832544f7d992fbe9eb483d5850229..b64ff7e38dfc9db3fec4dfa1b5553c01da82df72 100644
--- a/src/view/SprintBoardTaskCard.php
+++ b/src/view/SprintBoardTaskCard.php
@@ -4,6 +4,7 @@ final class SprintBoardTaskCard extends Phobject {
 
   private $project;
   private $viewer;
+  private $projectHandles;
   private $task;
   private $owner;
   private $canEdit;
@@ -28,6 +29,15 @@ final class SprintBoardTaskCard extends Phobject {
     return $this->viewer;
   }
 
+  public function setProjectHandles(array $handles) {
+    $this->projectHandles = $handles;
+    return $this;
+  }
+
+  public function getProjectHandles() {
+    return $this->projectHandles;
+  }
+
   public function setTask(ManiphestTask $task) {
     $this->task = $task;
     return $this;
@@ -128,14 +138,12 @@ final class SprintBoardTaskCard extends Phobject {
       $card->addHandleIcon($owner, $owner->getName());
     }
     $card->addAttribute($this->getCardAttributes());
-    $project_phids = array_fuse($task->getProjectPHIDs());
-    unset($project_phids[$this->project->getPHID()]);
 
-    if ($project_phids) {
-      $handle_list = $viewer->loadHandles($project_phids);
+    $project_handles = $this->getProjectHandles();
+    if ($project_handles) {
       $tag_list = id(new PHUIHandleTagListView())
-          ->setSlim(true)
-          ->setHandles($handle_list);
+        ->setSlim(true)
+        ->setHandles($project_handles);
       $card->addAttribute($tag_list);
     }
     return $card;